Scope with "WHERE ... LIKE" on a related table - ruby-on-rails

I'm trying to get data from a Postgresql table (table1) filtered by a field (property) of an other related table (table2).
In pure SQL I would write the query like this:
SELECT * FROM table1 JOIN table2 USING(table2_id) WHERE table2.property LIKE 'query%'
This is working fine:
scope :my_scope, ->(query) { includes(:table2).where("table2.property": query) }
But what I really need is to filter with a LIKE operator rather than strict equality. However this is not working:
scope :my_scope, ->(query) { includes(:table2).where("table2.property LIKE ?", "#{query}%") }
As I am getting this error:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "table2" LINE 1: ...ble2" WHERE "table1"."user_id" = $1 AND (tabl... ^ : SELECT "table1".* FROM "table1" WHERE "table1"."user_id" = $1 AND (table2.property LIKE 'query%') ORDER BY last_used_at DESC
What am I doing wrong here?

.includes() usually runs 2 separate queries unless it can find that your conditions forces a single LEFT OUTER JOIN query, but it fails to do so in your case as the references are in a string (see this example).
You can force the single query behaviour by specifing .references(:table2):
scope :my_scope, ->(query) { includes(:table2)
.references(:table2)
.where("table2.property LIKE ?", "#{query}%") }
Or you can you can just use .eager_load():
scope :my_scope, ->(query) { eager_load(:table2)
.where("table2.property LIKE ?", "#{query}%") }

Try by this way, By adding [] in query.
scope :my_scope, ->(query) { includes(:table2).where(["table2.property LIKE (?)", "#{query}%"]) }
Also try by adding (?).

Related

Why does database functions break rails query plan on includes?

A plain call works as intended. The resulting SQL uses LEFT OUTER JOINs to link tables as desired.
> Subscription.includes(plan: { student: :person }).order('persons.name')
=> #<ActiveRecord::Relation ... >
If a function is inserted upon order clause, seems that rails goes off-track in its query plan as the resulting SQL does not do the tables linkage and, therefore, issues the error:
> Subscription.includes(plan: { student: :person }).order('unaccent(persons.name)')
=> ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: missing FROM-clause entry for table "persons")
LINE 1: ...subscriptions".* FROM "subscriptions" ORDER BY unaccent(persons.na...
^
: SELECT "subscriptions".* FROM "subscriptions" ORDER BY unaccent(persons.name) LIMIT $1
The same does not apply to joins that executes the command BUT using INNER JOINs as the table linkage (not exactly the intended relationship)
> Subscription.joins(plan: { student: :person }).order('unaccent(persons.name)')
=> #<ActiveRecord::Relation ... > # GOOD
As a newbie here, what am I missing?
(Re-written from my comment above)
You need to add .references(:persons) to the query.
Rails tries to be "lazy" and avoid performing unnecessary JOINs when using includes. The usage of this unaccent SQL function is throwing off the ActiveRecord query planner - so you need to be more explicit, thus forcing rails to perform the JOIN.
See the documentation on "conditions":
If you want to add conditions to your included models you’ll have to
explicitly reference them. For example:
User.includes(:posts).where('posts.name = ?', 'example')
Will throw an error, but this will work:
User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
Note that includes works with
association names while references needs the actual table name

Rails using includes in scope fails to insert table into select statment

rails 4.2
I have the following scopes on Orders in orders.rb
scope :sales, -> {where ("orders.order_ref <>'' and date_of_sale IS NOT NULL ")}
scope :with_salesman, -> { includes(:pipe_records).where('pipe_records.pipe_part_id=1 AND pipe_records.owner_id IS NOT NULL') }
I'm calling this from my orders controller with
#by_salesman=Order.sales.with_salesman
Throws an error
PG::UndefinedTable: ERROR: missing FROM-clause entry for table
the sql from the console is
SELECT "orders".* FROM "orders" WHERE (pipe_records.pipe_part_id=1 AND pipe_records.owner_id IS NOT NULL)):
I was using join in the scope and it works fine but it dosnt return the salesman data which I want in the recordset , what am I missing to get rails to insert the "select orders.,pipe_records. FROM..."
The includes command will only do a join if you specify a field from the included table in a where clause. A SQL string doesn't count though; you would need something more like the following:
scope :with_salesman, -> { includes(:pipe_records).where(pipe_records: {pipe_part_id: 1}).where("pipe_records.owner_id IS NOT NULL") }
Note that the first where condition causes the join, which allows the second to work. Depending on your Rails version, you may have access to .not which allows for:
scope :with_salesman, -> { includes(:pipe_records).where(pipe_records: {pipe_part_id: 1}).where.not(pipe_records: {owner_id: nil}) }
This is nicer, but only supported in Rails 4 onwards. Since the question specifies Rails 4.2, you should use the second example.

rails get a list of records by checking if association is approved

I've got a organization model and an organization_profile model. The organization_profile model has an approved column. I would like to be able to find all approved users by calling something like: Organization.approved. I found that the best way to handle this is probably through scope. However I can't seem to get it to work. This is what i am trying
scope :approved, -> {joins(:organization_profile).where('organization_profile.approved = ?', true) }
But then Organization.approved gives me all kinds of errors:
Organization Load (8.0ms) SELECT "organizations".* FROM "organizations" INNER JOIN "organization_profiles" ON "organization_profiles"."user_id" = "organizations"."id" WHERE (organization_profile.approved = 't')
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "organization_profile"
LINE 1: ...profiles"."user_id" = "organizations"."id" WHERE (organizati...
^
: SELECT "organizations".* FROM "organizations" INNER JOIN "organization_profiles" ON "organization_profiles"."user_id" = "organizations"."id" WHERE (organization_profile.approved = 't')
Can anyone tell me the correct code?
Your query is using organization_profile (singular) but your table name is organization_profiles (plural).
A slightly better way to do this (which also avoids using strings), is to turn the where clause into an Arel predicate (might not be the right word):
scope :approved, -> { joins(:organization_profile).where(OrganizationProfile.arel_table['approved'].eq(true)) }
The condition is SQL code that should reflect correctly the name of your tables which are always plural :
where('organization_profiles.approved = ?', true)

What is wrong with my scope below

I am writing an active record scope in rails. I have the following sql:
SELECT user.roll_number, first_name, last_name FROM user
INNER JOIN classes ON user.roll_number = classes.roll_number
AND classes.id IN #{id} ORDER BY(roll_number)
I wrote the scope in the user class, basically the tables are in the db2 database, so i wrote the scope as shown below
scope :by_id, lambda { |id|
{ select("user.roll_number, first_name, last_name")
.joins("INNER JOIN classes ON (user.roll_number = classes.roll_number \
and classes.id = #{id})")
}
}
Is there something wrong with the scope? I am getting "unexpected keyword end error"
Like said in the comment, you need to remove those {} braces. I'm guessing the error you are seeing is because this:
{ select("...") }
Is interpreted as an instantiation of a Hash, with a key of select("...") and no value. Ruby isn't happy with you.
Your follow-up:
What if i pass multiple ids? i am getting empty list, even though there are records for id 1 and id 2. i am passing id in the array form as id = [1,2]
You made a SQL lambda, so it's all on you to make that SQL work. A list of [1,2] is going to look like:
INNER JOIN classes ON (user.roll_number = classes.roll_number
and classes.id = [1,2])
Will that work on your SQL DB? Wouldn't you want to use and classes.id IN (1,2)?

problem: activerecord (rails3), chaining scopes with includes

In Rails3 there seems to be a problem when chaining two scopes (ActiveRelations) that each have a different include:
Consider these two scopes, both of which work fine on their own:
First scope:
scope :global_only, lambda { |user|
includes(:country)
.where("countries.area_id <> ?", user.area) }
Work.global_only(user) => (cut list of fields from SQL for legibility)
SELECT * FROM "works" LEFT OUTER JOIN "countries" ON "countries"."id" = "works"."country_id" WHERE (countries.area_id <> 3)
Now the second scope:
scope :not_belonging_to, lambda { |user|
includes(:participants)
.where("participants.user_id <> ? or participants.user_id is null", user) }
Work.not_belonging_to(user) => (cut list of fields from SQL for legibility)
SELECT * FROM "works" LEFT OUTER JOIN "participants" ON "participants"."work_id" = "works"."id" WHERE (participants.user_id <> 6 or participants.user_id is null)
So both of those work properly individually.
Now, chain them together:
Work.global_only(user).not_belonging_to(user)
The SQL:
SELECT (list of fields) FROM "works" LEFT OUTER JOIN "countries" ON "countries"."id" = "works"."country_id" WHERE (participants.user_id <> 6 or participants.user_id is null) AND (countries.area_id <> 3)
As you can see, the join from the second scope is ignored altogether. The SQL therefore fails on 'no such column 'participants.user_id'. If I chain the scopes in the reverse order, then the 'participants' join will be present and the 'countries' join will be lost. It's always the second join that is lost, it seems.
Does this look like a bug with ActiveRecord, or am I doing something wrong, or is this a "feature" :-)
(PS. Yes, I know, I can create a scope that joins both tables and it will correctly yield the result I want. I have that already. But I was trying to make smaller scopes than can be chained together in different ways, which is supposed to be the advantage of activerecord over straight sql.)
As a general rule, use :includes for eager-loading and :joins for conditions. In the second scope, the join SQL must be manually written because a left join is required.
That said, try this:
scope :global_only, lambda { |user|
joins(:country).
where(["countries.area_id != ?", user.area])
}
scope :not_belonging_to, lambda { |user|
joins("left join participants on participants = #{user.id}").
where("participants.id is null")
}
Work.global_only(user).not_belonging_to(user)

Resources