Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to autobuild an associated polymorphic activerecord object in rails 3

class ItemSource < ActiveRecord::Base
  belongs_to :product, :polymorphic => true
end

class RandomProduct < ActiveRecord::Base
  has_one :item_source, :as => :product, :autosave => true, :dependent => :destroy
end

What I'd like to do is is call:

a = RandomProduct.find(1)
a.item_source

and if item_source doesn't already exist (= nil), then build it automatically (build_item_source).

previously, I did this with alias_chain_method, but that's not supported in Rails 3.
oh, and I also tried this to no avail:

class RandomProduct < ActiveRecord::Base
  has_one :item_source, :as => :product, :autosave => true, :dependent => :destroy

  module AutoBuildItemSource
    def item_source
      super || build_item_source
    end
  end  
  include AutoBuildItemSource
end
like image 856
Paul Avatar asked Sep 27 '10 08:09

Paul


People also ask

What is polymorphic association in Rails?

Polymorphic relationship in Rails refers to a type of Active Record association. This concept is used to attach a model to another model that can be of a different type by only having to define one association.

How would you choose between Belongs_to and Has_one?

They essentially do the same thing, the only difference is what side of the relationship you are on. If a User has a Profile , then in the User class you'd have has_one :profile and in the Profile class you'd have belongs_to :user . To determine who "has" the other object, look at where the foreign key is.

What is Active Record :: Base in Rails?

ActiveRecord::Base indicates that the ActiveRecord class or module has a static inner class called Base that you're extending.


2 Answers

In Rails 3, alias_method_chain (and alias_method, and alias) work fine:

class User < ActiveRecord::Base
  has_one :profile, :inverse_of => :user

  # This works:
  # 
  #   def profile_with_build
  #     profile_without_build || build_profile
  #   end
  #   alias_method_chain :profile, :build
  #
  # But so does this:

  alias profile_without_build profile
  def profile
    profile_without_build || build_profile
  end
end

But there's always accept_nested_attributes_for as an alternative, which calls build when profile_attributes are set. Combine it with delegate (optional) and you won't have to worry if the record exists or not:

class User < ActiveRecord::Base
  has_one :profile, :inverse_of => :user
  delegate :website, :to => :profile, :allow_nil => true
  accepts_nested_attributes_for :profile
end

User.new.profile # => nil
User.new.website # => nil
u = User.new :profile_attributes => { :website => "http://example.com" }
u.profile # => #<Profile id: nil, user_id: nil, website: "http://example.com"...>

If the association is always created, delegation isn't necessary (but may be helpful, anyhow).

(Note: I set :inverse_of to make Profile.validates_presence_of :user work and to generally save queries.)

like image 77
stephencelis Avatar answered Nov 23 '22 05:11

stephencelis


(Rails 4, FYI)

I personally prefer setting it up with after_initialize

after_initialize :after_initialize

def after_initialize
  build_item_source if item_source.nil?
end

This also works well because you can automatically use forms with what would otherwise be an empty association (HAML because it's nicer):

= form_for @product do |f|
  = f.fields_for :item_source do |isf|
    = isf.label :prop1
    = isf.text_field :prop1

If you didn't have the item_source built already, the label and text field wouldn't render at all.

like image 42
Tim Avatar answered Nov 23 '22 06:11

Tim