Rails: Fetch model across many associations - sql

I've tried wrapping my mind around how to navigate the associations I want, but I can't seem to figure it out. I'm trying to get all the Posts given a Tag. Each Post currently has a title and body text, both of which are represented as TaggedText. Each TaggedText can have many unique tags — like tagging multiple people/pages in a Facebook post (uniqueness is enforced in the model when saving an instance).
class Tag < ActiveRecord::Base
has_many :tagged_texts, through: :tag_ranges
end
class Post < ActiveRecord::Base
has_many :tagged_texts
end
class TaggedText < ActiveRecord::Base
# Each TaggedText cannot have more than one of each tag
has_many :tags, through: :tag_ranges
belongs_to :post
end
class TagRange < ActiveRecord::Base
# TaggedText cannot have more than one of each tag
belongs_to :tagged_text
belongs_to :tag
end
I tried joining the tables, but I get the error Association named 'tag_ranges' was not found on Post:
def get_posts_by_tag(tag, page, posts_per_page)
Post
.joins(:tagged_texts)
.joins(:tag_ranges)
.joins(:tags)
.where('tag.id = ?', tag.id)
.uniq
.limit(posts_per_page)
.offset(page - 1)
.to_a
end
What am I missing to get the query to work — or should I restructure my models and associations somehow?

As you error states, you need to add a tag_ranges association to your Post model. I've also added a few associations that you may or may not find useful, and one that will simplify your query greatly. Not that your TagRange class's associations are fine as is.
class Tag < ActiveRecord::Base
has_many :tag_ranges # need this association in order to get tagged_texts
has_many :tagged_texts, through: :tag_ranges
has_many :posts, -> { uniq }, through: :tagged_texts # posts with the given tag
end
class Post < ActiveRecord::Base
has_many :tagged_texts
has_many :tag_ranges, through: :tagged_texts # Post now has association named 'tagged_ranges'
has_many :tags, -> { uniq }, through: :tag_ranges # tags that given post has
end
class TaggedText < ActiveRecord::Base
has_many :tag_ranges # all tag ranges for a tag text
has_many :tags, through: :tag_range
belongs_to :post
end
And now, your query to get all the posts for a tag:
def get_posts_by_tag(tag, page, posts_per_page)
tag.posts.limit(posts_per_page).offset(page - 1).to_a
end
Hopefully this helps!

Related

ActiveRecord querying through multiple joins

So below are my models:
post.rb
class Post < ActiveRecord::Base
has_many :taggings
has_many :tags, through: :taggings
has_many :collectables
has_many :collections, through: :collectables
end
tagging.rb
class Tagging < ActiveRecord::Base
belongs_to :post
belongs_to :tag, counter_cache: :posts_count
end
tag.rb
class Tag < ActiveRecord::Base
has_many :taggings
has_many :posts, through: :taggings
end
collectable.rb
class Collectable < ActiveRecord::Base
belongs_to :post
belongs_to :collection, counter_cache: :posts_count
end
collection.rb
class Collection < ActiveRecord::Base
has_many :collectables
has_many :posts
end
A Collection has many Posts, and a Post has many Tags. Now I'm trying to create a search bar that'll search for Collections by Posts that have particular Tags (tag.name is the search term). For instance, Collection 1 has a post that has the tag #cat. Now if the user searches for "cat", Collection 1 will show up in the results. I'm not sure how the query should look like for making this happen.
def self.search(search)
Collection.joins(:posts) ... ?
end
try this:
def self.search(search)
Collection.includes(posts: :tags).where(tags: {name: search})
end
Try This
def self.search(search)
Collection.joins(posts: :tags).where({tags: {name: search}})
end
You can use includes, eager_load and joins, but when you use joins then only it will take those collections who have the posts in all other cases, it will take all collections who have the posts and who don't have the posts.

N + 1 Queries even with Eager loading in Rails 3.2

I have a has_many through, through relationship that needs to be eagerly loaded. Is this possible in Rails 3.2? I've tried several ways to include the association yet calling categories on the facebook_user object always makes another query to Category. Here's a simplified version of my activerecord associations.
class FacebookUser < ActiveRecord::Base
belongs_to :user, touch: true
has_many :user_categories, through: :user
has_many :categories, through: :user_categories
end
class User < ActiveRecord::Base
has_many :user_categories
has_many :categories, through: :user_categories
has_one :facebook_user
end
class UserCategory < ActiveRecord::Base
belongs_to :user
belongs_to :category
end
#single query
FacebookUser.includes( user: :categories).joins(user: :categories).each do |f|
## N+1 query on f.categories
f.categories.first
end
#multi-part query
facebook_user_ids = FacebookUser.where(user_id: [1,2,3]).joins(user: :categories).pluck('facebook_users.id')
FacebookUser.where( id: facebook_user_ids ).includes( user: :categories)
With
FacebookUser.includes( user: :categories)
you have eager loaded the user and his categories instead of the categories on facebook_user. So in order to avoid n+1 query you call then
f.user.categories
So why u just don't eager facebook_user's categories?
FacebookUser.includes(:categories)

ActiveRecord belongs_to through when using namespaces

My system is going to be large, so I've separated the models in namespaces. However, there is a model which I cannot setup the relation...
(My models are in portuguese, and the plurals are ok)
class Sistema::Instituicao < ActiveRecord::Base
has_many :agencias
has_many :dependencias, through: :agencias
#(...)
end
class Sistema::Agencia < ActiveRecord::Base
belongs_to :instituicao
has_many :dependencias
#(...)
end
class Sistema::Dependencia < ActiveRecord::Base
belongs_to :agencia
belongs_to :instituicao, through: :agencia
#(...)
end
But I get an error in Dependencia, as follows:
ArgumentError: Unknown key: through
What am I not seeing?
Thanks!
if you look at the api for belongs_to you will see that there's no :through option. the :through option is only available for has_one and has_many associations

Finding through a joins table

So I have a Product, product_tags joins table, and Tags. I would like to find all products with a specific tag name using Product.find. Is this possible?
You didn't post code, but I presume this is your setup:
class Product < ActiveRecord::Base
has_many :product_tags
has_many :tags, :through => :product_tags
end
class Tag < ActiveRecord::Base
has_many :product_tags
has_many :products, :through => :product_tags
end
class ProductTag < ActiveRecord::Base
belongs_to :product
belongs_to :tag
end
Then you can find all the products with a given tag name, e.g. "cool_product" as follows:
cool_tag = Tag.find_by_name("cool_product")
cool_tag.products # => list of all products
For reference and study, I highly recommend the read through the entire doc page on ActiveRecord::Associations::ClassMethods. The association methods are some of the most underappreciated features of Active Record.

How to find all that has multiple relationships in many to many with Rails3 in a elegant way?

I have a User class with
class User < ActiveRecord::Base
has_many :forum_subscriptions
has_many :forums, :through => :forum_subscriptions
And a Forum class with
class Forum < ActiveRecord::Base
has_many :users
I want to find all the users that are subscribed to forum "sport", forum "TV" and forum "Hobby"
What is the most elegant way to do it?
(I have a lot of ugly stuff in my mind :-)
Here is something I would use:
User.joins(:forums).where("forums.title IN (?)", %w(sport TV Hobby)).group("users.id")
I assumed the column for the forum's title is 'title'. Change it when you're using some other name. You can put this inside a nice scope inside the User Model to make it a bit more dynamic.
scope :subscribed_to, lambda { |forum_titles| joins(:forums).where("forums.title IN (?)", forum_titles).group("users.id") }
Now you can call this scope from inside the Controller or some other place:
User.subscribed_to(%w(sport TV Hobby)) # => Get all users that are subscribed to sport, TV and Hobby
User.subscribed_to(["sport", "TV", "Hobby"]) # => Does the same
User.subscribed_to(%w(Hobby)) # => Get all users that are subscribed to Hobby
User.subscribed_to("Hobby") # => Does the same
I assumed you have the following relationships:
class User < ActiveRecord::Base
has_many :forum_subscriptions
has_many :forums, :through => :forum_subscriptions
end
class Forum < ActiveRecord::Base
has_many :forum_subscriptions
has_many :users, :through => :forum_subscriptions
end
class ForumSubscription < ActiveRecord::Base
belongs_to :user
belongs_to :forum
end
Hope this is what you needed. :)