Scope on Polymorpic model in Ruby on Rails - ruby-on-rails

Is it possible to make a scope for a polymorphic model in the following case?
I have a polymorphic model named Mutations.
class Mutation < ApplicationRecord
belongs_to :mutationable, :polymorphic => true
end
A Mutation belongs to both models TimeRegistration and SickRegistration. A TimeRegistration and a SickRegistration (Mutationable) belongs_to a User.
class SickRegistration < ApplicationRecord
belongs_to :user
has_many :mutations, as: :mutationable
end
class TimeRegistration < ApplicationRecord
belongs_to :user
has_many :mutations, as: :mutationable
end
I want to create a scope for the Mutation whereby i can retrieve a collection of Mutations by a given user name. I have more scopes already on the Mutation model, so this one must be joined with the other used scopes (used for filtering).
So something like this on the Mutation Model:
scope :with_name, -> (name) { joins(mutationable: :user).where('users.name = ?', name) }
This won't work. I've also tried to make a delegate on the Mutation model and to make a custom SQL Query with multiple joins on the mutationable models, but without success. I think there must be an (more easy) way to do this, but i can't find any good examples or ansewers for this problem.
Please help. Thanks in advance!

******TRY THIS*****
Mutation.rb
belongs_to :sick_registation, ->{where(mutations: {mutationable_type: 'SickRegistation'})},
foreign_key: 'mutationable_id'
belongs_to :time_registation, ->{where(mutations: {mutationable_type: 'TimeRegistation'})},
foreign_key: 'mutationable_id'
scope :with_name, ->(name){
joins(sick_registation: :user).where(user:{name: name}) +
joins(time_registation: :user).where(user:{name: name})
}
Explanation:
There is no direct relation between your polymorphic relation and user.
So, I am joining the results of individual associated i.e sick and time.

Try
scope :with_name, -> (name) { where(mutationable: User.where(name: name)) }
edited. added )
Edit2:
My bad for my "wrong quick solution" due to not reading carefully enough, but I see the question has been edited and it is more clear now.
Knowing what you want to achieve I would suggest a different approach. Mutable should not be hardcoded looking at specific models because it will limit the flexibility provided by the polymorphic associations and will break 'Law of Demeter'
To get a collection of mutations of a particular set of XXXRegistrations I would do:
a. Use STI so the queriable XXXRegistrations, extend a Registration model. Add a scope to filter by user.
SickRegistration < Registration
b. Query Registration for user and then get the mutations
Registration.for_user(user).joins(:mutations)

Related

Rails field on join entity in has_many through relationship

I may be going about this the wrong way but after reading various SO articles and the Rails docs on associations and scopes, I'm not much wiser.
I have a many-to-may relationship expressed like so:
class User < ActiveRecord::Base
has_many :user_program_records
has_many :programs, through: :user_program_records
end
class Program < ActiveRecord::Base
has_many :user_program_records
has_many :users, through: :user_program_records
end
class UserProgramRecord < ActiveRecord::Base
belongs_to :user
belongs_to :program
# has a field "role"
end
The idea is that there are many users in the system and many programs. Programs have many users in them and users may belong to multiple programs. However - within a given program, a user can only have one role.
What I'd really like to be able to write is:
Program.first.users.first.role
and have that return me the role (which is just a String).
What's the cleanest way to do this? Basically, once I've scoped a user to a given program, how do I cleanly access fields on the relevant join table?
You are thinking about it slightly wrong:
user.role
Would be very ambiguous as a user can have different roles in different programs. Instead you need to think of the join entity as a thing of its own.
The easiest way is to select the join model directly:
program = Program.includes(:user_program_records, :users).first
role = program.user_program_records
.find_by(user: program.users.first)
.role
You can use stuff like association extensions and helper methods to make this a bit sexier.

Rails active record query with multiple associations

I have tables called users, orders, and delivery_times that are linked using the following relationship.
For table User:
belongs_to :orders
For table orders:
belongs_to :delivery_times
I want to write a query on table users using a condition on table delivery_times as shown:
User.includes(order: :delivery_time).where("delivery_times.start < ?",Time.now)
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "delivery_times"
However I get an error. Can I use the RoR ORM to make this query work using includes, even though I know there is a solution using joins?
You will need a join for this kind of query, since you need the joint knowledge of the delivery_times table and the users table.
What includes actually does is it decides between preload and eager_load automatically and tries to always take the better one. In you case it will do an eager_load; have a look into this article.
For the error you get, I guess it yould result from starting with Users and not User:
User.includes(order: :delivery_time).where("delivery_times.start < ?",Time.now)
Everything else seems correct to me.
The better definition of relations between your models would be this:
So your classes would look like this:
class User < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :user
has_one :delivery_time
end
class DeliveryTime < ActiveRecord::Base
belongs_to :order
end
The query you are making doesn't make any sense? What is the result that you are expecting?
If you want to get order that their delivery time is a specific time you can use scopes:
class Order < ActiveRecord::Base
belongs_to :user
has_one :delivery_time
scope :ready_to_deliver, includes(:delivery_time).where("delivery_time.start < ? ", Time.now)
end
Then you can get orders that are ready to deliver like this:
ready_orders = Order.ready_to_deliver

Rails 4 and referencing parent id in 'has_many' SQL

TL;DR: How do I use the ID of the respective parent object in a has_many SQL clause to find child objects?
Long version:
I have the following example code:
class Person < AR::Base
has_many :purchases, -> {
"SELECT * from purchases
WHERE purchase.seller_id = #{id}
OR purchase.buyer_id = #{id}"
}
This was migrated from Rails 3 which worked and looked like
has_many :purchases, :finder_sql => proc { #same SQL as above# }
I want to find all purchases associated with a Person object in one association, no matter whether the person was the one selling the object or buying it.
Update: I corrected the SQL, it was inside out. Sorry! Also: The association only needs to be read-only: I am never going to create records using this association, so using id twice should be OK. But I do want to be able to chain other scopes on it, e.g. #person.purchases.paid.last_5, so creating the sum of two associations in a method does not work (at least it didn't in Rails 3) since it doesn't return an AM::Relation but a simple Array.
When using this above definition in Rails 4.2, I get
> Person.first.purchases
undefined method `id' for #<Person::ActiveRecord_Relation:0x...>
The error is clear, but then how do I solve the problem?
Since this is only an example for much more complicated SQL code being used to express has_many relationships (e.g. multiple JOINS with subselects for performance), the question is:
How do I use the ID of the parent object in a has_many SQL clause?
I don't think your code will work at all. You are defining an association with two foreign keys ... that'd mean that in case you want to create a new Person from a present Purchase, what foreign key is to be used, seller_id or buyer_id? That just don't make sense.
In any case, the error you are getting is clear: you are calling a variable id which is not initialized in the block context of the SQL code.
A better approach to the problem I understand from your question would be to use associations in the following way, and then define a method that gives you all the persons, both buyers and sellers that a product has. Something like this:
class Purchase < ActiveRecord::Base
belongs_to :buyer, class_name: 'Person'
belongs_to :seller, class_name: 'Person'
def persons
ids = (buyer_ids + seller_ids).uniq
Person.where(ids: id)
end
end
class Person < ActiveRecord::Base
has_many :sold_purchases, class_name: 'Purchase', foreign_key: 'buyer_id'
has_many :buyed_purchases, class_name: 'Purchase', foreign_key: 'seller_id'
end
Im my approach, buyer_id and seller_id are purchase's attributes, not person's.
I may have not understood correctly, in that case please clarify.

Complex has_many :through with block conditions

I'm having a bit of difficulty figuring out how to do this in the "Rails" way, if it is even possible at all.
Background: I have a model Client, which has a has_many relationship called :users_and_managers, which is defined like so:
has_many :users_and_managers, -> do
Spree::User.joins(:roles).where( {spree_roles: {name: ["manager", "client"]}})
end, class_name: "Spree::User"
The model Users have a has_many relationship called credit_cards which is merely a simple has_many - belongs_to relationship (it is defined in the framework).
So in short, clients ---has many---> users ---has many---> credit_cards
The Goal: I would like to get all the credit cards created by users (as defined in the above relationship) that belong to this client.
The Problem: I thought I could achieve this using a has_many ... :through, which I defined like this:
has_many :credit_cards, through: :users_and_managers
Unfortunately, this generated an error in relation to the join with the roles table:
SQLite3::SQLException: no such column: spree_roles.name:
SELECT "spree_credit_cards".*
FROM "spree_credit_cards"
INNER JOIN "spree_users" ON "spree_credit_cards"."user_id" = "spree_users"."id"
WHERE "spree_users"."client_id" = 9 AND "spree_roles"."name" IN ('manager', 'client')
(Emphasis and formatting mine)
As you can see in the generated query, Rails seems to be ignoring the join(:roles) portion of the query I defined in the block of :users_and_managers, while still maintaining the where clause portion.
Current Solution: I can, of course, solve the problem by defining a plain 'ol method like so:
def credit_cards
Spree::CreditCard.where(user_id: self.users_and_managers.joins(:credit_cards))
end
But I feel there must be a more concise way of doing this, and I am rather confused about the source of the error message.
The Question: Does anyone know why the AR / Rails seems to be "selective" about which AR methods it will include in the query, and how can I get a collection of credit cards for all users and managers of this client using a has_many relationship, assuming it is possible at all?
The joins(:roles) is being ignored because that can't be appended to the ActiveRecord::Relation. You need to use direct AR methods in the block. Also, let's clean things up a bit:
class Spree::Role < ActiveRecord::Base
scope :clients_and_managers, -> { where(name: %w{client manager}) }
# a better scope name would be nice :)
end
class Client < ActiveRecord::Base
has_many :users,
class_name: "Spree::User",
foreign_key: :client_id
has_many :clients_and_managers_roles,
-> { merge(Spree::Role.clients_and_managers) },
through: :users,
source: :roles
has_many :clients_and_managers_credit_cards,
-> { joins(:clients_and_managers_roles) },
through: :users,
source: :credit_cards
end
With that setup, you should be able to do the following:
client = # find client according to your criteria
credit_card_ids = Client.
clients_and_managers_credit_cards.
where(clients: {id: client.id}).
pluck("DISTINCT spree_credit_cards.id")
credit_cards = Spree::CreditCard.where(id: credit_card_ids)
As you can see, that'll query the database twice. For querying it once, check out the following:
class Spree::CreditCard < ActiveRecord::Base
belongs_to :user # with Spree::User conditions, if necessary
end
credit_cards = Spree::CreditCard.
where(spree_users: {id: client.id}).
joins(user: :roles).
merge(Spree::Role.clients_and_managers)

has_and_belongs_to_many or polymorphic has_many :through?

I'm having trouble figuring out the right way to set this association up.
I have 3 models: Musicians, Bands and Managers. Each of those can have a certain "level" associated with them (stress, happiness, etc).
But I'm not sure how to correctly associated my Levels model with the other 3.
Do I need some sort of has_many :through that's polymorphic? And how on earth do I set that up? Is there some other type of associated I need?
If this is the case where you're defining attributes that can be assigned to a particular class of model, then you probably want to use a more conventional approach. Instead of kind use something like record_type and build a scope on that instead.
That way what you'd do to fetch them is create an accessor method, not a relationship, between any of your entities and this column. Something like this:
def levels
Level.for_record_type(self.class)
end
The for_record_type is a scope:
scope :for_record_type, lambda { |record_type|
where(:record_type => record_type.to_s)
}
There's no convention for associating models with classes instead of instances of other models.
Yep, this would be a polymorphic association:
class Level < ActiveRecord::Base
belongs_to :leveled, :polymorphic => true # maybe there's a better word than "leveled"
end
class Musician < ActiveRecord::Base
has_many :levels, :as => :leveled
end
You'll need to add a leveled_type and leveled_id to your Levels table.

Resources