Rails using 'where' on a model that has a has_many relationship - ruby-on-rails

I have 2 models, Account and User
class Account
has_many :users
end
class User
belongs_to :account
end
The relevant model details for my issue are:
Every Account has a subscription type, which can be either standard, premium or enterprise
How do I properly list all Users that belong to a premium Account? Additionally, how do I list all Users in all premium Accounts, since there can be any number of premium Accounts.
I've tried several variations of the things below:
Account.where(subscription: "premium").users # undefined method users
User.where(subscription: "premium") # returns nothing
Account.where(subscription: "premium").each do |account|
account.users # seems to return an array?
end

What you want is a left inner join:
User.joins(:account)
.where(accounts: { subscription: "premium" })
This will return any records from the users table that have a match in the joined table.
How do I properly list all Users that belong to a premium Account?
If what you mean is users that belong to a specific account you would call the #users method on that specific account.
account = Account.eager_load(:users).find(1)
account.users

For your first question:
list all Users that belong to a premium Account:
Your first try (Account.where(subscription: "premium").users) doesn't work because Account.where(subscription: "premium") already returns many Accounts, so a list (more precisely an ActiveRecord Relation object) and you can't call .users on that list. You can only call .users on one account
What you need is a join statement to join both tables. You can read more about it here: https://guides.rubyonrails.org/active_record_querying.html#joining-tables
And since you want users, you should start with user.
Your query should look something like that:
User.joins(:account).where(accounts: {subscription: "premium"})
This will give you all Users that have an account with the subscirption type premium.
I think your second question regarding the query is acutally the same
Additionally, how do I list all Users in all premium Accounts, since there can be any number of premium Accounts.

The other answers are great, another option would be in Account model
scope :premium, -> { where(subscription: 'premium') }
and your query
User.joins(:account).merge(Account.premium)

Related

PostgreSQL ActiveRecord Query Optimization

I'm new to RoR and I need a query that returns the same instances as the following definition of queue :
#all = User.all
#membersOfActivity = User.where(id: User.joins(:memberships).where("activity_id = ?", session[:current_activity_id]))
#usersIAlreadyLiked = User.where(id: User.joins(:likes).where("activity_id = ? AND activity_likes_user IS NOT NULL", session[:current_activity_id]))
#notWanted = #membersOfActivity + #usersIAlreadyLiked
#queue = #all - #notWanted
However, I understand how inefficient the query I just wrote is for it downloads all the Users first to then select the complement. Do you have any idea on how to make it more efficient and directly select the complement of #notWanted? I tried several queries but none of those worked.
If you think you have a better solutions please let me know! Thanks!
EDIT :
Each user has many memberships, that connect a user with an activity. So each activity has also many membership. Each membership has a user_id and an activity_id.
Each user has many likes, and each like connects a user and an activity. Therefore each activity has also many likes. A like has a user_id, activity_id, user_liked_activity, and activity_liked_user.
user_liked_activity is NULL if the user did not express an opinion about the activity, TRUE is the user liked the activity, and FALSE if the user disliked the activity. Viceversa for activity_liked_user.
Here's a Rails 5 solution, using not syntax. At first glance, this appears to be similar to your original solution, but this runs only one query, built from the separate excludable categories (members and already_liked).
class User < ApplicationRecord
has_many :likes
has_many :memberships
def self.queue(current_activity_id)
members = User.joins(:memberships).where(memberships: {activity_id: current_activity_id})
already_liked = User.joins(:likes).where(likes: {activity_id: current_activity_id, activity_liked_user: [true, false]})
where.not(id: members).where.not(id: already_liked)
end
end
You will call this from your controller like so:
#queue = User.queue(session[:current_activity_id])
As I understand the problem, for the current session activity, we want a query that will return all users except those that belong to one of two categories: (1) Those who are members of the activity, represented by users having a membership linked to the current activity, and (2) those who already have a "liked" rating from the activity, represented by users having a "like" linked to the current activity for which activity_likes_user is either true or false (but not nil).
We establish ActiveRecord relations for each category, but we don't call anything that would actually trigger a query. Category 1 is members, and Category 2 is already_liked.
Now we build a query on the User model (this is implied, because we are calling from within a class method on the User class) asking for users where id is not one of the members and is not one of the already_liked.
There may be a more elegant solution, but this does work, and I believe it handles edge cases properly, though you should build tests to verify this.

Active Record Where Clause For Relation In Model

I have two Models User and Site. The User has category like 'basic and premium'. The user has relation to sites one -> many (i.e one user can have more than one sites). Now i want to select sites of premium user. Can someone tell me how to use where clause in ActiveRecord to achieve this?
I want to select sites of premium user
This will do
User.includes(:sites).where('users.category = ?', 'premium')
Update
If sites also have categories like 'wordpress or joomla', how do i
apply where clause to select only wordpress sites of premium users
For that you need to tweak the query like this
User.includes(:sites).where('users.category = ? and sites.category = ?', 'premium','wordpress')
As you said that category is a column, so Rails automatically generates the following method for you:
User.find_by_category("basic")
If you do not want to use this, you can use where method, in which you have to send a key-value pair like following:
User.where(:category => "basic")
And when you have found a user with the category you desired, you can simply call sites on it to get all the associated sites with a particular user.
You can try this also:
#user_ids = User.where(category: 'premium').pluck(:id)
#sites = Site.where(user_id: #user_ids)

How would I create an activerecord scope(s) that would return customers who's next visit resulted in a purchase

I have three activerecord models: Customer, Visit and Campaign.
Customer has_many :visits
Visit belongs_to :customer
Campaign has_many :visits
The Visit model tracks everytime a particular customer visits a website, pages visited, ads displayed and most importantly if they made a purchase. A Campaign is a series of Ads the customers see during there visits to the site. Each Campaign lasts 1 hour (24 campaigns a day) and has many visits.
What I'm trying to do is develop some activerecord scopes or class methods that will enable me to identify "Next Visit Purchases".
For example, on July 4th the fourth campaign of the day had 100 visits by customers. I want to be able to look at the next visit for each of those customers and identify those visits/customers that had/made a purchase on that next visit. What I'm finding difficult to wrap my mind around is that customers subsequent visits aren't all on the same day, yet I want to identify the "Next Visit" and those that resulted in a Purchase.
What I envisioned is something like:
Campaign.find(2232).next_visit.purchase #where next_visit and purchase are scopes
or
Visit.find(5445).next_visit.purchase
I have a purchase flag in the visits model so the purchase scope is fairly straight forward.
scope, :purchase, where(:purchase_flag => true)
Also based on the Railscast #215, if I create this scope on the visits model I can then use joins and merge to apply them to the Customer and Campaign models.
Campaign.joins(:visits).merge(Visit.purchase)
Is this the correct approach? If so, how do I define my Next scope and if not, what would you suggest as an alternative approach.
Update:
I've gotten some great responses. Just curious to know if the general consensus is that Deepak's approach is on point or are the other responses preferable.
I dont think scope is the right thing here, because you need to call it on an object. You could implement it as a method in visit.rb.
Could be like:
def next_visit
Visit.where(['date > ?', self.date]).order(:date).limit(1).first
end
EDIT:
For you to be able to chain methods
def next_visits
Visit.where(['date > ?', self.date])
end
So what you want is to measure the efficiency of a campaign, and have data about the customers that came back after the campaign and did purchases.
I propose:
class Campaign
def next_visits
# Wrapping the whole code of this method in #next_visits will perform local caching
# There is a good railscast that explain it in details (5 minutes long video)
# You can see it there: http://railscasts.com/episodes/1-caching-with-instance-variables
#next_visits ||= begin
# This will be the starting date of "next visits"
since = self.end_of_campaign # I assume you can get the TimeDate of the end of campain
# List of ids of customers that participated to the campaign
customers_ids = self.visits.uniq(:customer_id).pluck(:customer_id)
# Return the visit records
next_visits = Visit.where(purchase_flag: true, customer_id: customers_ids)
next_visits.where('created_at > ?', since).first
end
end
end
Then you call Campaign.find(123).next_visits
EDIT
You should use correct TZ with the variable since
About #next_visits ||= begin ... end: This is a caching technique. First time you call the method, all the code inside the begin blok will be executed, and the result (the records) will be stored in the instance variable #next_visits and returned to the caller.
Next time you call the method, the cached result stroed in #next_visits will be directly returned without hitting your database. That is good for performance.
More info about that http://railscasts.com/episodes/1-caching-with-instance-variables
In the current database structure it is quite clumsy to achieve what you need and likely be very inefficient as well.
I have a different approach to this problem:
When a purchase is made - it is best opportunity to know which campaign/visit (check the last visit of the user and find the campaign) led to this purchase.
To capture these information, create appropriate relationship - with custom name - between campaign and visit.
Populate these in after_save/after_create callback of appropriate model (most likely Purchase or Visit).
Once the data is captured in above manner, queries are relatively easy.

Gem, update and compatibility

I have a User model, the user can gradually insert information on their profile (age, description, avatar, etc..). Those users can be viewed in the public web site only if they have complete their entire profile.
Whats is the best way in rails to put constraint on query without polluting every single call to Active Record User model. Is there're a way for
User.all
to return result with those constraints by default?
Tks a lot!
You could define a scope.
# user.rb
scope :complete, where("age IS NOT NULL", "description IS NOT NULL",...)
Then you can just do User.complete and it will fetch User objects matching those conditions. For more information:
http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html

Data Modeling Question for Rails

I have the following entities.
User
Event
Expense
Guest
A User is someone that is registered with my site. An event is something that a specific user can create and then invite people to (other users). An expense is created by a specific user for a specific event and can be associated with other users/guests of the event (like user A bought something for user B and user C and guest D). A guest is also created by a specific user for a specific event but once created for the event, the other users associated with that event can see the guest. A guest will never actually log into the system...it is just a placeholder for people that may have attended an event but whom are not registered on the site.
I am trying to wrap my head around the has_many and belongs_to attributes of these entities.
It is almost like in my many-to-many relationship tables I need another column which identifies the "type" of person (user or guest) so I can join properly to pull in names, etc. from the users and guests tables accordingly.
I have thought about creating a flag in the users table that says whether the record is a guest (and therefore not require that guest to register with the site, login, etc.) but that doesn't seem very clean.
Any thoughts are greatly appreciated.
Thanks!
Thomas
This sounds to me like you're naturally leaning towards what is called a polymorphic association. It might be useful for you to have a look at this Railscast by Ryan Bates: Polymorphic Associations
I agree with Gav Polymorphic just one table people with a column type and tada
class Person < ActiveRecord:Base
#methods and associations all people have
end
class User < Person
#methods and associations specific to users
end
class Guest < Person
#methods and associations specific to guests
end

Resources