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

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

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 "id IS NOT NULL" } ,这可以避免在每个关联页面上进行迭代,以检查它是否是新记录。

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

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

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

这允许您在与Magazine关联的页面的任何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]传递给Magazines控制器? 在我看来你想要这个:

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

然后,在你的magazines#edit view中,你会这样做:

 %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部分)

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