Rails: Find array of related objects - ruby-on-rails

This is the code I currently have:
project_ids = ProjectEnrollment.unscoped.where(user: self).pluck(:project_id)
Project.where(id: project_ids)
I'm sure there has to be a better way. Thanks!
Edit: I'm already using the method from the has_many relationship, with scoped projects for another case.

In your User model:
has_many :project_enrollments, -> { unscoped }
has_many :projects, through: :project_enrollments
In your ProjectEnrollment model:
belongs_to :user
belongs_to :project
Then you can do
user.projects

Related

Activerecord has_many :through through multiple models

I'm trying to access all comments from a given user with user.comments. The query is to go through two different models, which likely both return results. My relations are set up as follow:
class User < ActiveRecord::Base
has_many :organisers
has_many :participants
has_many :comments, through: :participants / :organisers (see explenation below)
end
class Organiser < ActiveRecord::Base
belongs_to :user
end
class Participant < ActiveRecord::Base
belongs_to :user
end
class Comment < ActiveRecord::Base
belongs_to :organiser
belongs_to :participant
end
A comment is validated to belong to either a participant, or an organiser.
I'm not sure how to go about this. I've tried
has_many :comments, through: :participants
has_many :comments, through: :organisers
and
has_many :comments, through: [:organisers, :participants]
But that last one isn't rails. Is there a proper way to do this? Thanks!
has_many :comments, ->(user) {
unscope(where: :user_id).
left_joins(:organizer, :participant).
where('organizers.user_id = ? OR participants.user_id = ?', user.id, user.id)
}
The unscope is to remove the comments.user_id = ? clause (which is added by default when you define a has_many relation).
The left_joins is called on Comment, so you need to pass in the relation names as defined on Comment, hence the singulars in this example.
I found a solution after many tries. You can use a scope with param in your last has_many sentence in the User model:
has_many :comments, -> (user) {where organiser: user.organisers}, through: :participants
The "user" param represet the User object whom is calling the comments method.
For anyone coming across this using polymorphic associations, the following worked for me inspired by magni- and Carlos Jimenez' answers:
has_many :comments, -> (user) {
unscope(where: :user_id).
where(commentable: [user.organizers, user.participants])
}
Since we couldn't use has_many, through here because comments come from both of organisers and participants. I just think there are 2 solutions here:
Solution #1 Define comments method:
class User < ActiveRecord::Base
def comments
Comment.joins([{organiser: :user}, {participant: :user}])
.where(users: {id: self.id})
end
end
So then your query to find comments is:
User.first.comments
Solution #2 Use scope in Comment
class Comment < ActiveRecord::Base
scope :from_user, -> (user) {
joins([{organiser: :user}, {participant: :user}]).where(users: {id: user.id})
}
end
So your query will be like:
user = User.first
comments = Comment.from_user(user)
Since we couldn't use has_many, through here because comments come from both of organizers and participants. I just think there are 2 solutions here:
Basically you can still change the foreign key to accept the self.id automatically with Rails here
User.first.comments
class User
has_many :comments, -> { joins([{ organiser: :user }, { participant: :user }]) }, through: :participants, foreign_key: "users.id"
end
I believe your associations would be confused, as user.comments wouldn't know whether it's going through Participant or Organiser, so the best option would be to declare two different joins (with different names):
http://guides.rubyonrails.org/association_basics.html#self-joins

How to query nested associations in rails

My models are
campaign.rb
has_many: views_logs
user.rb
has_many :views_logs
views_log.rb
belongs_to :campaign
belongs_to :user
I want to get Campaign.first.views_logs.uniq.users.genders
I know this query this is wrong but basically I want to get this
You need to define the direct relationship with the through parameter:
class ModelName
has_many :views_logs
has_many :users through: :views_logs
end
Then you can query it like this: model_name.user.where(gender: 'male')

Rows with at least one matched object through a 'has_many through' relationship

My Models are set up as follows:
class Record < ActiveRecord::Base
belongs_to :activity
has_many :codes, -> { order("codes.name") }, through: :activity
class Activity < ActiveRecord::Base
has_many :activity_code_links
has_many :codes, through: :activity_code_links
has_many :records
class ActivityCodeLink < ActiveRecord::Base
belongs_to :activity
belongs_to :code
class Code < ActiveRecord::Base
has_many :activity_code_links
has_many :activities, through: :activity_code_links
has_many :records, through: :activities
In a controller, i have an ActiveRecord Relation of records, #records.
I would like my resulting #records to only contain Records that have a specific Code.
Currently, i am using the below solution; however it is not efficient and also returns an Array, not an ActiveRecord Relation:
#records = #records.reject { |record| (record.codes & [code]).empty? }
Any help would be appreciated
Cheers
#records.joins(:codes).where(codes: {id: code.id})
Please note that if you don't need to use codes entities later you should use joins instead of includes since it won't instantiate ActiveRecord objects which adds overhead
#records = #records.includes(:codes).where(codes: {id: code.id})
Where code is a Code object which is the one you want to be filtered.

Rails multiple association types

Can you have a has_many belongs_to relationship AND a has_many :through relationship without any method call conflicts?
For example, what would user.hacks return - the hacks that the user posted, or the hacks that the user marked as favorites?
I need to be able to access both results.
class User < ActiveRecord::Base
has_many :hacks, through: :favorites
has_many :hacks
end
class Hack < ActiveRecord::Base
has_many :hacks, through: :favorites
belongs_to :users
end
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :hack
end
The has_many defines the methods depending on the relationship name.
This means, that if you are defining a relationship several times with the same name but different options, the last definition will override the methods of the previous definition calls.
So you can't define them with the same name.
If you need to access to both, hacks and favorite hacks, you have to create the relationships as follows
class User < ActiveRecord::Base
has_many :hacks
has_many :favorites
has_many :favorited_hacks, through: :favorites, source: :hack
end
I'm not sure what it will return, try it in the console.
However i would something like that in the user class (not tested, but you can look on how to alias relations):
has_many :favorite_hacks, through: :favorites, class: :hacks
There seems to be a typo in Hack.
On another note, the way you do it seems strange to me. The user hack relation seems like ownership, and the user - fav seems like bookmarking.
If all you want is to get a list of all favourite hacks (and all hacks belong to the user), then I think it would be a much better idea to model this behaviour like so:
class User < ActiveRecord::Base
has_many :hacks
end
class Hack < ActiveRecord::Base
belongs_to :user
scope :favorite, -> { where(favorite: true) }
end
and simply have a favorite attribute on your Hack model. Then you could find the favorites by invoking user.hacks.favorite.

Creating a many-to-many relation in Rails

So I'm Rails n00b and I want to create a "favorites" relationship such that a User can have many favorite Item. I'm not entirely sure how to do this, this is how I'm going to try but I'm not sure if this is a good practice at all:
class User < ActiveRecord::Base
has_many :favorites
//other code
end
class Favorite < ActiveRecord::Base
belong_to :user
has_one :item
end
class Item < ActiveRecord::Base
belongs_to :item
end
Is this a good way to do it? Should I be using has_and_belongs_to_many ?
I'm specially concerned in the following scenario: Say a user has 100 favorite items.
When I do a User.find(id) will I also be retrieving the 100 favorites and the 100 Items?
In case it's important: ruby version 1.9.3, rails version 3.2.11
Can you try has_many => :through?
class User < ActiveRecord::Base
has_many :favorites
has_many :items, :through => :favorites
//other code
end
In your case has_many :through is definitely the way to go. I would recommend reading: http://guides.rubyonrails.org/association_basics.html
Of particular interest with regard to your question:
2.8 Choosing Between has_many :through and has_and_belongs_to_many
Rails offers two different ways to declare a many-to-many relationship between models. The simpler way is to use has_and_belongs_to_many, which allows you to make the association directly:
class Assembly < ActiveRecord::Base
has_and_belongs_to_many :parts
end
class Part < ActiveRecord::Base
has_and_belongs_to_many :assemblies
end
The second way to declare a many-to-many relationship is to use has_many :through. This makes the association indirectly, through a join model:
class Assembly < ActiveRecord::Base
has_many :manifests
has_many :parts, :through => :manifests
end
class Manifest < ActiveRecord::Base
belongs_to :assembly
belongs_to :part
end
class Part < ActiveRecord::Base
has_many :manifests
has_many :assemblies, :through => :manifests
end
The simplest rule of thumb is that you should set up a has_many :through relationship if you need to work with the relationship model as an independent entity. If you don’t need to do anything with the relationship model, it may be simpler to set up a has_and_belongs_to_many relationship (though you’ll need to remember to create the joining table in the database).
You should use has_many :through if you need validations, callbacks, or extra attributes on the join model.
It is better than using has_and_belongs_to_many.
When I do a User.find(id) will I also be retrieving the 100 favorites
and the 100 Items?
No. You'll just get the user object.
Update:
Calling User.include(:favourites, :items).find(id) will get you joined tables in case you want to make many calls to items table from user object.

Resources