How do you reference only the persisted records in an active record association

后端 未结 6 530
一生所求
一生所求 2020-12-10 14:05

In the edit method of many controllers you initialize a new object and edit existing objects

class MagazinesController < ApplicationController
   def edit         


        
相关标签:
6条回答
  • 2020-12-10 14:46

    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)

    0 讨论(0)
  • 2020-12-10 14:50

    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.

    0 讨论(0)
  • 2020-12-10 14:52

    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:

    Returning only persisted records for a particular association

    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.

    Making the code reusable

    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
    
    0 讨论(0)
  • 2020-12-10 14:56

    Another cleaner syntax, using ActiveRecord where.not and still get back an ActiveRecord collection:

    - @magazine.pages.where.not(id: nil).each do |page|
        ...
    
    0 讨论(0)
  • 2020-12-10 14:57

    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
    
    0 讨论(0)
  • 2020-12-10 15:02

    Rails 4 and 5 answer:

    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.

    0 讨论(0)
提交回复
热议问题