In the edit method of many controllers you initialize a new object and edit existing objects
class MagazinesController < ApplicationController
def edit
I approach this problem differently. I do not create a new object in the controller, but instead do so directly in the form.
First, to run through your controller, why are you passing the page_id as your primary params[:id]
to your Magazines controller? Looks to me that you want this:
class MagazinesController < ApplicationController
def edit
@magazine = Magazine.find(params[:id]).includes(:pages)
end
end
Then, in your magazines#edit
view, you'd do this:
%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.
In that fields_for
line, you're asking for magazine-form fields for pages, but then telling it to only render the fields for a specific, new page, which you are creating on the fly with @magazine.pages.build
.
References:
fields_for
Nested Model Form Railscast (See also Part 2)
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.
Both the suggestions from @Florent2 and @CDub are sound. However @florent2's suggestion meant hitting the database again (and potentially ditching any preset eager loading which I didn't want to do) and @CDub's suggestion didn't quite work out in terms of code. Here is what I ended up going with:
class Magazine < ActiveRecord::Base
has_many :pages do
def persisted
collect{ |page| page if page.persisted? }
end
end
end
this allows you to call .persisted
on any ActiveRecord relation of pages associated with Magazine. It doesn't hit the database again as it simply filters through the pre-loaded objects returning the ones which are persisted.
Since I want to reuse this code on a regular basis I can pull it out into a module
module PersistedExtension
def persisted
select{|item| item if item.persisted?}
end
end
It can then be included into the association methods using a lambda:
class Magazine < ActiveRecord::Base
# ...
has_many :pages, -> { extending PersistedExtension }
end
and I can call it intuitively:
@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
Another cleaner syntax, using ActiveRecord where.not
and still get back an ActiveRecord
collection:
- @magazine.pages.where.not(id: nil).each do |page|
...
You could always reject pages that are new records...
%h4 Existing pages
- @magazine.pages.persisted.each do |page|
%p= link_to page, page.title
where on Page
you'd have something like:
def self.persisted
reject {|page| page.new_record? }
end
Just put this code in initializer (file in config/initializers
directory, with .rb
extension):
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
You can now call persisted
on any model and association.