I have four model classes:
class Group < ActiveRecord::Base
has_many :projects
has_many :personal_blogs
end
class Project < ActiveRecord::Base
has_many :events, :as => :event_producer
end
class PersonalBlog < ActiveRecord::Base
has_many :events, :as => :event_producer
end
class Event < ActiveRecord::Base
belongs_to :event_producer, :polymorphic => true
end
I want to find all of the events for a particular group. I figure this is a has_many :through association, but how do I specify a has_many on Group that finds all events in the projects or personal_blogs of a group? I could, of course, specify two associations and concatenate the results, but then I have to re-sort, limit, condition, etc. in Ruby, which could potentially be a performance nightmare with many events. I'd like to do this within ActiveRecord to avoid such a nightmare.
You could define a method in the Group class like next:
class Group < ActiveRecord::Base
has_many :projects
has_many :personal_blogs
def events
Event.find(:all, :conditions => ['(type = ? AND event_producer_id IN (?)) OR (type = ? AND event_producer IN (?))', 'project', project_ids, 'personal_blog', personal_blog_ids])
end
end
If you don't like SQL like the previous one, it's always possible to use Single Table Inheritance. This solution depends on your classes attributes and behavior, but will allow you to use a "has_many through" association.
Why not just do:
class Group < ActiveRecord::Base
has_many :projects
has_many :personal_blogs
def all_events
projects.events + personal_blogs.events
end
end
If you need an association that you can preload you could specify a custom 'from' query that includes the group id with the event.
class Group < ActiveRecord::Base
has_many :projects
has_many :personal_blogs
has_many :events, -> {from(<<~SQL)}
(
SELECT DISTINCT e.*, g.id as group_id
FROM events e
LEFT JOIN projects p ON p.event_id = e.id
LEFT JOIN personal_blogs pb ON pb.event_id = e.id
INNER JOIN groups g ON g.id IN (p.group_id, pb.group_id)
) as events
SQL
end
Related
I am looking for a way to query a model based on children in a has_many through association.
I have 3 models:
class Conversation < ActiveRecord::Base
has_many :conversations_participants
has_many :participants, through: :conversations_participants
end
class ConversationsParticipant < ActiveRecord::Base
belongs_to :conversation
belongs_to :participant, class_name: 'User'
end
class User < ActiveRecord::Base
has_many :conversations_participants
has_many :conversations, through: :conversations_participants
end
I need to find conversations where participants matches an array of ids.
This is what i have at the moment (not working):
Conversation.includes(:participants).where(participants: params[:participants])
Sounds like you just want the conversations, if so you can joins.
Conversation.joins(:participants).where(:users => { :id => params[:participants] } )
Otherwise, if you want to eager load the participants, use includes
Conversation.includes(:participants).where(:users => { :id => params[:participants] } )
You can pass an array like this:
Conversation.includes(:participants).where(:id => params[:participants])
Assuming that params[:participants] is an array.
Conversation.includes(:participants).where( 'conversation_participants.participant_id' => params[:participants])
assuming that participant_id is the foreign key of participants in the conversation_participants table
So I have two associated models Users and Magazines. They are associated by a has_many :through relationship via Subscriptions. So this is what it looks like:
class User < ActiveRecord::Base
has_many :subscriptions
has_many :magazines, :through => :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :magazine
end
class Magazine < ActiveRecord::Base
has_many :subscriptions
has_many :users, :through => :subscriptions
end
Users have a boolean attribute called paid. I only want users where paid == true to be able to subscribe to any magazines. How can I set up a conditional association like this? Thanks in advance for the help!
For the associations, you can pass a lambda before the hash options. In this lambda you can specify conditions, ordering, etc.
class Magazine < ActiveRecord::Base
has_many :subscriptions
has_many :users, ->{ where(users: {paid: true}) }, through: :subscriptions
end
For users, you could do something like this:
class User < ActiveRecord::Base
has_many :subscriptions
has_many :magazines, through: :subscriptions
def magazines
self.paid ? super : Magazine.none
end
end
While it's neat, the above might behave unpredictably when you're joining tables e.g. User.joins(:magazines) might ignore the paid condition or crash.
A better alternative:
class User < ActiveRecord::Base
has_many :subscriptions
has_many :magazines,
->{ where("subscriptions.user_id IN (
SELECT id FROM users WHERE users.paid = 't')") },
through: :subscriptions
end
That where in the lambda might be replaceable by a joins like so:
joins(subscriptions: :user).where(users: {paid: true})
But I think that'll join subscriptions twice - once for the lambda and once for the association. I'm not certain. If so, and if that bothers you, then:
joins("INNER JOIN users ON (users.id = subscriptions.user_id)").
where(users: {paid: true})
or:
joins("INNER JOIN users ON (
(users.id = subscriptions.user_id) AND (users.paid = 't')
)")
Also, I think you mislabeled your Subscription model as Registration
I have this relationship between categories, products & brands:
class Brand < ActiveRecord::Base
has_many :products
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :categories
belongs_to :brand
end
How can I select all categories by specified brand with this relations?
I try this but get an error
b = Brand.find(1)
Category.joins(:products).where(:products => b.products)
You did the right thing with the join, just add a more complex where definition:
Category.joins(:products).where(:products => {:brand_id => 1})
Controversially HABTM's are rarely, if ever, a good design and IMO just about the only thing Rails got wrong.
Introduce an xref table to join products and categories and use has_many :through on both sides of the relationship so you end up with
class Brand < ActiveRecord::Base
has_many :products
has_many categories :through => products # This is now allowed in Rails 3.x and above
end
class Category < ActiveRecord::Base
belongs_to :product_category
has_many :products :through => product_category
end
class Product < ActiveRecord::Base
belongs_to :brand
belongs_to :product_category
has_many :categories :through => product_category
end
class ProductCategory < ActiveRecord::Base
has_many :products
has_many :categories
end
This gives you the best flexibility with the least amount of code re-factoring for you plus a much more intuitive path to get whatever data you need on either side of the relationship and will enable you to achieve the following
b = Brand.find(1)
b.categories.all
Update
The above is totally untested code and I have just corrected a glaringly stupid mistake I made. If you have any issues implementing this then come back
I have two tables with a join table between them:
Parent: Pages
Child: Things
Join: Grids
In my models they are set up with a many to many relationship (has_many through):
class Page < ActiveRecord::Base
belongs_to :books
has_many :grids
has_many :things, :through => :grids
end
class Thing < ActiveRecord::Base
has_many :grids
has_many :pages, :through => :grids
end
class Grid < ActiveRecord::Base
belongs_to :page
belongs_to :thing
end
Now I want to be able to sort "things" using an ordering id from grid, called number, how can I do that ?
Thanks!
you need the ":include(s)" option in your find method, and then "order_by()" ...
you would use something like this:
Thing.where(...some condition or all..., :include => :grid ).order_by(grid.number)
See:
http://guides.rubyonrails.org/active_record_querying.html
http://m.onkey.org/active-record-query-interface
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
I've been stuck on this all day. I have a setup like the one below. I'm trying to define friends using the group_memberships association.
class User < ActiveRecord::Base
has_many :group_memberships
has_many :groups, :through => :group_memberships
has_many :friends # what goes here? <<
end
class GroupMembership < ActiveRecord::Base
belongs_to :user
belongs_to :role
belongs_to :group
end
class Role < ActiveRecord::Base
has_many :group_memberships
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, :through > :group_memberships
end
I'd like to do this without creating a join table for friends, unless it's completely crazy to do it without.
The group_membership table contains user_id and group_id linking one user to one group.
I'd trying to get
#user.friends
to return users with common group_memberships using the group_id.
has_many :friends, :through => :group_memberships, :source => :group
Nothing I've tried works, but I'll chalk that up to my complete misunderstanding of the above code.
Unfortunately Rails doesn't let you nest has_many's more than 2 deep.. Forgetting about naming it friends for a moment (let's call it users instead), this would theoretically be what you'd want:
has_many :group_memberships
has_many :groups, :through => :group_memberships
has_many :users, :through => groups
Except that this doesn't work. If you try it you'll see this not-so-helpful error message which comes from this bit of code, specifically source_reflection.options[:through].nil?. That is, the through isn't allowed to have a through itself.
Instead, you may want to do something like this:
Solution 1
class User < ActiveRecord::Base
has_many :group_memberships
has_many :groups, :through => :group_memberships
def friends
groups.with_users.map(&:users).flatten.uniq.reject{|u| u == self}
end
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, :through => :group_memberships
named_scope :with_users, :include => :users
end
Solution 2
Use the nested_has_many_through plugin that Radar mentioned. It looks like at least one fork of it on github has been updated to work on the latest Rails.
Solution 3 (just for kicks)
or, just for kicks, you could do it with one big SQL query:
class User < ActiveRecord::Base
has_many :group_memberships
has_many :groups, :through => :group_memberships
def friends
sql = <<-SQL
SELECT users.* FROM users, (
SELECT DISTINCT gm2.user_id AS user_id
FROM group_memberships gm, groups g, group_memberships gm2
WHERE gm.user_id = ? AND g.id = gm.group_id AND gm2.group_id = g.id AND gm2.user_id != ?
) AS user_ids
WHERE users.id = user_ids.user_id
SQL
User.find_by_sql([sql, id, id])
end
end
Use the nested_has_many_through plugin.
delegate :users, :to => 'group'