rails select distinct nested associations and fetch those associations - sql

i have the models User, Company, Product, View
class Company < ActiveRecord::Base
has_many :users
end
class Product < ActiveRecord::Base
has_many :views_by_user, -> { where viewable_type: User },
as: :viewable, class_name: "View"
end
class User < ActiveRecord::Base
has_many :viewed, as: :viewer, class_name: "View"
belongs_to :company
end
class View < ActiveRecord::Base
belongs_to :viewable, polymorphic: true
belongs_to :viewer, polymorphic: true
end
What i did with the above is, when a user views product, i save the data in the views
Now i want the list of distinct companies that have looked at my product(via user) and total count for my serializer. what i have done is,
distinct_users = #product.views_by_user
.includes(viewer: [:company])
.joins("left outer join users on views.viewer_id = users.id")
.select("distinct users.company_id, views.*")
but with this, i would have to do something like
distinct_users.will_paginate(...).map(&:viewer).map(&:company)
is there a better way to do it? also if i use distinct_users.count it throws me an error
PG::UndefinedFunction: ERROR: function count(integer, views) does not exist
LINE 1: SELECT COUNT(distinct users.company_id,...

Start from Company if this is the type of record you actually want. You can use merge to combine the conditions on a relation with those from another. Try this:
Company.joins(:users => :viewed).merge(View.where(viewable: #product))
HTH

Related

How to query an STI-driven model using nested associations?

My requirement is to write a query to retrieve records based of a model on STI based on the nested associations of the model.
Here’s what my modeling looks like:
class Loan < ApplicationRecord
belongs_to :borrower
end
class BusinessLoan < Loan
belongs_to :business, inverse_of: :business_loans
end
class HousingLoan < Loan
end
class Borrower < ApplicationRecord
has_many :loans
has_one :address, as: :addressable
end
class Business < ApplicationRecord
has_many :business_loans, inverse_of: :business
has_one :address, as: :addressable
end
class Address < ApplicationRecord
# COLUMN city, :string
belongs_to :addressable, polymorphic: true
end
I would like to write to retrieve the list of all loans whose business or borrower is in a particular city.
Here’s what I have at the moment:
cities = ["New York", "Washington"]
query_string = [
Loan.select(:id).joins(borrower: :address).where(city: cities).to_sql,
BusinessLoan.select(:id).joins(business: :address).where(city: cities).to_sql
].join(" UNION ")
Loan.where(id: Loan.find_by_sql(query_string))
I require the result as an ActiveRecord relation hence the last query
Is there a better way to write this query?
It doesn't look like there is a way to directly achieve this without writing a few new scopes in the associated models. Rails does not permit any operations in scopes that are not possible on the model on which the scopes are initiated.
You can however still solve the problem to yield a single query as follows:
Define from_cities scopes in Borrower and Business as shown below:
scope :from_cities, ->(cities) { joins(:address).where("addresses.city IN (?)", cities) }
Define from_cities in Loan to use these new scopes:
scope :from_cities, ->(cities) do
where(borrower_id: Borrower.from_cities(cities)).
or(where(business_id: Business.from_cities(cities)))
end

How do I chain multiple models to reduce the number of SQL queries

I'm trying to check whether a student has attempted an assigned test or not. I want to chain the relevant models to bring down the number of queries to just 1. The following are my models:
class Test < ActiveRecord::Base
has_many :assigns
has_many :attempts
belongs_to :topic
end
class Topic < ActiveRecord::Base
has_many :tests
has_many :attempts
has_many :assigns, through: :test
end
class Assign < ActiveRecord::Base
belongs_to :test
belongs_to :student
has_many :attempts
end
class Attempt < ActiveRecord::Base
belongs_to :test
belongs_to :topic
belongs_to :assign
belongs_to :student
end
I want to check if a particular student (id: 100) has attempted an assigned test or not, and also retrieve other details such as the topic name of the test. So far I have something like this:
ta = Assign.includes(:test => {:topic => :attempts})
This allows me to retrieve details such as the test_name, topic_name, when it was assigned etc. in a single query. How do I also include the Attempt records of student_id: 100 in the same query? With what I have now, when I retrieve the student's attempt details a brand new query is being generated.
What I want is something like the follwoing without having to touch the database again:
ta.test.attempts.where(student_id: 100)
How do I do all this with just one query?
Okay, since you want all kinds of information from all the joined tables, so you will have to join them up from the beginning.
Attempt.joins(:topic, :test, :assign)
Then you can filter it with the student_id
.where("attempts.student_id" => 100)
Finally, the fields you want
.select("attempts.id as attempt_id, tests.name as test_name, topics.name as topic_name, assigns.created_at as assigned_at")
In summary
Attempt
.joins(:topic, :test, :assign)
.where("attempts.student_id" => 100)
.select("attempts.id as attempt_id, tests.name as test_name, topics.name as topic_name, assigns.created_at as assigned_at")

Active Record query to find records that match all conditions in Rails has_many through relationship

I have two models, Apartments and Amenities, which are associated through ApartmentAmenities. I am trying to implement a filter where I only show apartments that have all of the amenities specified.
class Amenity < ActiveRecord::Base
has_many :apartment_amenities
has_many :apartments, through: :apartment_amenities
end
class ApartmentAmenity < ActiveRecord::Base
belongs_to :apartment
belongs_to :amenity
end
class Apartment < ActiveRecord::Base
has_many :apartment_amenities
has_many :amenities, through: :apartment_amenities
end
I've got a query working that will return all apartments that match at least one of the amenities of given set like so:
Apartment.joins(:apartment_amenities).where('apartment_amenities.amenity_id IN (?)', [1,2,3])
but this isn't quite what I'm going for.
Alright, after giving up for a few days then getting back to it, I finally found this question: How to find records, whose has_many through objects include all objects of some list?
Which led me to the answer that works properly:
def self.with_amenities(amenity_ids)
where("NOT EXISTS (SELECT * FROM amenities
WHERE NOT EXISTS (SELECT * FROM apartment_amenities
WHERE apartment_amenities.amenity_id = amenities.id
AND apartment_amenities.apartment_id = apartments.id)
AND amenities.id IN (?))", amenity_ids)
end

Rails has_and_belongs_to_many with specific key

I has two models
class Fellow < ApplicationRecord
has_and_belongs_to_many :skills
end
class Skill < ApplicationRecord
has_and_belongs_to_many :fellows
end
One fellow can have some skills, and one skill can be learned by some fellows. So I have third table
class CreateFellowsSkills < ActiveRecord::Migration[5.0]
def change
create_table :fellows_skills, id:false do |t|
t.belongs_to :skill, index: true
t.belongs_to :fellow, index: true
end
end
end
I want to use method: fellow.skills
That invoke such SQL:
SELECT "skills".* FROM "skills" INNER JOIN "fellows_skills" ON "skills"."id" = "fellows_skills"."skill_id" WHERE "fellows_skills"."fellow_id" = $1
The problem: I want to use field skill_id in table skills instead of id, so the query should be such:
SELECT "skills".* FROM "skills" INNER JOIN "fellows_skills" ON "skills"."skill_id" = "fellows_skills"."skill_id" WHERE "fellows_skills"."fellow_id" = $1
I tried to use different options in method has_and_belongs_to_many but the query is still incorrect.
http://cobwwweb.com/why-i-dont-use-has-and-belongs-to-many-in-rails
Instead of habtm, use
class Fellow < ApplicationRecord
has_many :fellows_skills
has_many :skills, through :fellows_skills
end
class Skill < ApplicationRecord
has_many :fellows_skills
has_many :fellows, through: :fellows_skills
end
class FellowsSkill < ApplicationRecord
belongs_to :fellow
belongs_to :skill
end
Furthermore I would suggest naming the third model FellowSkill (dropping the plural on fellow).
Finally I correct my db scheme and call primary key ID, so I don't need apply any changes now.

How to write this ActiveRecord Query using Join instead of subquery in Rails 4

Consider the following:
class User < ActiveRecord::Base
has_many :events
end
class Event < ActiveRecord::Base
belongs_to :user #this user is the event owner
has_many :members
end
class Members < ActiveRecord::Base
belongs_to :user
belongs_to :event
end
Now, I need to list all the members for which current_user is the owner. so I have come up with this:
#members = Member.where event_id: current_user.events
which produces the following query:
SELECT "members".* FROM "members" WHERE "members"."event_id" IN (SELECT "events"."id" FROM "events" WHERE "events"."user_id" = 1)
This works as expected but uses subqueries instead of JOIN. Does anyone know a better way to write this same query?
Add a has_many :through association to your User model:
class User < ActiveRecord::Base
has_many :events
has_many :members, :through => :events
end
Now you can query for all a user's members through the members association:
user.members
The SQL generated will look something like:
SELECT "members".* FROM "members" INNER JOIN "events" ON "members"."id" = "events"."member_id" WHERE "events"."user_id" = 1
Transformed to JOIN syntax (with table aliases to make it shorter and easier to read):
SELECT m.*
FROM events e
JOIN members m ON m.event_id = e.id
WHERE e.user_id = $1
I guess this will work.
Member.joins(:event).where("events.user_id = ?" , current_user.id)
You could do something like :
Member.joins(:event).where(events: {user_id: current_user.id})