Search code examples
ruby-on-railsactiverecord

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


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

class MagazinesController < ApplicationController
   def edit
      @magazine = Magazine.find(params[:magazine_id])
      @page = Page.find(params[:id])
      @new_page = @magazine.pages.new
   end
end

However in a view you will often want to cycle through the persisted objects and treat the new object separately

# magazines#edit
%h4 Existing pages
- @magazine.pages.each do |page|
  %p= link_to page, page.title

The problem

...is that the pages association contains both existing (persisted) pages but also the new page which we made via @new_page = @magazine.pages.new.

It's easy to deal with this however it's ugly

%h4 Existing pages
- @magazine.pages.each do |page|
  - if page.persisted?
    %p= link_to page, page.title

I would like to use some assocition method to select only those pages which are persisted:

%h4 Existing pages
- @magazine.pages.persisted.each do |page|
  %p= link_to page, page.title

Is there any way of doing this?


Solution

  • 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