Как вы ссылаетесь только на сохраненные записи в активной ассоциации записей

В методе редактирования многих контроллеров вы инициализируете новый объект и редактируете существующие объекты

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

Есть ли способ сделать это?

Ответ 1

Оба предложения от @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

Затем он может быть включен в методы ассоциации с использованием лямбда:

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

Вы можете создать в своей модели страницы область persisted: scope :persisted, -> { where "id IS NOT NULL" }, которая позволяет избежать итерации на каждой связанной странице, чтобы проверить, не является ли она новой записью или нет.

Ответ 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

Ответ 4

Я подхожу к этой проблеме по-разному. Я не создаю новый объект в контроллере, но вместо этого делаю это непосредственно в форме.

Во-первых, чтобы запустить ваш контроллер, почему вы передаете 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)

Ответ 5

Другой более чистый синтаксис, использующий ActiveRecord where.not и все еще возвращающий коллекцию ActiveRecord:

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

Ответ 6

Ответы Rails 4 и 5:

Просто поместите этот код в initializer (файл в каталог 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 для любой модели и ассоциации.