How do I create a scope that traverses a polymorphic association? - ruby-on-rails

I have these models (psuedocode):
class Order
has_many :line_items
end
class LineItem
belongs_to :purchasable, :polymorphic => true
belongs_to :order
end
class Tile
has_one :line_item, :as => :purchasable
end
I want to make a scope that allows me to access tiles from an order. something like Order#tiles so that I can do things like this in controllers:
my_order.tiles.new(...)
my_order.tiles.find(params[:id]).update_attributes(...)
How can I construct such a scope? (or is there another technique I should use?)

The associations you have don't work together. I think you might be looking for something like this:
class Order
has_many :line_items
has_many :tiles, :through => :line_items, :source => :purchasable, :source_type => "Tile"
...
end
class LineItem
belongs_to :order
belongs_to :purchasable, :polymorphic => true
...
end
class Tile
has_many :line_items, :as => :purchasable
...
end

Related

Can I use an extended active record association in a has_many :through declaration?

I've extended many of my has_many declarations to filter / join / preload associations. I'd like to re-use some of these extensions when I declare has_many :through relationships. Is this possible? Should I take a different approach?
Example:
I have this in my Library Model:
class Library < ActiveRecord::Base
has_many :meals, :dependent => :destroy do
def enabled
where(:enabled => true)
end
end
end
My Meal Model has this:
class Meal < ActiveRecord::Base
has_many :servings, :inverse_of => :meal, :dependent => :destroy
end
I'd like my library to have many servings, but only from the enabled meals. There are a couple ways I can do this:
# repeat the condition in the has_many :servings declaration
class Library < ActiveRecord::Base
has_many :servings, :through => :meals, :conditions => ["meals.enabled = ?", true]
end
# declare a different meals association for only the enabled meals
class Library < ActiveRecord::Base
has_many :enabled_meals, :class_name => "Meals", :conditions => [:enabled => true]
has_many :servings, :through => :enabled_meals
end
Is there any way to re-use the extension to my existing :meals declaration? (def enabled in the first code block)
Looks a lot like you want to use activerecord-association-extensions, as described here http://blog.zerosum.org/2007/2/8/activerecord-association-extensions.html.
I haven't tried it, but I think you could do:
module LibraryMealExtensions
def enabled?
where(:enabled=>true)
end
def standard_includes
includes(:servings)
end
end
class Library < ActiveRecord::Base
has_many :meals, :dependent => :destroy, :extend=>LibraryMealExtensions
has_many :servings, :through => :meals, :extend=>LibraryMealExtensions
end
Not sure about the "enabled=>true" there - you might have to say
where("meals.enabled=true")
b/c of confusion with aliases.

Expected model.rb to define MODEL

I have 3 tables Collections, Tracks and ProductContributors
Association of them is as follows
class Collection < ActiveRecord::Base
has_many :product_contributors, :as => :product
has_many :tracks, :through => Product_contributors, :as=> :product
end
class Track < ActiveRecord::Base
has_many :product_contributors, :as => :product
has_many :collections, :through => Product_contributors, :as => :product
end
class ProductContributor < < ActiveRecord::Base
belongs_to :product, :polymorphic => true
belongs_to :collection
belongs_to :track
end
whenever i hit the url for product contributor i get the following error :
Expected /app/models/track.rb to define TRACK
I've gone through this url but didnt help me in any case. I dont have the autoload issue, all my models are loaded properl
Any help would be highly appreciated..!!
I dare say its because of the typo in your Track class.
has_many :collections, :through => Product_contributors, :as => :product
is not valid. Try:
has_many :collections, :through => :product_contributors, :as => :product
Basically, it is trying to load the Model, but its finding the typo in the association, and it is then not loading, causing it to seem like the class is not there. I assume you will have a similar situation with the Collection class as well.

Eager loading nested association and scope

I'm a beginner and it's hard to explain my problem:
My models:
class Skill
has_many :categories
has_many :positions, :through => :categories
end
class Category
belongs_to :skill
has_many :positions
end
class Position
belongs_to :category
has_one :skill, :through => :category
end
I can successfully eager load everything, like this:
#skills = Skill.includes(:positions)
However sometimes I want to apply a scope on the Positions:
class Position
...
scope :active, where(:hidden => false)
end
I wish I could do:
#skills = Skill.includes(:positions.active)
Instead, I apply the scope in the views, but the eager loading doesn't work anymore:
<%= skill.positions.acitve ... %>
Is it possible to have both eager loading and scope?
You could use another association:
class Skill
has_many :categories
has_many :positions, :through => :categories
has_many :active_positions, :through => :categories
end
class Category
belongs_to :skill
has_many :positions
has_many :active_positions, :class_name => "Position", :conditions => {:hidden => false}
end
class Position
belongs_to :category
has_one :skill, :through => :category
end
And then
#skills = Skill.includes(:active_positions)
But then you'll get two associations. If you ever use skill.positions, all the skill's positions will be loaded from the database. You should only use skill.active_positions.
Try this:
#skills = Skill.includes(:positions).where('position.active = TRUE')

Ruby on Rails: Nested named scopes

Is there any way to nest named scopes inside of each other from different models?
Example:
class Company
has_many :employees
named_scope :with_employees, :include => :employees
end
class Employee
belongs_to :company
belongs_to :spouse
named_scope :with_spouse, :include => :spouse
end
class Spouse
has_one :employee
end
Is there any nice way for me to find a company while including employees and spouses like this:
Company.with_employees.with_spouse.find(1)
or is it necessary for me to define another named_scope in Company:
:with_employees_and_spouse, :include => {:employees => :spouse}
In this contrived example, it's not too bad, but the nesting is much deeper in my application, and I'd like it if I didn't have to add un-DRY code redefining the include at each level of the nesting.
You can use default scoping
class Company
default_scope :include => :employees
has_many :employees
end
class Employee
default_scope :include => :spouse
belongs_to :company
belongs_to :spouse
end
class Spouse
has_one :employee
end
Then this should work. I have not tested it though.
Company.find(1) # includes => [:employee => :spouse]
You need define all the time all of your conditions. But you can define some method to combine some named_scope
class Company
has_many :employees
named_scope :with_employees, :include => :employees
named_scope :limit, :lambda{|l| :limit => l }
def with_employees_with_spouse
with_employees.with_spouse
end
def with_employees_with_spouse_and_limit_by(limit)
with_employees_with_spouse.limit(limit)
end
end
class Employee
belongs_to :company
belongs_to :spouse
named_scope :with_spouse, :include => :spouse
end
class Spouse
has_one :employee
end
try this
Company.with_employees.merge( Employees.with_spouse)

Rails polymorphic relationship in the other direction

Having set up my polymorphic relationship like so:
class Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => true
belongs_to :user
end
class Wine < ActiveRecord::Base
has_many :reviews, :as => :reviewable
end
class Beer < ActiveRecord::Base
has_many :reviews, :as => :reviewable
end
I can do Wine.last.reviews and Beer.find(3).reviews etc...
What I'm strugling to do is go in the other direction, i.e. Lets say I want to find the last 10 reviews for Wine and the last 10 reviews for Beer.
The easiest way to do this is probably to add a named scope to your Review model that specifies the reviewable_type column.
Like so:
class Review < ActiveRecord::Base
belongs_to :reviewable, :polymorphic => true
belongs_to :user
named_scope :for_wines, :conditions => { :reviewable_type => 'Wine' }
named_scope :for_beers, :conditions => { :reviewable_type => 'Beer' }
end
That way you have the flexibility of scoping when finding your results...
Review.for_wines.approved.all
Review.for_beers.active.find(:all, :order => 'created_at')
etc

Resources