I have four tables. They are Brands, departments, categories and product_details. The models are
Brand model
class Brand < ActiveRecord::Base
has_many :product_details, :dependent=>:destroy
searchable do
integer :id
text :name
end
end
Deparment model
class Department < ActiveRecord::Base
has_many :product_details, :dependent=>:destroy
searchable do
integer :id
text :name
end
end
Category model
class Category < ActiveRecord::Base
has_many :product_details, :dependent=>:destroy
searchable do
integer :id
text :name
end
end
ProductDetail model
class ProductDetail < ActiveRecord::Base
belongs_to :department
belongs_to :category
belongs_to :brand
searchable do
text :name
text :brand do
brand.name
end
integer :department_id
integer :category_id
end
If user searches for department 1 first I have get all product details based on department id. The resultant table must contain brand name, category name and department name also. Is it possible to do it using sunspot solr? How?
I think you should create a Document for each Product with Brand, Department, Category and Product details.
Then you're able to search for what you want, and receive the information you need.
Related
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")
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
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.
I have an application where users can customize a calendar and fill it with a given pool of events. A user can also overwrite a title for his own calendar by an alias. So I have the following has_many :through relation:
class Calendar < ActiveRecord::Base
has_many :event_aliases
has_many :events, :through => :event_aliases
end
class Event < ActiveRecord::Base
attr_accessible :title
has_many :event_aliases
has_many :calendars, :through => :event_aliases
end
class EventAliases < ActiveRecord::Base
attr_accessible :course_id, :calendar_id, :custom_name
belongs_to :event
belongs_to :calendar
end
No I want to deliver the calendar with the aliases. If an event has an alias (custom_name), it should be displayed. Otherwise the default event name (title) should be displayed.
Is there a way to easily set up a query that returns all events for the current calendar whether with a custom_name (if exists) or with the default title?
My current solution is to hardcode an if condition into the query which I would like to avoid.
title_column = "case when custom_name IS NOT NULL then custom_name else title end as title"
# assume we are given a calendar_id
Calendar.find(calendar_id).event_aliases.joins(:event).select(title_column, :event_id).each do |event_alias|
# do further stuff here
end
I also could fetch all event_aliases and run through each of them to get the default title if necessary.
# assume we are given a calendar_id
Calendar.find(calendar_id).event_aliases.each do |event_alias|
title = event_alias.custom_name
if title.nil?
title = Event.find(event_alias.event_id).title
# do further stuff here
end
But this one results in too many queries to me.
So is there any smarter way of accomplishing what I want? Maybe using named scopes or another fancy rails technique?
UPDATE
I ended up with making a "custom" select via the has_many :through relationship. So the only thing changes is the Calendar model:
class Calendar < ActiveRecord::Base
has_many :event_aliases
has_many :events, :through => :event_aliases,
:select => "event_aliases.custom_name as custom_name, events.*"
end
So accessing the custom_name / the title now happens a little like #Doon suggested:
Calendar.find(1).courses.each do |course|
title = course.custom_name || course.title
end
This creates only 2 queries instead of 3:
Calendar Load (0.6ms) SELECT `calendars`.* FROM `calendars` WHERE `calendars`.`id` = 1 LIMIT 1
Event Load (0.7ms) SELECT event_aliases.custom_name as custom_name, events.* FROM `events` INNER JOIN `event_aliases` ON `events`.`id` = `event_aliases`.`event_id` WHERE `event_aliases`.`calendar_id` = 1
what about using includes to grab the events at the same time as you pull the aliases.
Calendar.find(1).event_aliases.includes(:event).each do |e|
puts e.custom_name.blank? ? e.event.title : e.custom_name
end
the SQL Rails generates will look something like this:
Calendar Load (0.2ms) SELECT "calendars".* FROM "calendars" WHERE "calendars"."id" = ? LIMIT 1
EventAlias Load (0.2ms) SELECT "event_aliases".* FROM "event_aliases" WHERE "event_aliases"."calendar_id" = 1
Event Load (0.2ms) SELECT "events".* FROM "events" WHERE "events"."id" IN (1, 2)
also if you want to clean it up a bit you can add a virtual field to the EventAlias
class EventAlias < ActiveRecord::Base
def name
custom_name || self.event.title
end
end
As long as you use the includes, the queries will be be the same.
I have tree tables
books
bookmarks
users
where there is a n to m relation from books to users trough bookmarks.
Im looking for a query, where I get all the books of a certain user including the bookmarks. If no bookmarks are there, there should be a null included...
my sql statement looks like:
SELECT * FROM `books`
LEFT OUTER JOIN `bookmarks `
ON bookmarks.book_id = books.id
AND bookmarks.user_id = ?
In rails I only know the :include statement, but how can I add the second bookmarks.user_id = ? statement in the ON section of this query? if I put it in the :conditions part, no null results would get returned!
Thanks!
Markus
Add the following associations to your models:
class Book < ActiveRecord::Base
has_many :bookmarks
has_many :users, :through => :bookmarks
end
# assuming bookmarks table has book_id and user_id columns.
class Bookmark < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
class User < ActiveRecord::Base
has_many :bookmarks
has_many :books, :through => :bookmarks
end
Now you can make following calls:
u.books # books for a user
u.bookmarks # bookmarks for a user
# find the user and eager load books and bookmarks
User.find(params[:id], :include => [:books, :bookmarks])
b.users # users for a book
b.bookmarks # bookmarks for a book
# find the book and eager load users and bookmarks
Book.find(params[:id], :include => [:users, :bookmarks])