How to join across polymorphic tables in one query? - sql

I have 2 polymorphic associations through which I need to query.
I have a news_article table which has a polymorphic association to teams, players, etc. Those teams, players, etc have a polymorphic association to photos through phototenic.
I need to find all articles that have at least one picture that is 500px wide.
The Article model I have a has_many :teams (through the polymorphic table)
and in the teams I have a has_many :photos (though another polymorphic table)
I thought that I could use joins like this
Article.find(:last, :joins => {:teams => :photos}, :conditions => "photos.aspect_ratio < 1.55 AND photos.aspect_ratio > 1.30")
but it is not working. Any ideas?

Hope this is your setup...
class Article < ActiveRecord::Base
has_many :teams
end
class Team < ActiveRecord::Base
has_many :photos
end
class Photo < ActiveRecord::Base
belongs_to :teams
end
Can you please use the following query and let us know if it works for you?
Article.find(:last, :include => {:teams => :photos}, :conditions => "photos.aspect_ratio < 1.55 AND photos.aspect_ratio > 1.30")
Hope It helps...
rgds,
Sourcebits Team

If you are using Rails 3 already:
Article.joins(:teams).where(condition).joins(:photos).where(condition)
If you are using Rails 2.3.8:
Article.find(:all, :include => {:teams => :photos}, :conditions => [YOUR CONDITIONS])
Hope It helps...

Related

Searching a has_many :through association including the middle model

First, the topic.
I have three models, which are linked between each other with a has_many :trough association like this:
#User model
has_many :chars_del, :class_name => CharDelegated, :dependent => :destroy
has_many :chars, :through => :chars_del
#CharDelegated model
#has a field owner:integer
belongs_to :char
belongs_to :user
#Char model
#has fields name:string
has_many :chars_del, :class_name => CharDelegated
has_many :users, :through => :chars_del
What I need to do is I need to search from a User Record to find all the Chars that the particular user ownes (:owner field is true) ordered by name. I have been stuck with this for a couple hours now, so I believe that I could have missed a very simple answer... But nothing that I have tried so far did work even a bit.
UPDATE
found something that works:
user.chars.where(:char_delegateds => {:owner => 1}).order('name')
don't know why the :chars_del gave an error, but the full table name did the job.
Andrew, your answer works well too and is a little faster on the database, thans alot.
Does
user.chars.order('name')
not work? (Given user is a single User instance.)
Edit
Given your new information:
CharDelegated.where(user_id: user.id, owner: true).map(&:char)
should work.
In your specific example you don't need to search through the middle table but if you want to see an example of how to use the joining table and search through it for a more complex scenario you can do it this way.
#char = Char.all(:include => :users, :conditions => ["char_delegated.user_id in (?)", user_id]).order('name')

Table and Ruby ActiveRecord Class design for (sub)categories

I guess i have a rather simple question since I'm new to Ruby and even newer to ActiveRecords.
What I want to achieve is a class representation with ActiveRecords (and the corresponding SQL schema) that models the following problem:
There exist categories and subcategories (modeled by parent_id)
Products belong to only one category
Each product can have 0..inf features
Features simply have some data fields and are only referenced by the products
My current schema is shown below in the picture:
Is this schema suitable for ActiveRecords? How would the classes look like? I simply cant figure out how the JoinTable fits into the ActiveRecord structure.
Further, how can i model the link from parent_id->categories.id?
Any help appreciated!
cheers
To model the relationships you described you would do:
models/category.rb
class Category < ActiveRecord::Base
has_many :products
has_many :subcategories, :class_name => "Category", :foreign_key => :parent_id
end
models/product.rb
class Product < ActiveRecord::Base
belongs_to :product
has_many :features, :through => :product_features
has_many :product_features
end
models/feature.rb
class Feature < ActiveRecord::Base
has_many :product_features
has_many :products, :through => :product_features
end
models/productfeature.rb
class ProductFeature < ActiveRecord::Base
belongs_to :product
belongs_to :feature
end
Given this structure then you have the join modelled as a Many-to-Many relation. This is useful since the HABTM style of join is going away in Rails 3.1
To get the information, I often use the console rails console for testing and this would allow you do do
#category = Category.first #get the first category
#category.subcategories #returns an array of categories
The traversal of the links is via the relations that you setup in the models, with the intention that its readable, in the context of using sensible names. The self-joins, as per your question, is also covered in Rails Guides: Associations with a good example. The rest of this guide also details the other relationships.
One other thing to remember is to create your migrations so that the join table is created with the id's which are the foreign keys.
My models would look like this:
class Category < ActiveRecord::Base
has_many :products
end
class Product < ActiveRecord::Base
belongs_to :category
has_many :product_features
has_many :features, :through => :product_features
end
class ProductFeature < ActiveRecord::Base
belongs_to :product
belongs_to :feature
end
class Feature < ActiveRecord::Base
has_many :product_features
has_many :products, :through => :product_features
end
Rails has an association called has_and_belongs_to_many. Rails expects a table with two columns to store the join data. I usually use dual has_many to achieve the same results as it gives you flexibility to add additional information in the join table.
Sample code
product.category
product.category = category1
category.products
category.products << product1
product.features
product.features << feature1
feature.products
feature.products << product1
Here is the API for ActiveRecord::Associations::ClassMethods
There are a lot of examples in there of different relationships and how to construct them. It's worth taking the time to understand how/why you construct these associations.
For the Many-to-many join you will want to look at
has_many ..., :through => ...
has_and_belongs_to_many ...
The docs explain when and why to use each.

Rails efficient SQL for "Popular" users

I'm building a blog-type website in Rails (3) where "Authors" can follow other authors and in order and in order to find authors with the most followers I wrote this scope:
scope :popular, all.sort { |a, b| a.followers.count <=> b.followers.count }
I know this is really inefficient as it has to perform multiple queries for each author in the database and then compares the result. I'm asking this question to see if there isn't a better way of doing this. The relevant code is as follows:
class Author < ActiveRecord::Base
has_many :relationships, :dependent => :destroy, :foreign_key => "follower_id"
has_many :following, :through => :relationships, :source => :followed
has_many :reverse_relationships, :dependent => :destroy, :foreign_key => "followed_id", :class_name => "Relationship"
has_many :followers, :through => :reverse_relationships, :source => :follower
scope :popular, all.sort { |a, b| a.followers.count <=> b.followers.count }
end
The "followers" are implemented through a separate model:
class Relationship < ActiveRecord::Base
belongs_to :follower, :class_name => "Author"
belongs_to :followed, :class_name => "Author"
end
And for retrieving popular authors:
#popular_authors = Author.popular[0..9]
I'd love to be able to write something like Author.popular.limit(10) instead, but I understand that in order for this to work, Author.popular has to return an object of the ActiveRecord::Relation class, rather than an array.
As I mentioned, this code does work, but it's horribly inefficient, issuing hundreds of queries just to find the top 10 authors. Any help would be greatly appreciated!
One optimization might be to use eager loading. I suspect you have many queries that say, SELECT * fromAuthorWHEREfollower_id= 7
Eager loading can turn your hundreds of queries into a giant one for you. This might generate a slow query, but often times will faster because the time for the slow query was less than the 5000 fast queries.
I am not a SQL guru, but you might also want to use a GROUP_BY with LIMIT = 10. to get the 10 most popular.
Try something like
Authors.find(:all, :include => Authors, :group_by => " SELECT `count` as (some subquery I don't know to get the number of followers)", :limit => 10)
Scroll down to Eager loading of associations

Find all tags that are in use

I am little bit stuck with the following problem. I have two models:
class Book < ActiveRecord:Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord:Base
has_and_belongs_to_many :books
end
I have a list of specific tags that can but must not be used in the tags table:
tag1, tag2, tag3, tag4, tag5, ...
Each new book can have several tags. As usual, relationships are stored in a join table "books_tags".
How can I get a list of all tags that are at least related to one book?
You can use :joins as an option in your find call. E.g.
Tag.find(:all, :select => 'distinct tags.*', :joins => :books)
This will only find tags that have a book associated and the :select => 'distinct tags.*' ensures you only retrieve each tag once even if they are associated with multiple books.
This would probably be easier using the has_many ..., :through method of joining than the old-fashioned has_and_belongs_to_many which is generally not as versatile.
A simple way of restructuring this is:
class Book < ActiveRecord:Base
has_many :book_tags
has_many :tags, :through => :book_tags
end
class Tag < ActiveRecord:Base
has_many :book_tags,
has_many :books, :through => :book_tags
end
class BookTag < ActiveRecord::Base
belongs_to :book
belongs_to :tag
end
If you're looking for a list of tags that have at least one book, you can retrieve this using the BookTag model. In this case you're looking for the distinct set of tags from the join list:
SELECT DISTINCT tag_id FROM book_tags
You can wrangle that into a find call easily enough.
You may find an acts_as_taggable type plugin that handles this for you, though.

Complex associations in ActiveRecord models

I'm trying to understand how ActiveRecord deals with associations that are more complex than simple has_many, belongs_to, and so on.
As an example, consider an application for recording music gigs. Each Gig has a Band, which has a Genre. Each Gig also has a Venue, which has a Region.
In the rough notation of MS Access (which I'm suddenly beginning to feel quite nostalgic for) these relationships would be presented like this
1 ∞ 1 ∞ ∞ 1 ∞ 1
Genre ---- Band ---- Gig ---- Venue ---- Region
I would like to be able to find out, for example, all the bands who've played in a region, or all the venues that host a certain genre.
Ideally, my models would contain this code
class Genre
has_many :bands
has_many :gigs, :through => bands
has_many :venues, :through => :gigs, :uniq => true
has_many :regions, :through => :venues, :uniq => true
end
class Band
belongs_to :genre
has_many :gigs
has_many :venues, :through => :gigs, :uniq => true
has_many :regions, :through => :venues, :uniq => true
end
class Gig
belongs_to :genre, :through => :band
belongs_to :band
belongs_to :venue
belongs_to :region, :through => :venue
end
and so on for Venue and Region.
However, it seems I have to produce something like this instead
class Genre
has_many :bands
has_many :gigs, :through => bands
has_many :venues, :finder_sql => "SELECT DISTINCT venues.* FROM venues " +
"INNER JOIN gigs ON venue.id = gig.venue_id " +
"INNER JOIN bands ON band.id = gig.band_id " +
"WHERE band.genre_id = #{id}"
# something even yuckier for regions
end
class Band
belongs_to :genre
has_many :gigs
has_many :venues, :through => :gigs, :uniq => true
# some more sql for regions
end
class Gig
delegate :genre, :to => :band
belongs_to :band
belongs_to :venue
delegate :region, :to => :venue
end
I have two questions - one general and one particular.
The general:
I would have thought that what I was trying to do would come up fairly often. Is what I have really the best way to do it, or is there something much simpler that I'm overlooking?
The particular:
What I have above doesn't actually quite work! The #{id} in the second genre model actually to return the id of the class. (I think). However, this seems to work here and here
I realise this is a rather epic question, so thank you if you've got this far. Any help would be greatly appreciated!
Associations are designed to be readable and writable. A large part of their value is that you can do something like this:
#band.gigs << Gig.new(:venue => #venue)
It sounds, though, like you want something that's read-only. In other words, you want to associate Venues and Genres, but you'd never do:
#venue.genres << Genre.new("post-punk")
because it wouldn't make sense. A Venue only has a Genre if a Band with that particular Genre has a Gig there.
Associations don't work for that because they have to be writable. Here's how I'd do readonly associations:
class Genre
has_many :bands
def gigs
Gig.find(:all, :include => 'bands',
:conditions => ["band.genre_id = ?", self.id])
end
def venues
Venue.find(:all, :include => {:gigs => :band},
:conditions => ["band.genre_id = ?", self.id])
end
end
You can add conditions and parameters to your associations.
Recent versions of ActiveRecord give the power of named_scopes, which will work on associated records as well.
From a current project
Folder has_many Pages
Page has_many Comments
# In Page
named_scope :commented,
:include => "comments",
:conditions => ["comments.id IS NULL OR comments.id IS NOT NULL"],
:order => "comments.created_at DESC, pages.created_at DESC"
Using this we can say:
folder.pages.commented
Which will scope on the associated records, doing a conditional with the supplied parameters.
Plus! named_scopes are composable.
And more scopes:
named_scope :published, :conditions => ["forum_topics.status = ?", "published"]
And chain them together:
folder.pages.published.commented
For associations like this, you're going to end up writing custom SQL -- there's no real way that you can handle a chain of associations like this without having to do some fairly massive joins, and there really isn't an efficient way for the built-in query generators to handle it with a one-liner.
You can look into the :joins parameter of ActiveRecord as well -- this may do what you want.
Sounds like a job for nested_has_many_through! Great plugin that allows you to do nested has_many :throughs