Rails: ActiveRecord Query for has_many :through model - ruby-on-rails-3

How to query for Companies with a certain Branch in a "has_many :through" relationship?
#company.rb
has_many :branch_choices
has_many :branches, :through => :branch_choices
"Find all companies with Branch ID 3"

Company.includes(:branches).where(:branches => {:id => 3})
or
Branch.find(3).companies
UPDATE
Actually, there's one downside to the first snippet: it eagerly loads branches along with the companies. To avoid this overhead you may consider using a left join:
Company.
joins("LEFT JOIN `branch_choices` ON `branch_choices`.`company_id` = `companies`.`id`").
where(:branch_choices => {:branch_id => 3})

Related

ActiveRecord has_many through polymorphic has_many

It seems like rails still not support this type of relation and throws ActiveRecord::HasManyThroughAssociationPolymorphicThroughError error.
What can I do to implement this kind of relation?
I have following associations:
Users 1..n Articles
Categories n..n Articles
Projects 1..n Articles
And here is Subscription model
Subscription 1..1 User
Subscription 1..1 Target (polymorphic (Article, Category or User))
And I need to select articles through Subscription#target#article according to user#subscriptions.
I have no idea hot to implement this
Ideally I want to get instance of Association class
UPDATE 1
Here is little example
Let say user_1 has 4 Subscription records:
s1 = (user_id: 1, target_id: 3, target_type: 'User')
s2 = (user_id: 1, target_id: 2, target_type: 'Category')
s3 = (user_id: 1, target_id: 3, target_type: 'Project')
s4 = (user_id: 1, target_id: 8, target_type: 'Project')
I need method User#feed_articles, that fetches all articles, that belong to any of target, I subscribed.
user_1.feed_articles.order(created_at: :desc).limit(10)
UPDATE 2
I separate articles sources by type in User model:
has_many :out_subscriptions, class_name: 'Subscription'
has_many :followes_users, through: :out_subscriptions, source: :target, source_type: 'User'
has_many :followes_categories, through: :out_subscriptions, source: :target, source_type: 'Category'
has_many :followes_projects, through: :out_subscriptions, source: :target, source_type: 'Project'
has_many :feed_user_articles, class_name: 'Article', through: :followes_users, source: :articles
has_many :feed_category_articles, class_name: 'Article', through: :followes_categories, source: :articles
has_many :feed_project_articles, class_name: 'Article', through: :followes_projects, source: :articles
But how can I merge feed_user_articles with feed_category_articles and feed_project_articles without loss of perfomance
UPDATE 3.1
The only way I found is to use raw SQL join query. Looks like it works fine, but I'm not sure.
def feed_articles
join_clause = <<JOIN
inner join users on articles.user_id = users.id
inner join articles_categories on articles_categories.article_id = articles.id
inner join categories on categories.id = articles_categories.category_id
inner join subscriptions on
(subscriptions.target_id = users.id and subscriptions.target_type = 'User') or
(subscriptions.target_id = categories.id and subscriptions.target_type = 'Category')
JOIN
Article.joins(join_clause).where('subscriptions.user_id' => id).distinct
end
(This is just for Users and Categories)
It supports scopes and other features. The only thing interests me: does this query lead to some undesirable effect?
I think that from DB performance prospective using UNION ALL multiquery will be more efficient than using polymorphic multijoin. Also it will be more readable. I tried to write an Arel query as example but it does not play nice (I failed to make order by clause work properly) so I think you have to put it via raw SQL. You can use SQL template apart from ORDER BY clause for drying it up.
You are correct that Rails doesn't support has_many :through w/ polymorphic associations. You could mimic this behavior by defining an instance method on your User class. That would look something like this:
def articles
Article.
joins("join subscriptions on subscriptions.target_id = articles.id and subscriptions.target_type = 'Article'").
joins("join users on users.id = subscriptions.user_id")
end

Rails 3 has_one with join table

I have the following:
has_many :sports, :through => :user_sports
has_one :primary_sport, class_name: "UserSport", conditions: ["user_sports.primary = ?", true]
has_many :user_sports
When I run this in console:
athlete = Athlete.all.last
athlete.primary_sport
The record that is returned is the record from the join table instead of the record joining the sports table. Any way to return the actual sport from the join?
You might probably do something like this:
class UserSport < ActiveRecord::Base
has_many :athletes
has_many :sports
end
athlete = Athlete.all.last
athlete.primary_sport.sport
Didn't try it by myself, just check and see :)

Order by polymorphic belongs_to attribute

How do I make an ActiveRecord query that orders by an attribute of a polymorphic belongs_to association?
For example I have model called Tagging that has a polymorphic belongs_to association named tagged_item.
Unfortunately Tagging.joins(:tagged_item) throws a ActiveRecord::EagerLoadPolymorphicError. So I can't do something like Tagging.joins(:tagged_item).order("tagged_item.created_at DESC").
Any suggestions?
You can't make a join directly with a polymorphic relationship because the polymorphic objects' data are in different tables. Still you could try to do it manually as in the following example.
class Tagging < ActiveRecord::Base
belongs_to :tagged_item, :polymorphic => true
end
class Post
has_many :image_tagging, :as => :tagged_item
end
class Comment
has_Many :image_tagging, :as => :tagged_item
Tagging.select("taggins.*, COALESCE(posts.created_at, comments.created_at) AS tagged_item_created_at").
joins("LEFT OUTER JOIN posts ON posts.id = tagging.tagged_item_id AND tagging.tagged_item_type = 'Post'").
joins("LEFT OUTER JOIN comments ON comments.id = tagging.tagged_item_id AND tagging.tagged_item_type = 'Comment'").
order("tagged_item_created_at DESC")
COALESCE chooses the first column provided if exists otherwise the other one. It's the same as IFNULL in mysql or you could even use CASE WHEN ... IS NULL THEN ... ELSE ... END

rails: include statement with two ON conditions

I have tree tables
books
bookmarks
users
where there is a n to m relation from books to users trough bookmarks.
Im looking for a query, where I get all the books of a certain user including the bookmarks. If no bookmarks are there, there should be a null included...
my sql statement looks like:
SELECT * FROM `books`
LEFT OUTER JOIN `bookmarks `
ON bookmarks.book_id = books.id
AND bookmarks.user_id = ?
In rails I only know the :include statement, but how can I add the second bookmarks.user_id = ? statement in the ON section of this query? if I put it in the :conditions part, no null results would get returned!
Thanks!
Markus
Add the following associations to your models:
class Book < ActiveRecord::Base
has_many :bookmarks
has_many :users, :through => :bookmarks
end
# assuming bookmarks table has book_id and user_id columns.
class Bookmark < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
class User < ActiveRecord::Base
has_many :bookmarks
has_many :books, :through => :bookmarks
end
Now you can make following calls:
u.books # books for a user
u.bookmarks # bookmarks for a user
# find the user and eager load books and bookmarks
User.find(params[:id], :include => [:books, :bookmarks])
b.users # users for a book
b.bookmarks # bookmarks for a book
# find the book and eager load users and bookmarks
Book.find(params[:id], :include => [:users, :bookmarks])

Sorting ActiveRecord models by sub-model attributes?

Lets assume I have a model Category which has_many Items. Now, I'd like to present a table of Categories sorted on various attributes of Items. For example, have the category with the highest priced item at the top. Or sort the categories based on their best rated item. Or sort the categories based on the most recent item (i.e., the category with the most recent item would be first).
class Category < ActiveRecord::Base
has_many :items
# attributes: name
end
class Item < ActiveRecord::Base
belongs_to :category
# attributes: price, rating, date,
end
Which is the best approach?
Maintain additional columns on the Category model to hold the attributes for sorting (i.e., the highest item price or the best rating of an item in that category). I've done this before but it's kinda ugly and requires updating the category model each time an Item changes
Some magic SQL incantation in the order clause?
Something else?
The best I can come up with is this SQL, for producing a list of Category sorted by the max price of the contained Items.
select categories.name, max(items.price) from categories join items group by categories.name
Not sure how this translates into Rails code though. This SQL also doesn't work if I wanted the Categories sorted by the price of the most recent item. I'm really trying to keep this in the database for obvious performance reasons.
Assuming the attributes listed in the items model are database columns there are many things you could do.
The easiest is probably named_scopes
/app/models/category.rb
class Category < ActiveRecord::Base
has_many :items
# attributes: name
named_scope :sorted_by_price, :joins => :items, :group => 'users.id', :order => "items.price DESC"
named_scope :sorted_by_rating, :joins => :items, :group => 'users.id', :order => "items.rating DESC"
named_scope :active, :condition => {:active => true}
end
Then you could just use Category.sorted_by_price to return a list of categories sorted by price, highest to lowest. The advantages of named_scopes lets you chain multiple similar queries. Using the code above, if your Category had a boolean value named active. You could use
Category.active.sorted_by_price to get a list of active categories ordered by their most expensive item.
Isn't that exactly what :joins is for?
Category.find(:all, :joins => :items, :order => 'price')
Category.find(:all, :joins => :items, :order => 'rating')