nested queries for has_many :through in rails - ruby-on-rails

Association of model is
class Campaign
has_many :views_logs
has_many :users, through: :views_logs
end
I want to get only those users of campaign where views_logs is created b/w specific dates.
Note: created_at query for views_logs not for Users creation

Campaign.joins(:users).where("views_logs.created_at = ?", my_date)
Joining Users like this will perform a SQL JOIN on your views_logs then a SQL JOIN on your users, which allows you to use a view_logs column in your WHERE condition.

Related

Rails EXCEPT query (filter out records present in a joint table)

I'm trying to list the model instances that do not have the association with another model created yet.
Here is how my models are related:
Ticket.rb:
has_one :purchase
has_one :user, through: :purchase
User.rb:
has_many :purchases
has_many :tickets, through: :purchases
Purchase.rb:
belongs_to :ticket
belongs_to :user
I have an SQL query but have troubles when translating it to rails:
SELECT id FROM tickets
EXCEPT
SELECT ticket_id FROM purchases;
It works great as it returns all ids of the tickets that are not purchased yet.
I've tried this:
Ticket.joins('LEFT JOIN ON tickets.id = purchases.ticket_id').where(purchases: {ticket_id: nil})
but it seems not to be the right direction.
If you're just trying to get the list of Ticket records with no associated purchases, use .includes instead. In my experience a join will fail with no associated records, and this will keep you from needing to write any actual SQL.
Ticket.includes(:purchase).where(purchases: { ticket_id: nil} )
The generated SQL query is a bit more difficult to read as a human, but I've used it several times and not seen any real difference in performance.

How do I get the records with exact has_many through number of entries on rails

I have a many to many relationship through a has_many through
class Person < ActiveRecord::Base
has_many :rentals
has_many :books, through rentals
end
class Rentals < ActiveRecord::Base
belongs_to :book
belongs_to :person
end
class Book < ActiveRecord::Base
has_many :rentals
has_many :persons, through rentals
end
How can I get the persons that have only one book?
If the table for Person is called persons, you can build an appropriate SQL query using ActiveRecord's query DSL:
people_with_book_ids = Person.joins(:books)
.select('persons.id')
.group('persons.id')
.having('COUNT(books.id) = 1')
Person.where(id: people_with_book_ids)
Although it's two lines of Rails code, ActiveRecord will combine it into a single call to the database. If you run it in a Rails console, you may see a SQL statement that looks something like:
SELECT "persons".* FROM "persons" WHERE "deals"."id" IN
(SELECT persons.id FROM "persons" INNER JOIN "rentals"
ON "rentals"."person_id" = "persons"."id"
INNER JOIN "books" ON "rentals"."book_id" = "books"."id"
GROUP BY persons.id HAVING count(books.id) > 1)
If this is something you want to do often, Rails offers what is called a counter cache:
The :counter_cache option can be used to make finding the number of belonging objects more efficient.
With this declaration, Rails will keep the cache value up to date, and then return that value in response to the size method.
Effectively this places a new attribute on your Person called books_count that will allow you to quite simply filter by the number of associated books:
Person.where(books_count: 1)

How to combine two has_many associations for an instance and a collection in Rails?

I'm having trouble combining two has_many relations. Here are my associations currently:
def Note
belongs_to :user
belongs_to :robot
end
def User
has_many :notes
belongs_to :group
end
def Robot
has_many :notes
belongs_to :group
end
def Group
has_many :users
has_many :robots
has_many :user_notes, class_name: 'Note', through: :users, source: :notes
has_many :robot_notes, class_name: 'Note', through: :robots, source: :notes
end
I'd like to be able to get all notes, both from the user and the robots, at the same time. The way I currently do that is:
def notes
Note.where(id: (user_notes.ids + robot_notes.ids))
end
This works, but I don't know a clever way of getting all notes for a given collection of groups (without calling #collect for efficiency purposes).
I would like the following to return all user/robot notes for each group in the collection
Group.all.notes
Is there a way to do this in a single query without looping through each group?
Refer Active record Joins and Eager Loading documentation for detailed and efficient ways.
For example, You could avoid n+1 query problem here in this case as follows,
class Group
# Add a scope to eager load user & robot notes
scope :load_notes, -> { includes(:user_notes, :robot_notes) }
def notes
user_notes & robot_notes
end
end
# Load notes for group collections
Group.load_notes.all.notes
You can always handover the querying to the db which is built for such purposes. For example, your earlier query for returning all the notes associated with users and robots can be achieved by:
Notes.find_by_sql ["SELECT * FROM notes WHERE user_id IN (SELECT id FROM users) UNION SELECT * FROM notes WHERE robot_id IN (SELECT id FROM robots)"]
If you want to return the notes from users and robots associated with a given group with ID gid(say), you'll have to modify the nested sql query:
Notes.find_by_sql ["SELECT * FROM notes WHERE user_id IN (SELECT id FROM users WHERE group_id = ?) UNION SELECT * FROM notes WHERE robot_id IN (SELECT id FROM robots WHERE group_id = ?)", gid, gid]
Note:
If you want your application to scale then you may want as many DB transactions executed within a given period as possible, which means you run shorter multiple queries. But if you want to run as little queries as possible from ActiveRecord using the above mentioned method, then it will effect the performance of you DB due to larger queries.

Ruby on rails fetch name and show from the has many through

we have user table and also wee have a country table the association is given as
user.rb
has_many :user_countries
has_many :countries, :through => :user_countries
user_country.rb
belongs_to :user
belongs_to :country
country.rb
has_many :users, :through => :user_countries
now what i want is to fetch the country name and show it into the sql record such that the new user_country column should come which will show the country of the user while fetching users
data = User.select("users.*, countries.name as country_name").
joins("INNER JOIN `user_countries` ON `user_countries`.`user_id ` = `users`.`id`").
joins("INNER JOIN `countries` ON `countries`.`id ` = `user_countries`.`country_id`")
till now i am trying to do this but not succeeded it throws the mysql error . i need an extra column giving country_name as extra column. Please help me with this
since you already using activerecord associations, you can use active record query as the following:
User.joins(:countries).select("users.*, countries.name as country_name")
You can learn more about select here
Looks like you're trying to create a many to many relationship. This can be done very simply with a has_and_belongs_to_many association. Take a look at http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association.

Rails, find results based on shared association using Active Record Query or AREL

I'm working on a rails project where I have a User, that has_many Teams through a Membership model. The User has a privacy setting which can be (public, private or protected)
On the current user's homepage, I want to display all of the users who have their profiles set to public, but also the users who have their profile set to protected and share a team with the current user.
I could do this as two separate queries and then combine the resulting arrays but I'm assuming it's 'better' to keep it as one - I think it will also then behave better with will_paginate.
I thought I might need to use Arel because of the .or condition but just can't get my head around the joins needed to work out shared teams.
I'm new to AREL, SQL and Stackoverflow for that matter, so apologies if this doesn't make much sense.
I'm assuming the following setup:
class User < AR
has_many :memberships
has_many :teams, through: :memberships
end
class Membership < AR
belongs_to :user
belongs_to :team
end
class Team < AR
has_many :memberships
has_many :teams, through: :memberships
end
Then this should work:
sql = <<QUERY
SELECT users.* FROM users WHERE users.privacy_setting = 'public'
UNION
SELECT users.* FROM users JOIN memberships ON users.id = memberships.user_id
WHERE memberships.team_id IN (#{current_user.team_ids})
AND users.privacy_setting = 'protected'
QUERY
# use the paginated find_by_sql method (provided by will_paginate)
User.paginate_by_sql(sql, page: params[:page], per_page: 50)
Of course the column names, etc depend on your setup...
PS: No need for AREL, I think...

Resources