Find record with two ids at the same time - ruby-on-rails

I have models:
User
class User < ActiveRecord::Base
has_many :users_conversations, dependent: :destroy
has_many :conversations, through: :users_conversations, dependent: :destroy
end
UsersConversation
class UsersConversation < ActiveRecord::Base
belongs_to :conversation
belongs_to :user
end
Conversation
class Conversation < ActiveRecord::Base
has_many :users_conversations, dependent: :destroy
has_many :users, through: :users_conversations
end
I need to create query to get the joint conversation between two known users by their ID's. I have this one
Conversation.left_joins(:users).where(users: { id: [current_user.id, params[:user_id]]}).first
but it finds conversations where is any of [current_user.id, params[:user_id]], so I always get the same conversation, where is current_user, but it is wrong. As I see it is because id: [current_user.id, params[:user_id]] works like OR, but I need AND. Thank you.

The answer is not correct as mentioned in the comments. Please do not refer it.
Try using
Conversation.left_joins(:users).find_by("users.id IN(?)", [current_user.id, params[:user_id]]})
Haven't actually tried but this should work. Please let me know if you face an error.
Also, use find_by instead or Model.where(condition).first. Using the latter is a bad habit and convention in rails.
Explanation: When you have to find the records where a field matches an array, i.e., you need the value of this filed to be specific from an array, you should use IN operator of SQL. Like
SELECT * FROM Customers WHERE Country IN ('Germany', 'France', 'UK');

Related

ActiveRecord: has_many choices limited to has_many of another model

I would like to achieve something as follows where PersonSubject has many topics, but the choices of these topics are limited to the the selection of topics through another model (ie: through the associated subject):
class Topic < ApplicationRecord
belongs_to :subject
end
class Subject < ApplicationRecord
has_many :topics
end
class PersonSubject < ApplicationRecord
belongs_to :person
belongs_to :subject
has_many :topics # where the choices are limited to the subject.skills
end
I would then like if any person_subject.subject.topics are deleted (or association removed), it would automatically update the person_subject.topics to no longer "point" to the Topic(s) that were deleted.
Is this possible?
You can use a lambda to put arbitrary filters on an association. See What is the equivalent of the has_many 'conditions' option in Rails 4?
has_many :topics, -> { where(skill: subject.skills) }
I don't know that this is exact code will work without seeing your schema (what is the data type of subject.skills, and how do you join this with topic?). But hopefully this gets you on the right track
edit
in response to your comment, I think
has_many :topics, through: :skills
would work

count query - how to get relation instead of hash

I have model structure like this:
class Forum < ActiveRecord::Base
has_many :topics
class Topic < ActiveRecord::Base
has_many :posts
belongs_to :forum
class Post < ActiveRecord::Base
belongs_to :topic
has_many :post_links
has_many :links, ->{ uniq }, through: :post_links, dependent: :destroy
class PostLink < ActiveRecord::Base
belongs_to :post
belongs_to :link
class Link < ActiveRecord::Base
has_many :post_links
has_many :posts, ->{ uniq }, through: :post_links, dependent: :destroy
Now I want to get all Links from Forum with id = 1, sorted by how often they show up in posts in this forum.
Link.joins(posts: [topic: :forum]).where("forums.id = ?",1).group("links.id").order("count_all DESC").count
It gives me hash like {140 => 10, 12 => 9, 137 => 8}
I'd like to have Link relation instead of hash with ids, but I'm not sure how to change this query.
Using select in the query should return an ActiveRecord::Relation. Try this:
Link.select("links.*, COUNT(links.id) as link_count").joins(posts: [topic: :forum]).where("forums.id = ?",1).group("links.id").order("link_count DESC").count
Since I don't have your model structure and relations I can't test this, but I did it with a similar query. Using selectyou will get a Relation.
links.* will return all columns/attributes of the Link model. In case you only need specific attributes (like the url of the link) just use link.url instead.
I managed to solve it with this query:
Link.joins(posts: [topic: :forum]).where("forums.id = ?",1)
.group("links.id").order("count_links_id DESC")
.select("links.*, COUNT(links.id) AS count_links_id")
Since you are fetching the count form the maximum grouped ID's you cannot directly get the active record relation but,
You can just fire a where condition in the Link model from the returned result to get the Active record association,
Link.where(id: (Link.joins(posts: [topic: :forum]).where("forums.id = ?",1).group("links.id").order("count_all DESC").count).keys)
Returns,
#<ActiveRecord::Relation [#<Link...]

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

Method to give number of favorites in has_many through relationship

I have a User model where the users can "favorite" each other. I'm achieving this through a Favoriting model as a has_many through relationship to reference User to itself:
class User < ActiveRecord::Base
has_many :favoriting
has_many :favorites, through: :favoritings, source: :favorited
has_many :favoriteds, class_name: "Favoriting", foreign_key: "favorited_id"
has_many :favoriters, through: :favoriteds, source: :user
...
end
class Favoriting < ActiveRecord::Base
belongs_to :user
belongs_to :favorited, :class_name => 'User'
...
end
This all works great. I can do u.favorites and get a user's favorites, and I can do u.favoriters to get the users that have favorited u. I can also do u.favorites_count to get the number of favorites.
However, I can't do u.favoriters_count to get the number of users that have favorited u.
Any idea if there is access to a built-in method for favoriters_count or even favoriteds_count with this type of DB relationship? I could write my own but would rather keep the code base as simple and "Rails-y" as possible.
Have you considered adding a counter_cache alongside with a favoritings_count column?
No, the methods added by has_many are listed in 4.3.1 of http://guides.rubyonrails.org/association_basics.html and do not include a method by this name.

Aggregate has_many With Another has_many Rails

I have the following models:
class User < ActiveRecord::Base
has_many :subscriptions, as: :subscribable
has_many :user_to_high_school_subscriptions
has_many :high_school_subscriptions, through: :user_to_high_school_subscriptions
def all_subscriptions
self.subscriptions + self.high_school_subscriptions.subscriptions
end
end
class UserToHighSchoolSubscription < ActiveRecord::Base
belongs_to :user
belongs_to :high_school_subscription
end
class HighSchoolSubscription < ActiveRecord::Base
has_many :user_to_high_school_subscriptions
has_many :users, through: :user_to_high_school_subscriptions
has_many :subscriptions, as: :subscribable
end
class Subscription < ActiveRecord::Base
belongs_to :subscribable, polymorphic: true
end
Is there a clever way for me to get ALL Subscriptions that a User has.
I tried
u = User.first
subs = u.all_subscriptions
but that is erroring out (undefined method subscriptions' for #<ActiveRecord::Relation:). I think it's choking when I try to use the has_many :subscriptions on the HighSchoolSubscription because a user has_many :high_school_subscriptions. (This line: self.high_school_subscriptions.subscriptions).
Is there a way to aggregate has_many on a has_many in Rails?
Running rails 3.2.1
self.subscriptions does not return an Array but ActiveRecord::Relation. That's the reason why the + methods and does not work and your get the mentioned error. The simplest fix is to do it this way:
def all_subscriptions
self.subscriptions.all + self.high_school_subscriptions.all.collect { |hss| hss.subscriptions.all }.flatten
end
The all method will trigger the database queries and return an array. Because a user might have many high school subscriptions and these also may has many subscriptions, you have to iterate over all high school subscriptions and collect their subscriptions. As you can see, this is a complete overkill.
Redesign your data model or just do it a different way.
Perhaps, scoping the Subscription model might be the way. Add it an attribute that would specify what kind of subscription it is and then you can completely remove the HighSchoolSubscription model.

Resources