Referencing different names in joins with scopes - ruby-on-rails

I have a polymorphic association and sometimes I want to preload it's associations.
When I left join the model, my WHERE filters get lost because they don't referenced the named association.
SELECT COUNT(*) FROM `companies` LEFT OUTER JOIN `key_values` `latest_information` ON `latest_information`.`attachment_id` = `companies`.`id` AND `latest_information`.`attachment_type` = 'Company' AND `key_values`.`name` = 'latest_information' WHERE `latest_information`.`id` IS NOT NULL
# => ActiveRecord::StatementInvalid: Mysql2::Error: symbol key_values.`name` not found
This is the query that is generated but it's invalid due to the key_values.name not being referenced.
Here's what my model looks like:
class Company < LeadRecord
has_many :key_values, as: :attachment, dependent: :delete_all
has_one :latest_information,
-> { KeyValue.latest('latest_information') },
class_name: KeyValue.to_s,
as: :attachment
end
class KeyValue < LeadRecord
belongs_to :attachment, polymorphic: true
def self.latest(name)
order(created_at: :desc).where(name: name) # This is the source of the error
end
end
I can probably fix this by passing addition parameters to self.latest such as the association name but I want to know if there's a better Rails way to do this.

In the interim I have solved this by making this change on KeyValue.
# key_value.rb
def self.latest(name, association_name = 'key_values')
order(created_at: :desc).where("#{association_name}.name = ?", name)
end
# company.rb
has_one association_name,
-> {
KeyValue.latest(
method_name.to_s,
association_name.to_s,
)
},
class_name: KeyValue.to_s,
as: :attachment

Related

rails_admin search through nested belongs_to association

I have a model:
class TenantReference < ActiveRecord::Base
include TenantReferenceAdmin
belongs_to :tenant, inverse_of: :reference
default_scope { eager_load(:tenant) }
end
and a Tenant model:
class Tenant < ActiveRecord::Base
default_scope { eager_load(:user) }
belongs_to :user
end
and a User model:
class User < ActiveRecord::Base
has_many :tenants, :foreign_key => :user_id, class_name: 'Tenant'
end
and finally a TenantReferenceAdmin rails admin file:
module TenantReferenceAdmin
extend ActiveSupport::Concern
included do
rails_admin do
list do
field :tenant do
filterable true
queryable true
searchable [ :first_name, :last_name]
end
...
what I'm trying to achieve is that in the tenantreference admin page the user can search TenantReference objects by the first_name or last_name of the user through their Tenant reference.
This configuration is producing a postgresql query like:
SELECT "tenant_references"."id" AS t0_r0, .... "tenants"."id" AS t1_r0, ......
FROM "tenant_references" LEFT OUTER JOIN "tenants" ON "tenants"."id" = "tenant_references"."tenant_id"
WHERE ((tenants.first_name ILIKE '%query%')
OR (tenants.last_name ILIKE '%query%')
ORDER BY tenant_references.id desc LIMIT 20 OFFSET 0)
which doesn't work, because first/last_name are actually fields of user, not of tenant.
how could I fix this?
Thanks,
The problem seems to be that rails admin only adds JOIN to the query if the current model has a direct link (has_many, has_one ...) to the other model to search in. and it joins it if the corresponding field is marked as queryable true.
so I changed adding to the references model this line:
has_one :user, through: :tenant
I then created an invisible list field:
field :user do
visible false
queryable true
searchable [{User => :first_name}, {User => :last_name}]
end
that can be searched upon, but it's not shown in the list.
this has solved the issue, I don't consider this ideal as I had to modify my model in order to be able to perform rails_admin search, when rails_admin could have handled this situation without changing the code. But for now I can live with this

Rails 5: How to allow model create through when polymorphic reference also carries distinct association

I have model with polymorhphic reference to two other models. I've also included distinct references per this article eager load polymorphic so I can still do model-specific queries as part of my .where clause. My queries work so I can search for scores doing Score.where(athlete: {foo}), however, when I try to do a .create, I get an error because the distinct reference alias seems to be blinding Rails of my polymorphic reference during validation.
Given that athletes can compete individually and as part of a team:
class Athlete < ApplicationRecord
has_many :scores, as: :scoreable, dependent: :destroy
end
class Team < ApplicationRecord
has_many :scores, as: :scoreable, dependent: :destroy
end
class Score < ApplicationRecord
belongs_to :scoreable, polymorphic: true
belongs_to :athlete, -> { where(scores: {scoreable_type: 'Athlete'}) }, foreign_key: 'scoreable_id'
belongs_to :team, -> { where(scores: {scoreable_type: 'Team'}) }, foreign_key: 'scoreable_id'
def athlete
return unless scoreable_type == "Athlete"
super
end
def team
return unless scoreable_type == "Team"
super
end
end
When I try to do:
Athlete.first.scores.create(score: 5)
...or...
Score.create(score: 5, scoreable_id: Athlete.first.id, scoreable_type: "Athlete")
I get the error:
ActiveRecord::StatementInvalid (SQLite3::SQLException: no such column: scores.scoreable_type
Thanks!
#blazpie, using your scoping suggestion worked for me.
"those scoped belongs_to can be easily substituted by scopes in Score: scope :for_teams, -> { where(scorable_type: 'Team') }

Rails scope query on model

I have a model with this relationship:
class Plan < ApplicationRecord
has_many :enrollment_plans
has_many :enrollments, through: :enrollment_plans
...
end
EDIT Here is the join table:
class EnrollmentPlan < ApplicationRecord
belongs_to :enrollment, required: true
belongs_to :plan, required: true
end
I tried to throw this scope on the model:
scope :for_enrollment, -> (enrollment) { where('enrollments.enrollment_id = ?', enrollment.id) }
but I get the following error. I am trying to figure out why I can't do this query. What do I need to change it to?
pry(main)> Plan.for_enrollment(Enrollment.last).to_a
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "enrollments"
LINE 1: SELECT "plans".* FROM "plans" WHERE (enrollments.enrollment_...
^
ActiveRecord does not include associations by default, you need to add enrollments to query by hand. Try:
scope :for_enrollment, -> (enrollment) do
joins(:enrollments).
where('enrollments.id = ?', enrollment.id)
end
This scope will make query with joins of three tables: plans, enrollment_plans and enrollments. You may do the same logic with two tables query:
scope :for_enrollment, -> (enrollment) do
joins(:enrollment_plans).
where('enrollment_plans.enrollment_id = ?', enrollment.id)
end

ActiveRecord has_many with custom foreign key sql expression

In ActiveRecord, a has_many relationship uses a foreign key column to load the association. So:
class Person < ActiveRecord::Base
has_many :messages
end
class Messages < ActiveRecord::Base
belongs_to :person
end
Person.limit(10).includes(:messages)
# select * from persons limit 10;
# select messages.* from messages where person_id in (1, 2, 3...)
I have a case (and I've seen other people ask for this as well) where I don't want Rails to automatically append the foreign key check to the where clause. Instead, I might want something like:
class Person < ActiveRecord::Base
has_many :messages,
:foreign_key => false,
:conditions => proc { ["person_id is null or person_id = ?", self.id] }
end
Person.limit(10).includes(:messages)
# select messages.* from messages where person_id is null or person_id in (1, 2, 3...)
How can I do this? To sum up, I don't want ActiveRecord automatically appending the foreign key in the WHERE clause, I want to be able to specify the expression it uses for the association.
I don't want to do this:
class Person < ActiveRecord::Base
def messages
Message.where("person_id is null or person_id = #{ self.id }")
end
end
as that would break eager loading, as far as I know.
I also don't want to use the finder_sql option to has_many, as that would break things like person.messages.where(:id => 1) or Person.limit(10).includes(:messages => :image)
Something like this works for me on Rails 3.2.1:
class Person < ActiveRecord::Base
has_many :messages, :finder_sql => lambda{ "SELECT * FROM messages WHERE messages.person_id=#{id} OR messages.person_id IS NULL" }
end
# person = Person.includes(:messages).first
# person.messages.loaded? #=> true

update_all through an association

I am trying to use update_all through an association, and i am getting mysql errors, anyone know why please?
class Basket < ActiveRecord::Base
has_many :basket_items
has_many :articles, :through => :basket_items
def activate_articles
articles.update_all :active => true
end
end
class BasketItem < ActiveRecord::Base
belongs_to :basket
belongs_to :item
belongs_to :article
end
Mysql::Error: Unknown column 'basket_items.basket_id' in 'where clause': UPDATE `articles` SET `active` = 1 WHERE ((`basket_items`.basket_id = 114))
http://dev.rubyonrails.org/ticket/5353
Looks like there was a problem with n-n associations using has_many :through and using update all. Nothing seems to have been done.
1-n associations do appear to work.
Bug?
dev.rubyonrails moved it's tickets to github's issue tracker. Here is the moved link: https://github.com/rails/rails/issues/522
#nolman posted this help on the ticket
#daicoden and I at #square were pairing on this and we were able to put something together along the lines of:
class Comment
class << self
def trash_all
sql = "UPDATE #{quoted_table_name} "
add_joins!(sql, {})
sql << "SET #{sanitize_sql_for_assignment({:trashed => true})} "
add_conditions!(sql, {})
connection.execute(sql)
end
end
end
Now you can call todolist.comments(:conditions => {:trashed => false}).trash_all
This results in the following SQL:
UPDATE `comments` INNER JOIN `todos` ON `todos`.id = `comments`.todo_id SET `trashed` = 1 WHERE (`comments`.`trashed` = 0 AND `todos`.`todolist_id` = 968316918)
Hope this helps!

Resources