在许多控制器的编辑方法中,您可以初始化一个新对象并编辑现有对象
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
有什么办法可以做到这一点吗?
您可以在页面模型中创建一个
persisted
范围:scope :persisted, -> { where.not(id: nil) }
,这可以避免在每个关联页面上进行迭代以检查它是否是新记录。
@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
您始终可以拒绝新记录的页面...
%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
我以不同的方式处理这个问题。我不在控制器中创建新对象,而是直接在表单中创建新对象。
首先,要运行控制器,为什么要将 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 部分)
只需将此代码放入初始化程序中(
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
。
另一种更清晰的语法,使用 ActiveRecord
where.not
并仍然返回 ActiveRecord
集合:
- @magazine.pages.where.not(id: nil).each do |page|
...