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

时间:2013-11-07 17:34:48

标签: ruby-on-rails activerecord

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

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

有没有办法做到这一点?

6 个答案:

答案 0 :(得分:12)

您可以在页面模型中创建persisted范围:scope :persisted, -> { where "id IS NOT NULL" },这可以避免在每个关联页面上进行迭代,以检查它是否是新记录。

答案 1 :(得分:3)

来自@ 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

答案 2 :(得分:2)

您可以随时拒绝新记录的页面...

%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

答案 3 :(得分:1)

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

首先,要通过控制器运行,为什么要将page_id作为主要params[:id]传递给Magazines控制器?在我看来你想要这个:

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行中,您要求为页面设置杂志表单字段,然后告诉它仅渲染特定新页面的字段,您正在使用{{{ 1}}。

参考文献:
fields_for
Nested Model Form Railscast(另见第2部分)

答案 4 :(得分:1)

另一种更简洁的语法,使用ActiveRecord where.not并仍返回ActiveRecord集合:

- @magazine.pages.where.not(id: nil).each do |page|
    ...

答案 5 :(得分:0)

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