How to test the membership in a many-to-many relation in Rails3 - ruby-on-rails-3

For example, one user has joined many group; and one group has many user members. Now I get a user object and a group object, I want to know whether this user is a member of this group.
I can see some methods, but still wandering whether there is a better way?

So, if i understand your question, what you have is something like:
class User < ActiveRecord::Base
has_many :groups, :though => :user_members
class Group < ActiveRecord::Base
has_many :users, :though => :user_members
And you want to know if a user is a member of a specific group.
So, given something like:
u = User.first
g = Group.first
Just do:
u.groups.include? g
Thats all there is to it!
ian.

ipd's way is fine, another more Rails oriented way to do it is :
u.groups.exists?(g)

Related

ActiveRecord: sort of complicated query with nested associations and sorting

Alrigth, so I'm not sure if this isn't too specific... But I really don't have a clue how to construct such a query, neither in AR, nor in SQL. So here's the situation:
I have a User model. User has_many Projects. A Project, in turn, have a following associations:
Project has_one BasicEvent
Project had_many AdditionalEvents
BasicEvent and AdditionalEvent classes are built on inheritance from a AR model class, Event
Now, the goal is this: on my view, I need:
access to all Projects count per User
access to all Events count, where happened_at attribute in nil
have all the Users sorted, by the count of Events, where happened_at attribute in nil
I made a couple of attempts at it so far, but didn't really get too far... I will appreciate any help with this complicated (at least from my perspective) query.
You can start from code like:
# -*- frozen-string-literal: true -*-
class UserStatService
SELECT_CLAUSE = <<~SQL
(SELECT count(1) FROM projects WHERE user_id=users.id) AS projects_count,
(SELECT count(1) FROM events WHERE (happened_at IS NULL)
AND (project_id IN (SELECT id FROM projects WHERE user_id=users.id)
) AS events_count,
users.*
SQL
def call
User
.select( SELECT_CLAUSE )
.order( 'events_count' )
end
end
Each User instance in resultset will have projects_count and events_count attributes.
Also any kind of cache (counter_cache or handmade) is recommended. Because the query is not expected to be fast.
Projects count per User: Project.group(:user_id).count
Events count, where happened_at attribute in nil: Event.where(happened_at: nil).count
Users sorted, by the count of Events, where happened_at attribute in nil: This is the trickiest but it's also not particularly difficult:
User.
select("users.*, (SELECT COUNT(*) FROM events WHERE user_id = users.id AND happened_at IS NULL) AS unhappened_events_count").
order("unhappened_events_count DESC")
This is not a particularly efficient query but it should do its job well for several thousand records (easily) – especially if you set an index on user_id and happened_at in the events table.
Assuming basic_events and additional_events have essentially the same columns, a great solution to this problem would be to change your database/app to use single table inheritance.
Assuming you're using Rails 5.x your models would look like this:
class User < ApplicationRecord
has_many :projects
has_many :basic_events, through: :projects
has_many :additional_events, through: :projects
end
class Project < ApplicationRecord
belongs_to :user
has_one :basic_event
has_many :additional_events
end
class Event < ApplicationRecord
belongs_to :project
end
class BasicEvent < Event
belongs_to :project
end
class AdditionalEvent < Event
belongs_to :project
end
This way, there are only three tables involved users, projects and events. Your events table will have a :type column where you can specify if it's basic or additional.
For your queries:
access to all Projects count per User
User
.select("users.*, count(projects.id) AS projects_count")
.joins(:projects)
.group('users.id')
.order('projects_count DESC')
Using select like this give you access to a :projects_count method on each User object returned in this active record relation, so if you assigned this query to a variable called users_with_projects_count you could do users_with_projects_count.first.projects_count and it would return the number of projects associated with that user.
access to all Events count, where happened_at attribute in nil
User
.select("users.*, count(events.id) AS events_count")
.joins(:events)
.where('events.happened_at IS NULL')
.group('users.id')
You can access :events_count the same way you did :projects_count in the last example.
have all the Users sorted, by the count of Events, where happened_at attribute in nil
User
.select("users.*, count(events.id) AS events_count")
.joins(:events)
.where('events.happened_at IS NULL')
.group('users.id')
.order('events_count DESC')
You use the same query as the last example just add an :order.

Returning associations for specific model when there is a polymorphic association in Rails 3.2

I have a polymorphic association in a Rails 3 app where a User may favorite objects of various classes.
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :favoriteable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :favorites
end
class Image < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
class Video < ActiveRecord::Base
has_many :favorites, :as => :favoriteable
end
I would like to be able return a list of just a User's favorite_images for example.
user.favorite_images #returns a list of the images associated with the user via :favoritable
I'm guessing there is a straightforward way of doing this but I haven't been able to figure it out. Let me know if you need anymore info.
Thanks!
===edit====
I know that I could retrieve what I am looking for via
favorite_images = user.favorites.collect{|f| if f.favoriteable_type=='Image'; f.favoriteable; end}
I could define an instance method for the User class and put that inside. I was wondering if there is a way to do it as some sort of has_many association. Really just because going forward it would be easier to have all that in one place.
When you created the table for Favorite you created a couple of columns favoriteable_id and favoriteable_type and you can use this information to restrict your query.
If you do user.favorites you will get all of the favorites and to restrict them to say just the images then you can do user.favorites.where(favoriteable_type: 'image') but that just gives you the favorite records and it sounds like you want the actual images. To get those you can do it by then mapping and pulling the favoriteable out. You'll likely want to include it in the query though so you don't hit the database so much. I would also make this a method on User.
def favorite_images
favorites.includes(:favoriteable).where(favoriteable_type: 'Image').map(&:favoriteable)
end

Rails 3 HasMany Through with STI

Here is my STI Models:
class User < ActiveRecord::Base
end
class Athlete < User
has_many :sports, :through => :user_sports
has_many :user_sports
end
class Coach < User
end
The UserSports table has user_id and sport_id... but then you run this:
athlete = Athlete.all.last
athlete.sports
The SQL that is generated is trying to use the athlete_id instead of the user_id... not too sure what I am doing wrong here... any suggestions would be greatful!
I'm not sure why you have a UserSports table. You can just use a foreign key for either User or Sport, depending on their relation to each other.
The User model would need a specified relation to the Sport model, and vice versa.
More information on that is here: http://guides.rubyonrails.org/association_basics.html#the-has_many-association
It makes sense that it's trying to pull the athlete_id instead of the user_id, since you are calling on an Athlete object.
As a side note: There is no need to write Athlete.all.last - you only need to write Athlete.last.

Rails 3 has_many :through accessing attributes

I am working with a has_many through for the first time, and despite a lot of reading here and in the guide I am not understanding the correct way to access attributes on the through table. My tables are the same as this example from another post.
class Product < ActiveRecord::Base
has_many :collaborators
has_many :users, :through => :collaborators
end
class User < ActiveRecord::Base
has_many :collaborators
has_many :products, :through => :collaborators
end
class Collaborator < ActiveRecord::Base
belongs_to :product
belongs_to :user
end
Assuming that the collaborators table has additional attributes, say hours_spent, what is the correct way to find the hours_spent from the collaborator table for a particular user and product?
When I have found my users via the product, and am iterating over them as in
#product.users.each do |user|
This seems to work
user.collaborator[0].hours_spent
I get the correct value, but since there should only be one collaborator record for each User/Product pair, the index is throwing me off, making me think I’m doing something wrong.
Thank you for reading!
EDIT
Perhaps I am not getting the has_many through concept. Maybe a MySQL example would help.
What I was thinking is that if I did
SELECT * FROM collaborators where user_id = 1;
I would expect a set (zero or more) as the result. Similarly
SELECT * FROM collaborators where product_id = 1;
would also give me a set, but
SELECT * FROM collaborators where user_id = 1 and product_id = 1;
would give at most 1 row.
If I am understanding properly, all 3 queries return a set. So I guess I need some kind of uniqueness constraint, but that would have to be a compound key of sorts, on both of the belongs to keys. Is that even possible? Is there a structure that better models this?
Thanks so much for the quick and helpful responses!
There may be a single database row per pair, but when considering a single user, that user can be associated to many products, so a user can have many rows in the collaborators table. Similarly, when considering a single product, that product can be associated to many users, so a product can have many rows in the collaborators table.
Also, instead of using user.collaborators[0].hours_spent, use user.collaborators.first.try(:hours_spent) (which may return null), if you only want the first collaborator's hours spent.
If a single user can only have one single product and a single product can only have a single user, then switch the has_many's to has_one's for everything.
Update: The preceding is the answer to the original question which has since been clarified via comments. See comments for detail and see comments on other answer by Peter.
Perhaps you should use has_and_belongs_to_many. If your Collaborator is used only to make link between User and Product without having more fields.
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
The beetween migration would be:
class CreateUsersProducts < ActiveRecord::Migration
def change
create_table "users_products", :id => false do |t|
t.integer :user_id
t.integer :product_id
end
end
end
After implementing this, what I found was that I think I had the correct relationships setup, I had to use the has_many :though as users could have many products, and it needed to be :through because there are additional attributes on the collaborator table. The sticking point was how to get there to only be a single Collaborator record for each user/product pair, and then how do I guarantee I got it. And to this point the answer I've found is it has to be done in code.
To make sure there is only a single record for each pair, I used
class Collaborator < ActiveRecord::Base
validates :product_id, :presence => true, :uniqueness => {:scope => [:user_id], :message => "This is a duplicate join"}
And then to make doubly sure I'm finding the right record, I have a scope
scope :collaboration_instance, lambda {|p_id, u_id| where("collaborations.product_id = ? && collaborations.user_id = ?", p_id, u_id)}
If someone has a more elegant solution, or just wants to improve this one, please post and I will change yours to the selected answer.

rails scope and joins

I have tried everything i thought would work for this and am turning up nothing.
in rails 3, I need to find all users with a cd player in their car. A car has one user and one radio, and a user belongs to a car, and a radio has many cars.
I am stumbling on how I would perform this search via a scope in the user model.
class User
belongs_to :car
class Car
belongs_to radio
has_one :user, :dependent => destroy
class Radio
has_many :cars
I am assuming that you mean this:
Car has radio_id, User has car_id,
since a radio has many cars and car has one user. The table with the foreign key always is on the belongs_to end of the relationship.
Without really knowing the structure you're looking for, something like the following should work:
scope :with_cd_player, joins(:cars).where('cars.radio_id is not null')
if there is a category column on the radio, the following would work.
scope :with_cd_player, joins(:car => :radio).where('cars.radio_id is not null').where("radios.category = 'cd_player'")
For Rails Version >= 4:
scope :with_cd_player, -> { joins(:cars).where.not(cars: { radio_id: nil }) }
Also you can using "merge"
https://gorails.com/blog/activerecord-merge