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 => tru开发者_运维技巧e, :dependent => :destroy
module AutoBuildItemSource
def item_source
super || build_item_source
end
end
include AutoBuildItemSource
end
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.)
(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.
How about creating the item_source
when the RandomProduct
is created:
class RandomProduct << ActiveRecord::Base
after_create :create_item_source
end
Of course, if you need to pass specific arguments to the item source, you could do something like this, instead:
class RandomProduct << ActiveRecord::Base
after_create :set_up_item_source
protected
def set_up_item_source
create_item_source(
:my => "options",
:go => "here"
)
end
end
Not that you really need a gem for this since it's so simple to do yourself, but here's a gem that makes it even easier to declare an auto-build:
https://github.com/TylerRick/active_record_auto_build_associations
精彩评论