Nested include with where statements using ActiveRecord - ruby-on-rails

the structure of my app is as follows:
c
lass Project < ActiveRecord::Base
has_and_belongs_to_many :team_members
has_one :legal_contract
has_many :documents
end
class ProjectsTeamMember < ActiveRecord::Base
belongs_to :project
belongs_to :team_member
end
class LegalContract < ActiveRecord::Base
belongs_to :project
end
class TeamMember < ActiveRecord::Base
has_many :projects_team_members
has_many :projects, through: :projects_team_members
has_and_belongs_to_many :projects
end
I am trying to find all team_members who are associated with legal_contract with one of ids from an array. Trying the includes method:
team_members = TeamMember.includes( {projects_team_members: [ { project: :legal_contract} ] } )
I wonder now how do I narrow it down now to pull only those tmembers and not all. I tried
arry= [1,3,5]
team_members = TeamMember.includes( {projects_team_members: [ { project: :legal_contract} ] }).where('legal_contract.id IN', arry)
but getting an error
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: zero-length delimited identifier at or near """"
LINE 1: ...s"."updated_at" AS t0_r6, "projects_team_members"."" AS t1_r...
Does anyone know? Thanks
**UPDATE**
Thanks to Karlingen
TeamMember.joins(:projects => :legal_contract).where(legal_contracts: { id: [1,3,5] })

You need to accomplish this through a join like so:
TeamMember.joins(:projects => :legal_contract).where(legal_contracts: { id: [1,3,5] })
First you join the projects table and its legal contract. And then you search for all legal contracts that has the given ids.
Do note that the key in the Where clause is written in plural since it is the name of the table.

You need to set a primary key for projects_team_members, are you sure you have one?
It appears as though Rails is creating invalid syntax when trying to find a projects_team_members:
"projects_team_members".""

Related

Write a scope in rails to fetch simaliar objects by categories

I need help to write a scope to fetch similar businesses.
My Business model has many categories. So I want a scope to query all businesses with similar categories except the present record.
business.rb
class Business < ApplicationRecord
........
has_many :business_categories
has_many :categories , through: :business_categories
scope :similar_businesses, -> (current_business_id, category_name) {joins(:categories).where(categories:{name: category_name}).where.not(id: current_business_id, name: category_name)}
........
category.rb
class Category < ApplicationRecord
has_many :business_categories
has_many :businesses,, through: :business_categories
validates :name ,presence: true
business_controller.rb
def show
.....
#business = Business.friendly.find(params[:id])
#similar_businesses = Business.similar_businesses(#business.id, #business.categories.pluck(:name))
.....
business/show.html.erb
<%#similar_businesses.each do |business|%>
<%=business.business_name%> <br>
<%end%>
With this get up. I have the PG error
PG::UndefinedFunction: ERROR: operator does not exist: character varying = bigint
LINE 1: ...tegories"."category_id" WHERE "categories"."name" IN (SELECT...
I assume that your Category model has a category_name(or something similar) attribute and by similar categories you meant categories having the same category_name. This is how you can get all the businesses with similar category:
scope :businesses_by_category, ->(category_name) { joins(:categories).where(categories: { name: category_name }) }

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') }

Trouble with simple Rails nested associations where clause for parent

I have the following models:
class Business < ApplicationRecord
has_many :shopping_trips
end
class ShoppingTrip < ApplicationRecord
belongs_to :business
has_many :purchases
end
class Purchase < ApplicationRecord
belongs_to :shopping_trip
end
So a Business can have many shopping trips, and each of these shopping trips can have many purchases.
I am trying to run a simple query on the Purchase table to find purchases that belong to a particular business. So I'm writing this:
purchases = Purchase.joins(:shopping_trip => :business ).where(:shopping_trip => {:business_id => 1})
Unfortunately it's not working. I get the following error:
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "shopping_trip"
LINE 1: ...sses"."id" = "shopping_trips"."business_id" WHERE "shopping_...
^
: SELECT "purchases".* FROM "purchases" INNER JOIN "shopping_trips" ON "shopping_trips"."id" = "purchases"."shopping_trip_id" INNER JOIN "businesses" ON "businesses"."id" = "shopping_trips"."business_id" WHERE "shopping_trip"."business_id" = $1
The join looks about right but the where clause seems to fail.
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing
FROM-clause entry for table "shopping_trip"
You need to specify table name not the association name inside the where. So shopping_trip should be shopping_trips
purchases = Purchase.joins(:shopping_trip => :business ).where(:shopping_trips => {:business_id => 1})
A better solution is to set up indirect associations so that you can query through the join model without manually joining:
class Business < ApplicationRecord
has_many :shopping_trips
has_many :purchases, through: :shopping_trips
end
class ShoppingTrip < ApplicationRecord
belongs_to :business
has_many :purchases
end
class Purchase < ApplicationRecord
belongs_to :shopping_trip
has_one :business, through: :shopping_trip
end
You can now query from either side:
#business = Business.eager_load(:purchases).find(1)
#purchases = #business.purchases
# or
#purchases = Purchase.eager_load(:buisness).where(businesses: { id: 1 })
Check This...
all_purchase = Purchase.all
all_purchase.each do |each_purchase|
each_purchase.shopping_trip.business
end

Rails association with multiple foreign keys

I want to be able to use two columns on one table to define a relationship. So using a task app as an example.
Attempt 1:
class User < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
So then Task.create(owner_id:1, assignee_id: 2)
This allows me to perform Task.first.owner which returns user one and Task.first.assignee which returns user two but User.first.task returns nothing. Which is because task doesn't belong to a user, they belong to owner and assignee. So,
Attempt 2:
class User < ActiveRecord::Base
has_many :tasks, foreign_key: [:owner_id, :assignee_id]
end
class Task < ActiveRecord::Base
belongs_to :user
end
That just fails altogether as two foreign keys don't seem to be supported.
So what I want is to be able to say User.tasks and get both the users owned and assigned tasks.
Basically somehow build a relationship that would equal a query of Task.where(owner_id || assignee_id == 1)
Is that possible?
Update
I'm not looking to use finder_sql, but this issue's unaccepted answer looks to be close to what I want: Rails - Multiple Index Key Association
So this method would look like this,
Attempt 3:
class Task < ActiveRecord::Base
def self.by_person(person)
where("assignee_id => :person_id OR owner_id => :person_id", :person_id => person.id
end
end
class Person < ActiveRecord::Base
def tasks
Task.by_person(self)
end
end
Though I can get it to work in Rails 4, I keep getting the following error:
ActiveRecord::PreparedStatementInvalid: missing value for :owner_id in :donor_id => :person_id OR assignee_id => :person_id
TL;DR
class User < ActiveRecord::Base
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
end
Remove has_many :tasks in User class.
Using has_many :tasks doesn't make sense at all as we do not have any column named user_id in table tasks.
What I did to solve the issue in my case is:
class User < ActiveRecord::Base
has_many :owned_tasks, class_name: "Task", foreign_key: "owner_id"
has_many :assigned_tasks, class_name: "Task", foreign_key: "assignee_id"
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User"
belongs_to :assignee, class_name: "User"
# Mentioning `foreign_keys` is not necessary in this class, since
# we've already mentioned `belongs_to :owner`, and Rails will anticipate
# foreign_keys automatically. Thanks to #jeffdill2 for mentioning this thing
# in the comment.
end
This way, you can call User.first.assigned_tasks as well as User.first.owned_tasks.
Now, you can define a method called tasks that returns the combination of assigned_tasks and owned_tasks.
That could be a good solution as far the readability goes, but from performance point of view, it wouldn't be that much good as now, in order to get the tasks, two queries will be issued instead of once, and then, the result of those two queries need to be joined as well.
So in order to get the tasks that belong to a user, we would define a custom tasks method in User class in the following way:
def tasks
Task.where("owner_id = ? OR assigneed_id = ?", self.id, self.id)
end
This way, it will fetch all the results in one single query, and we wouldn't have to merge or combine any results.
Extending upon #dre-hh's answer above, which I found no longer works as expected in Rails 5. It appears Rails 5 now includes a default where clause to the effect of WHERE tasks.user_id = ?, which fails as there is no user_id column in this scenario.
I've found it is still possible to get it working with a has_many association, you just need to unscope this additional where clause added by Rails.
class User < ApplicationRecord
has_many :tasks, ->(user) {
unscope(:where).where(owner: user).or(where(assignee: user)
}
end
Rails 5:
you need to unscope the default where clause
see #Dwight answer if you still want a has_many associaiton.
Though User.joins(:tasks) gives me
ArgumentError: The association scope 'tasks' is instance dependent (the scope block takes an argument). Preloading instance dependent scopes is not supported.
As it is no longer possible you can use #Arslan Ali solution as well.
Rails 4:
class User < ActiveRecord::Base
has_many :tasks, ->(user){ where("tasks.owner_id = :user_id OR tasks.assignee_id = :user_id", user_id: user.id) }
end
Update1:
Regarding #JonathanSimmons comment
Having to pass the user object into the scope on the User model seems like a backwards approach
You don't have to pass the user model to this scope.
The current user instance is passed automatically to this lambda.
Call it like this:
user = User.find(9001)
user.tasks
Update2:
if possible could you expand this answer to explain what's happening? I'd like to understand it better so I can implement something similar. thanks
Calling has_many :tasks on ActiveRecord class will store a lambda function in some class variable and is just a fancy way to generate a tasks method on its object, which will call this lambda. The generated method would look similar to following pseudocode:
class User
def tasks
#define join query
query = self.class.joins('tasks ON ...')
#execute tasks_lambda on the query instance and pass self to the lambda
query.instance_exec(self, self.class.tasks_lambda)
end
end
I worked out a solution for this. I'm open to any pointers on how I can make this better.
class User < ActiveRecord::Base
def tasks
Task.by_person(self.id)
end
end
class Task < ActiveRecord::Base
scope :completed, -> { where(completed: true) }
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
def self.by_person(user_id)
where("owner_id = :person_id OR assignee_id = :person_id", person_id: user_id)
end
end
This basically overrides the has_many association but still returns the ActiveRecord::Relation object I was looking for.
So now I can do something like this:
User.first.tasks.completed and the result is all completed task owned or assigned to the first user.
Since Rails 5 you can also do that which is the ActiveRecord safer way:
def tasks
Task.where(owner: self).or(Task.where(assignee: self))
end
My answer to Associations and (multiple) foreign keys in rails (3.2) : how to describe them in the model, and write up migrations is just for you!
As for your code,here are my modifications
class User < ActiveRecord::Base
has_many :tasks, ->(user) { unscope(where: :user_id).where("owner_id = ? OR assignee_id = ?", user.id, user.id) }, class_name: 'Task'
end
class Task < ActiveRecord::Base
belongs_to :owner, class_name: "User", foreign_key: "owner_id"
belongs_to :assignee, class_name: "User", foreign_key: "assignee_id"
end
Warning:
If you are using RailsAdmin and need to create new record or edit existing record,please don't do what I've suggested.Because this hack will cause problem when you do something like this:
current_user.tasks.build(params)
The reason is that rails will try to use current_user.id to fill task.user_id,only to find that there is nothing like user_id.
So,consider my hack method as an way outside the box,but don't do that.
Better way is using polymorphic association:
task.rb
class Task < ActiveRecord::Base
belongs_to :taskable, polymorphic: true
end
assigned_task.rb
class AssignedTask < Task
end
owned_task.rb
class OwnedTask < Task
end
user.rb
class User < ActiveRecord::Base
has_many :assigned_tasks, as: :taskable, dependent: :destroy
has_many :owned_tasks, as: :taskable, dependent: :destroy
end
In result, we can use it so:
new_user = User.create(...)
AssignedTask.create(taskable: new_user, ...)
OwnedTask.create(taskable: new_user, ...)
pp user.assigned_tasks
pp user.owned_tasks

Rails 4 - scope with has_one instead of attribute

I have the following two models:
class Person < ActiveRecord::Base
has_one :archive, :dependent => :destroy
class Archive < ActiveRecord::Base
belongs_to :person
Archive holds person_id.
I need to have a scope Person.with_archive. Since Person has no db column of archive_id, the only solution I found is:
def self.has_archive
res = Array.new
self.all.each do |i|
res << i if !i.archive.nil?
end
res
end
The problem is that I get an Array instead of a relationship, which limits me to chain other scopes.
Any nice and clean solution for that?? Thanks!
Try this:
scope :with_archive, -> { joins(:archive) }
This should return only the Persons with an archive, because joins uses an INNER JOIN statement.

Resources