9

Similar to this question, how do I set a property on the join model just before save in this context?

class Post < ActiveRecord::Base
  has_many :post_assets
  has_many :assets, :through => :post_assets
  has_many :featured_images, :through => :post_assets, :class_name => "Asset", :source => :asset, :conditions => ['post_assets.context = ?', "featured"]

end

class PostAssets < ActiveRecord::Base
  belongs_to :post
  belongs_to :asset

  # context is so we know the scope or role
  # the join plays
  validates_presences_of :context
end

class Asset < ActiveRecord::Base
  has_many :post_assets
  has_many :posts, :through => :post_assets
end

I just want to be able to do this:

@post = Post.create!(:title => "A Post")
@post.featured_images << Asset.create!(:title => "An Asset")
# ...
@post = Post.first
@featured = @post.featured_images.first
  #=> #<Asset id: 1, title: "An Asset">
@featured.current_post_asset #=> #<PostAsset id: 1, context: "featured">

How would that work? I've been banging my head over it all day :).

What currently happens is when I do this:

@post.featured_images << Asset.create!(:title => "An Asset")

Then the join model PostAsset that gets created never gets a chance to set context. How do I set that context property? It looks like this:

PostAsset.first #=> #<PostAsset id: 1, context: nil>

Update:

I have created a test gem to try to isolate the problem. Is there an easier way to do this?!

This ActsAsJoinable::Core class makes it so you can have many to many relationships with a context between them in the join model. And it adds helper methods. The basic tests show basically what I'm trying to do. Any better ideas on how to do this properly?

Community
  • 1
  • 1
Lance
  • 75,200
  • 93
  • 289
  • 503
  • This also seems similar to my question earlier this week: http://stackoverflow.com/questions/3035064/updating-extra-attributes-in-a-has-many-through-relationship-using-rails I just retroactively change the join model. I assume there must be a callback, however, to do work on the join model before it is saved. +1 'cause I'm curious. – Robbie Jun 21 '10 at 02:40
  • there is a `before_add` and `after_add` callback on the non-join model (so `assets` on the Post), but you can't really access the join model any way without calling `PostAsset.first(:conditions => {...})`. It seems like it's a missing feature in Rails after reading all the forums. – Lance Jun 21 '10 at 03:42

1 Answers1

1

Look at the has_many options in the ActiveRecord::Associations::ClassMethods API located here: http://rails.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001316

This is the most interesting quote:

:conditions

Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1. Record creations from the association are scoped if a hash is used. has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create or @blog.posts.build.

So I believe your conditions must be specified as a hash, like so:

class Post < ActiveRecord::Base
  has_many :post_assets
  has_many :featured_post_assets, :conditions => { :context => 'featured' }

  has_many :assets, :through => :post_assets

  has_many :featured_images, :through => :featured_post_assets,
           :class_name => "Asset", :source => :asset,
end

And you should also do the following:

@post.featured_images.build(:title => "An asset")

instead of:

@post.featured_images << Asset.create!(:title => "An Asset")

This should call the scoped asset build, as suggested in the quote above to add the context field to asset. It will also save both the join model object (post_asset) and the asset object to the database at the same time in one atomic transaction.

Patrick Klingemann
  • 8,884
  • 4
  • 44
  • 51
  • what about `@post.featured_images << Asset.first` and related (not actually building/creating it)? – Lance Jun 21 '10 at 20:07
  • I'm pretty sure build doesn't save the new record until the @post object is saved. << will save the new object before the @post object is saved, create will do the same. – Patrick Klingemann Jun 23 '10 at 04:56
  • This does not work with :through associations. At least not in Rails 3.2.12. When specifying conditions on a through association, object.association.build does not set those conditions. Maybe this is a bug in Rails, or just not meant to work with :through associations. – davekaro Feb 26 '13 at 18:54
  • 1
    If anyone else runs into this, the right way to set the conditions is on the join model. So, in the above example it would turn into something like has_many :featured_post_assets, :conditions => { :context => "featured" }. Then leave off the conditions from :featured_images association. – davekaro Mar 01 '13 at 02:45
  • I've updated my answer to include the changes suggested by @davekaro – Patrick Klingemann Mar 01 '13 at 13:08
  • 2
    In Rails4 using `:conditions` is now deprecated in favor of a lambda. I've asked a very similar question to this one here: http://stackoverflow.com/questions/17098708/losing-an-attribute-when-saving-through-an-association-w-scope-rails-4-0-0-rc2 – Mario Zigliotto Jun 23 '13 at 05:55