Eager loading nested association and scope - ruby-on-rails

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')

Related

creating associations rails during CRUD

I have a model with associations. How to create/update the associations as CRUD operations are performed on the model.
That is, when I run
#v1_seller = V1::Seller.new(seller_params)
#v1_seller.save
It should save the associations.
Should I create after_create hooks and pass the params (but then I will have to do the same in update)? Or am I missing something? I feel that it should be done automatically in rails.
currently I am doing it explicitly:
#v1_seller = V1::Seller.new(seller_params)
if #v1_seller.save
#v1_seller.assign_categories(params)
my seller model:
class V1::Seller < ActiveRecord::Base
has_many :categories, :class_name => 'V1::Category', dependent: :delete_all
has_many :category_names, :class_name => 'V1::CategoryName', through: :categories
# right now I am manually calling this after a create/update operation in my controller
def assign_categories(params)
params.require(:seller).require(:categories)
params.require(:seller).permit(:categories => []).permit(:name, :brands => [])
self.categories.clear
params[:seller][:categories].each do |c|
if c[:brands].nil? || c[:brands].empty?
next # skip the category if it has no brands associated with it
end
category_name = c[:name]
category = V1::Category.new
category.category_name = V1::CategoryName.find_by(name: category_name)
category.seller = self
category.save
c[:brands].each do |b|
begin
category.brand_names << V1::BrandName.find_by(name: b)
rescue ActiveRecord::RecordInvalid
# skip it. May happen if brand is already added to the particular category
end
end
end
end
end
And V1::Cateogry model:
class V1::Category < ActiveRecord::Base
belongs_to :category_name, :class_name => 'V1::CategoryName', inverse_of: :category
belongs_to :seller, :class_name => 'V1::Seller', inverse_of: :category
has_many :brands, :class_name => 'V1::Brand', dependent: :delete_all, inverse_of: :category
has_many :brand_names, :class_name => 'V1::BrandName', through: :brands, inverse_of: :category
validates :seller, :uniqueness => {:scope => [:category_name, :seller]}
end
Seem like you need nested attributes.
Checkout the docs here: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Rails 4: Nested attributes readonly error

I have quite a complicated setup of associations to allow my model :thing to be rated, which I think can be best understood by looking at my models. Basically, when a new :thing is created, new :thing_ratings are also created based on the :ratings that belong to the :categories that the :thing belongs to.
For example, if a :category “Books” has a :rating “Plot”, then a new :thing that is created with an association to Books should have a :thing_rating also named “Plot”.
The problem is, though the :thing and :thing_ratings are created without problems, on the :thing show page, I'm getting this error:
ActiveRecord::HasManyThroughNestedAssociationsAreReadonly in ThingsController#show
Cannot modify association 'Thing#thing_ratings' because it goes through more than one other association.
How can I get around this problem? I saw an answer to a similar problem that suggested to make the association between :thing and :thing_rating readonly, but I also want to be able to create instances of another model :up_votes for :thing_ratings, and I don't think I can do that if the association is readonly.
models/thing.rb
has_many :category_things
has_many :categories, :through => :category_things
has_many :category_ratings, through: :categories
has_many :ratings, :through => :categories
has_many :thing_ratings, through: :ratings
models/category.rb
has_many :category_ratings
has_many :ratings, :through => :category_ratings
has_many :category_things
has_many :things, :through => :category_things
models/rating.rb
has_many :category_ratings
has_many :categories, :through => :category_ratings
has_many :thing_ratings
has_many :things, :through => :thing_ratings
models/category_thing.rb
belongs_to :category
belongs_to :thing
models/category_rating.rb
belongs_to :category
belongs_to :rating
models/thing_rating.rb
belongs_to :rating
belongs_to :thing
has_many :up_votes, as: :voteable
controllers/things_controller.rb
def show
#thing = Thing.find(params[:id])
#thing.categories.build
#category_thing = CategoryThing.all
#category_rating = CategoryRating.all
#thing_ratings = #thing.category_ratings
#thingcats = #thing.categories
#thing.thing_ratings.build
# ...
end
def create
#thing = Thing.new(thing_params)
#category = Category.all
#thing.categories.build
#thing_ratings = #thing.category_ratings
#thingcats = #thing.categories
#thingrats = #thing.ratings
if #thing.save
params["categories"].strip.split(',').map(&:strip).each do |name|
CategoryThing.create!(category_id: Category.where(name: name).first.id, thing_id: #thing.id)
end
#thingrats.each do |r|
ThingRating.create!(rating_id: r.id, thing_id: #thing.id, name: r.name)
end
redirect_to new_thing_path
end
end
Well it turns out that I didn't actually have to associate thing_ratings with ratings at all. I just made thing_ratings belong directly to things.

Making sum on join tables

I am creating a Rails 3.2 web app.
In this app I got four tables. Project, Task, Article and Item.
What I want to do is to get all the task values (prices from article) summed up
in a single call.
This is what I tried and it works, but is it the best way of doing it?
#project.tasks.where("status = ?", "completed").joins(:articles).sum(:price)
Task table
class Task < ActiveRecord::Base
has_many :articles
has_many :items, :through => :articles
end
Article Join Table
class Article < ActiveRecord::Base
belongs_to :task
belongs_to :item
attr_accessible :account_id, :amount, :price, :item_id, :task_id
end
Item table
class Item < ActiveRecord::Base
has_many :articles
has_many :tasks, :through => :articles
end
to sum it up it looks ok the way you did it, but also you can prettify your code:
project.rb
has_many :completed_tasks, class: 'Task', :conditions => {:status => 'completed'}
controller
#project.completed_tasks.joins(:articles).sum(:price)

Rails: apply where conditions to both model and associations

in my app I have the models Item, Category and Categorization defined as above:
class Item < ActiveRecord::Base
attr_accessible :name, :description, :property_valuations, :barcode
has_many :categorizations
has_many :categories, :through => :categorizations
end
class Category < ActiveRecord::Base
attr_accessible :name, :description, :parent, :children, :items, :parent_id
has_many :children, :class_name => "Category", :foreign_key => "parent_id", :dependent => :nullify
belongs_to :parent, :class_name => "Category"
has_many :categorizations
has_many :items, :through => :categorizations
def all_children(children_array = [])
children = Category.where(:parent_id => self.id).includes(:items)
children_array += children.all
children.each do |child|
children_array = child.all_children(children_array)
end
children_array
end
end
class Categorization < ActiveRecord::Base
attr_accessible :category, :item
belongs_to :category
belongs_to :item
end
I'm search the category tree recursively for items associated to categories. However, besides filtering categories by parent_id, I want to apply a filter items name. How can I do this (I was expecting something like Category.where(:parent_id => self.id).includes(:items).where(:name => 'something...'))?
Thanks in advance!
You can do
Category.where(:parent_id => self.id).includes(:items).where("items.name like ?", '%cheap%')
That will get the categories, and the items that matched the criteria.
i.e.:
categories = Category.where(:parent_id => 2).includes(:items).where("items.name like ?", 'AA%')
categories.first.items.size # -> 2
categories.first.id # -> 1
Category.find(1).items.size # -> 3
Category.find(1).items.where("name like ?", 'AA%').count # -> 2
If you just want to get the items of the subcategories of a parent category:
parent_category = Category.find(2)
Item.joins(:categories).where(categories: {parent_id: parent_category.id}).where("items.name like ?", 'AA%')
Recursive querying (to avoid the manual recursion of your all_children function) might be supported by your database, eg postgres via the with command, but rails doesn't have a wrapper that accesses that. If that recursion happens a lot, it might be worth doing some reading here: http://gmarik.info/blog/2012/10/14/recursive-data-structures-with-rails -- and it does look there are some promising gems addressing the issue, but I haven't used any of them personally.

How do I create a scope that traverses a polymorphic association?

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

Resources