Rails 3 - Eager loading on a legacy database - ruby-on-rails

I have inherited a web app with the following tables: Categories, SubCategories and Pages. Pages has category_id and sub_category_id columns.
I need to write an efficient query to eager load pages by category and sub_category for iterate in my view (crude example follows):
- Category One (categories.each do |category|...)
-- Page One (category.pages.each do |page|...)
-- SubCategory One (category.sub_categories.each do |sub_category|...
---- Page Two (sub_category.pages.each do |page|...)
Category.rb:
class Category < ActiveRecord::Base
has_many :pages
has_many :sub_categories
end
SubCategory.rb:
class SubCategory < ActiveRecord::Base
belongs_to :category
has_many :pages
end
Page.rb:
class Page < ActiveRecord::Base
belongs_to :category
belongs_to :sub_category
scope :active_pages, :conditions => {:is_active => true}
end
I've experimented with queries like the following with little success where the sub_categories are concerned:
Category.includes(:sub_categories, :pages).where('pages.is_active = 1')
Categories works great, but I'm not sure how to eager load the sub_categories. Thanks in advance, any help is greatly appreciate.

according to this article which I was just looking at something like this might do what you want:
Category.find( :all, :include => [ :pages, { :sub_categories => :pages } ] )

Related

Thinking Sphinx Rails Multiple Association

I have the following models
class Product < ActiveRecord::Base
belongs_to :sub_category
end
class SubCategory < ActiveRecord::Base
belongs_to :category
has_many :products
end
class Category < ActiveRecord::Base
has_many :sub_categories , -> { where("activate = 1") }
end
I need to index my products table.I need to search using category name(which is in category table) and subcategory name(in subcategories table)
ThinkingSphinx::Index.define :product , :with => :active_record do
indexes description
indexes name
indexes merchant_name
indexes sub_category(:sub_category) , :as => :sub_category_name
indexes category(:name) , :as => :cat_name
has sub_category_id
end
The category(:name) is failing.The subcategory is working fine.
Could somebody please help.I tried sub_category.category(:name) but thats also failing
Error Message
ERROR: index 'link_core': sql_range_query: You have an error in your
SQL syntax; check the manual that corresponds to your MySQL server
version for the right syntax to use near 'AS cat_name, products.id AS
sphinx_internal_id, 'Product' AS `sphinx_internal_' at line 1
(DSN=mysql://root:***#localhost:3306/xxxx_dev_phase4)
name should be passed as a chained method, not as an argument
indexes sub_category.category.name , :as => "category_name"
Thanks to the owner Pat for helping me out
concerned github thread

Nested eager loading with attribute accessors

I have my models setup like this:
class Country < ActiveRecord::Base
has_many :manufacturers
end
class Manufacturer < ActiveRecord::Base
belongs_to :country
has_many :cars
end
class Cars < ActiveRecord::Base
belongs_to :manufactuer
has_many :comfort_levels
attr_accessor :attr_accessor_1, :attr_accessor_2
end
class ComfortLevel < ActiveRecord::Base
belongs_to :car
end
This is how I am eager loading manufacturers with cars (including car's attr accessors) for a country:
data = current_country.manufacturers.to_json :include => {:cars => {:methods => [:attr_accessor_1, :attr_accessor_1]}}
What will be the syntax to also eager load the comfort levels for cars in the above call?
I have tried various things, but no luck so far.
Would greatly appreciate any help in this regard. Thanks!
Have you tried this?
data = current_country.manufacturers.to_json :include => {:cars => {:methods => [:attr_accessor_1, :attr_accessor_1], :comfort_levels => {}}}
I was finally able to eager load everything, using:
data = current_country.manufacturers.to_json :include => [{:cars => {:methods => [:attr_accessor_1, :attr_accessor_1]}}, :comfort_levels]
The above call did not nest comfort level records inside car records, however, the following did:
data = current_country.manufacturers.to_json :include => {:cars => {:methods => [:attr_accessor_1, :attr_accessor_1, :comfort_levels]}}
This link helped me clear some concepts around the syntax for eager loading.

Rails Associations and Scopes

I'm learning scopes in Rails and having some trouble making some scope definitions. Say I have the following models..
class Category < ActiveRecord::Base
has_many :posts
has_many :comments, :through => :post
end
class Post < ActiveRecord::Base
belongs_to :category
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
has_many :categories, :through => :post
# attribute: owner
end
Notice the attribute owner for Comment. I'm trying to write a scope which will return me the categories that have comments made by only the owner I pass. So if a category has comments and these comments are made by several other owners, these should not be included. I have this working somewhat. I'm getting categories that have comments with the owner I pass..but I also get categories that have comments by other owners too.
In my Category model, I have this..
scope :comments_by_owner, lambda { |name| joins(:comments).where("comments.owner = ?", name) }
I make a call using
Category.comments_by_owner("tom")
I tried playing around with joins and uniq but no luck..
Try this:
scope :comments_by_owner, lambda { |name| joins(:posts => :comments).where(:comments => {:owner => name})
It appears that select with a block uses Array#select. Try running through all categories, selecting categories which contain no comments for other names.
def find_single_name_categories(name)
Category.scoped.select do |category|
category.comments.scoped.select { |comment| comment.name != name }.empty?
end
end

Rails 3 Three Models Join

I don't seem to get this right for some reason, though it sounds simple :/
I have three models :
User(id,name,email)
Skill(id,name,description)
UserSkill(user_id,skill_id,level)
How can i get all skills of a certain user, whether he or she has discovered them or not ?
For example, 3 skills (walk, talk, write). 3 users (John, Mary, Jack).
If Mary walks and writes, how can i get it back as a result like :
Mary => {Skill: walk(includes UserSkill), Skill : talk, Skill : write(includes UserSkill) }
You get the idea :)
Try this:
class User
def skill_list
Skill.all(
:select =>"skills.*, A.user_id AS user_id",
:joins => "LEFT OUTER JOIN user_skills A
ON A.skill_id = skills.id
AND A.user_id = #{id}").map do |skill|
skill.name + (skill.user_id.nil? ? "" : "(*)")
end
end
end
Now
user = User.find_by_name("Mary")
user.skill_list
Will print:
[
walk(*),
talk,
write(*)
]
I'm assuming you want to set something up like this:
class User < ActiveRecord::Base
has_many :user_skills
has_many :skills, :through => :user_skills
end
class Skill < ActiveRecord::Base
has_many :user_skills
has_many :users, :through => :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
then you can do:
my_user.skills # returns all Skill records assigned to the user
my_user.user_skills.includes(:skill) # this allows you to access :level in addition to Skill attributes
So the way to get both skills and user_skills is to use the :user_skills association. Basic has_many :through. Am I missing something?
user = User.first
user.user_skills.all.map(&:skills)

how to access rails join model attributes when using has_many :through

I have a data model something like this:
# columns include collection_item_id, collection_id, item_id, position, etc
class CollectionItem < ActiveRecord::Base
self.primary_key = 'collection_item_id'
belongs_to :collection
belongs_to :item
end
class Item < ActiveRecord::Base
has_many :collection_items
has_many :collections, :through => :collection_items, :source => :collection
end
class Collection < ActiveRecord::Base
has_many :collection_items, :order => :position
has_many :items, :through => :collection_items, :source => :item, :order => :position
end
An Item can appear in multiple collections and also more than once in the same collection at different positions.
I'm trying to create a helper method that creates a menu containing every item in every collection. I want to use the collection_item_id to keep track of the currently selected item between requests, but I can't access any attributes of the join model via the Item class.
def helper_method( collection_id )
colls = Collection.find :all
colls.each do |coll|
coll.items.each do |item|
# !!! FAILS HERE ( undefined method `collection_item_id' )
do_something_with( item.collection_item_id )
end
end
end
I tried this as well but it also fails with ( undefined method `collection_item' )
do_something_with( item.collection_item.collection_item_id )
Edit: thanks to serioys sam for pointing out that the above is obviously wrong
I have also tried to access other attributes in the join model, like this:
do_something_with( item.position )
and:
do_something_with( item.collection_item.position )
Edit: thanks to serioys sam for pointing out that the above is obviously wrong
but they also fail.
Can anyone advise me how to proceed with this?
Edit: -------------------->
I found from online documentation that using has_and_belongs_to_many will attach the join table attributes to the retreived items, but apparently it is deprecated. I haven't tried it yet.
Currently I am working on amending my Collection model like this:
class Collection < ActiveRecord::Base
has_many :collection_items, :order => :position, :include => :item
...
end
and changing the helper to use coll.collection_items instead of coll.items
Edit: -------------------->
I've changed my helper to work as above and it works fine - (thankyou sam)
It's made a mess of my code - because of other factors not detailed here - but nothing that an hour or two of re-factoring wont sort out.
In your example you have defined in Item model relationship as has_many for collection_items and collections the generated association method is collection_items and collections respectively both of them returns an array so the way you are trying to access here is wrong. this is primarily case of mant to many relationship. just check this Asscociation Documentation for further reference.
do_something_with( item.collection_item_id )
This fails because item does not have a collection_item_id member.
do_something_with( item.collection_item.collection_item_id )
This fails because item does not have a collection_item member.
Remember that the relation between item and collection_items is a has_many. So item has collection_items, not just a single item. Also, each collection has a list of collection items. What you want to do is probably this:
colls = Collection.find :all
colls.each do |coll|
coll.collection_items.each do |collection_item|
do_something_with( collection_item.id )
end
end
A couple of other pieces of advice:
Have you read the documentation for has_many :through in the Rails Guides? It is pretty good.
You shouldn't need the :source parameters in the has_many declarations, since you have named your models and associations in a sensible way.
I found from online documentation that using has_and_belongs_to_many will attach the join table attributes to the retreived items, but apparently it is deprecated. I haven't tried it yet.
I recommend you stick with has_many :through, because has_and_belongs_to_many is more confusing and doesn't offer any real benefits.
I was able to get this working for one of my models:
class Group < ActiveRecord::Base
has_many :users, :through => :memberships, :source => :user do
def with_join
proxy_target.map do |user|
proxy_owner = proxy_owner()
user.metaclass.send(:define_method, :membership) do
memberships.detect {|_| _.group == proxy_owner}
end
user
end
end
end
end
In your case, something like this should work (haven't tested):
class Collection < ActiveRecord::Base
has_many :collection_items, :order => :position
has_many :items, :through => :collection_items, :source => :item, :order => :position do
def with_join
proxy_target.map do |items|
proxy_owner = proxy_owner()
item.metaclass.send(:define_method, :join) do
collection_items.detect {|_| _.collection == proxy_owner}
end
item
end
end
end
end
Now you should be able to access the CollectionItem from an Item as long as you access your items like this (items.with_join):
def helper_method( collection_id )
colls = Collection.find :all
colls.each do |coll|
coll.items.with_join.each do |item|
do_something_with( item.join.collection_item_id )
end
end
end
Here is a more general solution that you can use to add this behavior to any has_many :through association:
http://github.com/TylerRick/has_many_through_with_join_model
class Collection < ActiveRecord::Base
has_many :collection_items, :order => :position
has_many :items, :through => :collection_items, :source => :item, :order => :position, :extend => WithJoinModel
end

Resources