Как вы ссылаетесь только на сохраненные записи в активной ассоциации записей
В методе редактирования многих контроллеров вы инициализируете новый объект и редактируете существующие объекты
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
для любой модели и ассоциации.