11

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

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

有没有办法做到这一点?

4

6 回答 6

14

You can create in your Page model a persisted scope: scope :persisted, -> { where "id IS NOT NULL" }, which avoids iterating on each associated page to check whether it's a new record or not.

于 2013-11-07T20:07:09.823 回答
4

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

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

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

这允许您调用.persisted与杂志关联的页面的任何 ActiveRecord 关系。它不会再次访问数据库,因为它只是过滤预加载的对象,返回持久化的对象。

使代码可重用

由于我想定期重用此代码,我可以将其拉出到模块中

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
于 2013-11-08T10:46:52.633 回答
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
于 2013-11-07T17:41:19.133 回答
2

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

- @magazine.pages.where.not(id: nil).each do |page|
    ...
于 2019-05-15T15:03:18.087 回答
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行中,您要求页面的杂志表单字段,然后告诉它只呈现特定的新页面的字段,您正在使用@magazine.pages.build.

参考:
fields_for
嵌套模型表单 Railscast(另见第 2 部分)

于 2013-11-07T19:24:10.747 回答
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任何模型和关联。

于 2016-07-23T18:34:42.280 回答