I am working on a rails app which has three models 'request', 'service' and 'price'. Request and services have a has_many_through relationship, and service and price have a has_one-belongs-to relationship i.e a service has one price and price belongs to service.
The model with relationship
class Request < ActiveRecord::Base
has_many :request_services
has_many :services, through: :request_services
end
class RequestService < ActiveRecord::Base
belongs_to :service
belongs_to :request
end
class Service < ActiveRecord::Base
has_many :request_services
has_many :requests, through: :request_services
has_one :price
end
class Price < ActiveRecord::Base
belongs_to :service
end
Now a user will create a new request which can have many services. So, I want to write a query to select the prices of the services that a user has selected in his request.
So far I have written this query,
1) To find the services in the request
Request.find(1).services
2) After this I want to get the prices of selected services, so I am writing
Request.find(1).services.prices
But this query is throwing error.
The error i am getting when running this query in rails console
Can anybody please help me in writing the query to fetch the prices of all the selected services
Request Load (0.2ms) SELECT "requests".* FROM "requests" WHERE "requests"."id" = ? LIMIT 1 [["id", 1]]
NoMethodError: Service Load (0.3ms) SELECT "services".* FROM "services" INNER JOIN "request_services" ON "services"."id" = "request_services"."service_id" WHERE "request_services"."request_id" = ? [["request_id", 1]]
undefined method `prices' for #<Service::ActiveRecord_Associations_CollectionProxy:0x007f4ec4c41bb0>
Maybe you can tackle it the other way around:
Price.joins(service: { request_services: :request}).where('requests.id = ?', request_id)
This gets all prices related to the request with the given id
You need to call Request.find(1).services.price (price is singular) since each service has only one price
Essentially your query states: "For this request (1) give me the Price of each related Service"
But chaining doesn't work that way. I'm unsure why you're using has_many-through called RequestService, but regardless, you have several options to get the price data for a request, a join, iterate-and-add-to-array, or the most simplistic add has-many-through on prices.
e.g.
Iterate & Add to Array
prices = []
Request.find(1).services.each { |s| prices << s.price }
Has Many Through
class Request < ActiveRecord::Base
has_many :request_services
has_many :services, through: :request_services
has_many :prices, through: :services
end
Which would let you do:
Request.find(1).prices
Related
How do I get an ActiveRecord collection with all the unique Jobs that exist for a particular User given that there are two possible associations?
class User
has_many :assignments
has_many :jobs, through: assignments
has_many :events
has_many :jobs_via_events, through: :events, source: :jobs
end
class Assignment
belongs_to :user
belongs_to :job
end
class Event
belongs_to :user
belongs_to :job
end
class Job
has_many :assignments
has_many :events
end
The best I could come up with so far is using multiple joins, but it isn't coming back with correct results:
Job.joins("INNER JOIN assignments a ON jobs.id = a.job_id").joins("INNER JOIN events e ON jobs.id = e.job_id").where("a.user_id = ? OR e.user_id = ?", userid, userid)
Any advice?
A simple approach is to collect all job-ids first and then fetch all jobs for those ids.
We first ask for an array of all job-ids for a user:
event_job_ids = Event.where(user_id: user).select(:job_id).map(&:job_id)
assignment_job_ids = Assignment.where(user_id: user).select(:job_id).map(&:job_id)
all_job_ids = (event_job_ids + assignment_job_ids).uniq
note that, depending on the rails version you use, it is better to replace the select(:job_id).map(&:job_id) by a .pluck(:job_id) (which should be more efficient, but the select.map
Getting the jobs for the assembled ids is then straightforward:
jobs = Job.where(id: all_job_ids)
This is the naive approach, how can we approve upon this?
If I would write a query I would write something like the following
select * from jobs
where id in (
select job_id from assignments where user_id=#{user_id}
union
select job_id from events where user_id=#{user_id}
)
so how do we convert this to a scope?
class User
def all_jobs
sql = <<-SQL
select * from jobs
where id in (
select job_id from assignments where user_id=#{self.id}
union
select job_id from events where user_id=#{self.id}
)
SQL
Job.find_by_sql(sql)
end
Of course this is untested code, but this should get you started?
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)
I have a Post model in my app, which has a posts attribute that stores a JSON object that looks something like this:
Post.last.posts = {
twitter: 'This is a tweet',
facebook: 'This is a facebook post'
}
A user creates a single Post, which is then sent out to multiple platforms. This architecture made sense for the original app design. Now I want to offer the user the ability to hide a post they've made to one platform without affecting posts to other platforms. Because a post is a single model in the database I need to find a workaround. I'm not sure if this is the best approach, but I decided to create a join table between my User model and Post model. Note that posts are created by a different user model (Admin) and User merely views posts.
Here are my models:
class Post < ActiveRecord::Base
has_many :post_users
has_many :users, through: :post_users
end
class User < ActiveRecord::Base
has_many :post_users
has_many :posts, through: :post_users
end
class PostUser < ActiveRecord::Base
belongs_to :post
belongs_to :user
delegate :hide_twitter, to: :post
delegate :hide_facebook, to: :post
def hide_twitter
self.hide_twitter
end
end
In the view I'm building, each Post is represented as a series of cards - one for each platform. So if a post is on Twitter and Facebook, it will be shown as two seperate cards - one per platform. What I want to do is give User the ability to hide one of the cards without affecting the other(s). Because a Post belongs to many users, this has to be an attribute of a join table (e.g. PostUser).
What I'd like to know is if it's possible to access this attribute of the join table through the Post model? I want to do something like the following but I'm not sure if it's possible or if I'm taking the correct approach by using delegate in my join table.
current_user.posts.first.hide_twitter
=> false
current_user.posts.first.hide_facebook
=> true
When I use delegate as above and try to call the above line of code, I get the following error:
Post Load (1.4ms) SELECT "posts".* FROM "posts" INNER JOIN "post_users" ON "posts"."id" = "post_users"."post_id" WHERE "post_users"."user_id" = $1 ORDER BY "posts"."id" ASC LIMIT 1 [["user_id", 90]]
NoMethodError: undefined method `hide_twitter' for #<Post:0x007fc27d383f50>
from /Users/ACIDSTEALTH/.gem/ruby/2.3.0/gems/activemodel-4.2.5.1/lib/active_model/attribute_methods.rb:433:in `method_missing'
I realize I could do something very roundabout like this answer, but was hoping for something a little more elegant/conventional.
How about something like:
class Post < ActiveRecord::Base
has_many :post_users
has_many :users, through: :post_users
end
class User < ActiveRecord::Base
has_many :post_users
has_many :posts, through: :post_users
end
class PostUser < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
And then:
PostUser.where(post: current_user.posts.first).hide_twitter
There are these models:
Patient
Patient has_many MedicalOrders
MedicalOrder
MedicalOrder belongs_to Patient
MedicalOrder has_many Tasks
Task
Task belongs_to MedicalOrder
Task has_many ControlMedicines
ControlMedicine
ControlMedicine belongs_to Task
And there's this block of code to get the actual #patient's control_medicines:
def index
#control_medicines = []
#patient.medical_orders.each do |mo|
mo.tasks.order(created_at: :desc).each do |t|
t.control_medicines.each do |cm|
#control_medicines << cm
end
end
end
end
I know it's not the best way to query associated models but haven't figured out how to do it using .includes() method. Mostly because .includes() only works being called to a Class (eg, Patient.includes()) and they're not suitable for nested models, like in this situation.
I've read about preloading, eager_loading and includes but all the examples are limited to get data from two associated models.
You can use the has_many through in Rails to allow ActiveRecord to make your joins for you.
class Patient < ActiveRecord::Base
has_many :medical_orders
has_many :tasks, through: :medical_orders
has_many :control_medicines, through: :tasks
end
Writing your query like:
#patient.control_medicines
Generates SQL like:
SELECT "control_medicines".* FROM "control_medicines"
INNER JOIN "tasks" ON "tasks"."id" = "control_medicines"."task_id"
INNER JOIN "medical_orders" ON "medical_orders"."id" = "tasks"."medical_order_id"
WHERE "medical_orders.patient_id" = $1 [["id", 12345]]
I am working on a liquid democracy app. My data model is:
Both Users and Organizations are Voters. A Voter has an ordered list of Delegations. A Delegation consists of a set of Tags and an ordered list of Voters (delegates).
When determining which Voter to serve as proxy, the first Delegation with a matching Tag is used and then the first delegate in the list who has voted on the Issue.
To create the ordered list of delegations, I can simply add a position field to the Delegation model. acts_as_list can then be used to manage the order.
What I'm uncertain about is how to structure the list of delegates. It seems like the database columns should be:
delegation_id
delegate_type
delegate_id
position
My current stab is:
class User < ActiveRecord::Base
has_and_belongs_to_many :organizations
has_many :delegations, as: :voter
acts_as_tagger
acts_as_voter
end
class Organization < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :delegations, as: :voter
acts_as_tagger
acts_as_voter
end
class Delegation < ActiveRecord::Base
belongs_to :voter, polymorphic: true
has_many :tags
has_and_belongs_to_many :delegation_entries, -> { order("position ASC") }
acts_as_list scope: :voter
end
class DelegationEntry < ActiveRecord::Base
belongs_to :delegation
acts_as_list scope: :delegation
end
When I do Organization.first.delegations << Delegation.create from the console I see:
(0.4ms) BEGIN
Delegation Load (1.0ms) SELECT "delegations".* FROM "delegations" WHERE ("delegations"."voter_id" IS NULL AND position > 1) ORDER BY delegations.position ASC LIMIT 1
SQL (0.7ms) UPDATE "delegations" SET position = (position + 1) WHERE ("delegations"."voter_id" = 1 AND position >= 1)
So, acts_as_list doesn't handle polymorphic associations apparently. The increment is only on the id, so it would update both the User and Organization. Since order is preserved, does this matter? It would likely become an issue when doing reorderings and insertions.
Ultimately, this structure was replaced with single table inheritance which has a single set of identifiers, so acts_as_list will work.