如何仅引用活动记录关联中的持久记录

问题描述 投票:0回答:6

在许多控制器的编辑方法中,您可以初始化一个新对象并编辑现有对象

class MagazinesController < ApplicationController
   def edit
      @magazine = Magazine.find(params[:magazine_id])
      @page = Page.find(params[:id])
      @new_page = @magazine.pages.new
   end
end

但是,在视图中,您经常希望循环浏览持久化对象并单独处理新对象

# magazines#edit
%h4 Existing pages
- @magazine.pages.each do |page|
  %p= link_to page, page.title

问题

...是

pages
关联既包含现有(持久)页面,也包含我们通过
@new_page = @magazine.pages.new
创建的新页面。

处理这个很容易,但很丑陋

%h4 Existing pages
- @magazine.pages.each do |page|
  - if page.persisted?
    %p= link_to page, page.title

我想使用一些关联方法来仅选择那些保留的页面:

%h4 Existing pages
- @magazine.pages.persisted.each do |page|
  %p= link_to page, page.title

有什么办法可以做到这一点吗?

ruby-on-rails activerecord
6个回答
17
投票

您可以在页面模型中创建一个

persisted
范围:
scope :persisted, -> { where.not(id: nil) }
,这可以避免在每个关联页面上进行迭代以检查它是否是新记录。


4
投票

@Florent2 和 @CDub 的建议都很合理。然而,@florent2 的建议意味着再次访问数据库(并且可能会放弃我不想做的任何预设的急切加载),而 @CDub 的建议在代码方面并没有完全奏效。这就是我最终的结果:

仅返回特定关联的持久记录

class Magazine < ActiveRecord::Base
  has_many :pages do 
    def persisted
      collect{ |page| page if page.persisted? }
    end
  end
end

这允许您在与杂志关联的页面的任何 ActiveRecord 关系上调用

.persisted
。它不会再次访问数据库,因为它只是过滤预加载的对象,返回持久化的对象。

使代码可重用

因为我想定期重用此代码,所以我可以将其提取到模块中

module PersistedExtension
  def persisted
    select{|item| item if item.persisted?}
  end
end

然后可以使用 lambda 将其包含到关联方法中:

class Magazine < ActiveRecord::Base
  # ...
  has_many :pages, -> { extending PersistedExtension }

end

我可以直观地称呼它:

@magazine = Magazine.first

@magazine.pages.persisted
# => array of pages which are persisted

# the new persisted association extension works on any AR result set
@magazine.pages.order('page ASC').persisted

3
投票

您始终可以拒绝新记录的页面...

%h4 Existing pages
- @magazine.pages.persisted.each do |page|
    %p= link_to page, page.title

Page
上你会看到类似:

def self.persisted
  reject {|page| page.new_record? }
end

2
投票

我以不同的方式处理这个问题。我不在控制器中创建新对象,而是直接在表单中创建新对象。

首先,要运行控制器,为什么要将 page_id 作为主要

params[:id]
传递到杂志控制器?在我看来你想要这个:

class MagazinesController < ApplicationController
   def edit
      @magazine = Magazine.find(params[:id]).includes(:pages)
   end
end

然后,在您的

magazines#edit
视图中,您可以执行以下操作:

%h4 Existing pages
- @magazine.pages.each do |page|
  %p= link_to page, page.title

= form_for @magazine do |f|
  = f.fields_for :pages, @magazine.pages.build do |builder|
    = builder.text_field :title
    # etc.

fields_for
行中,您要求页面的杂志表单字段,然后告诉它仅呈现您使用
@magazine.pages.build
即时创建的特定新页面的字段。

参考资料:
fields_for
嵌套模型形式 Railscast(另请参阅第 2 部分)


2
投票

Rails 4 和 5 答案:

只需将此代码放入初始化程序中(

config/initializers
目录中的文件,扩展名为
.rb
):

module MyApp
  module ActiveRecordExtensions
    extend ActiveSupport::Concern

    class_methods do

      def persisted
        select(&:persisted?)
      end

    end
  end
end

ActiveSupport.on_load :active_record do
  include MyApp::ActiveRecordExtensions
end

您现在可以在任何模型和关联上调用

persisted


2
投票

另一种更清晰的语法,使用 ActiveRecord

where.not
并仍然返回
ActiveRecord
集合:

- @magazine.pages.where.not(id: nil).each do |page|
    ...
© www.soinside.com 2019 - 2024. All rights reserved.