views:

217

answers:

1

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?

+1  A: 

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 :assets, :through => :post_assets
  has_many :featured_images, :through => :post_assets,
           :class_name => "Asset", :source => :asset,
           :conditions => { 'post_assets.context' => "featured" }
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
This looks like it should work.
thomasfedb
what about `@post.featured_images << Asset.first` and related (not actually building/creating it)?
viatropos
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