Rails using includes in scope fails to insert table into select statment - ruby-on-rails

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.

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

Query on ruby on Rails

How do you query on Ruby on Rails or translate this query on Ruby on Rails?
SELECT
orders.item_total,
orders.total,
payments.created_at,
payments.updated_at
FROM
public.payments,
public.orders,
public.line_items,
public.variants
WHERE
payments.order_id = orders.id AND
orders.id = line_items.order_id AND
This is working on Postgres but I'm new to RoR and it's giving me difficulty on querying this sample.
So far this is what I have.
Order.joins(:payments,:line_items,:variants).where(payments:{order_id: [Order.ids]}, orders:{id:LineItem.orders_id}).distinct.pluck(:email, :id, "payments.created_at", "payments.updated_at")
I have a lot of reference before asking a question here are the links.
How to combine two conditions in a where clause?
Rails PG::UndefinedTable: ERROR: missing FROM-clause entry for table
Rails ActiveRecord: Pluck from multiple tables with same column name
ActiveRecord find and only return selected columns
https://guides.rubyonrails.org/v5.2/active_record_querying.html
from all that link I produced this code that works for testing.
Spree::Order.joins(:payments,:line_items,:variants).where(id: [Spree::Payment.ids]).distinct.pluck(:email, :id)
but when I try to have multiple queries and pluck a specific column name from a different table it gives me an error.
Update
So I'm using Ransack to query I produced this code.
#search = Spree::Order.ransack(
orders_gt: params[:q][:created_at_gt],
orders_lt: params[:q][:created_at_lt],
payments_order_id_in: [Spree::Order.ids],
payments_state_eq: 'completed',
orders_id_in: [Spree::LineItem.all.pluck(:order_id)],
variants_id_in: [Spree::LineItem.ids]
)
#payment_report = #search.result
.includes(:payments, :line_items, :variants)
.joins(:line_items, :payments, :variants).select('payments.response_code, orders.number, payments.number')
I don't have error when I remove the select part and I need to get that specific column. Is there a way?
You just have to make a join between the tables and then select the columns you want
Spree::Order.joins(:payments, :line_items).pluck("spree_orders.total, spree_orders.item_total, spree_payments.created_at, spree_payments.updated_at")
or
Spree::Order.joins(:payments, :line_items).select("spree_orders.total, spree_orders.item_total, spree_payments.created_at, spree_payments.updated_at")
That is equivalent to this query
SELECT spree_orders.total,
spree_orders.item_total,
spree_payments.created_at,
spree_payments.updated_at
FROM "spree_orders"
LEFT OUTER JOIN "spree_payments" ON "spree_payments"."order_id" = "spree_orders"."id"
LEFT OUTER JOIN "spree_line_items" ON "spree_line_items"."order_id" = "spree_orders"."id"
You can use select_all method.This method will return an instance of ActiveRecord::Result class and calling to_hash on this object would return you an array of hashes where each hash indicates a record.
Order.connection.select_all("SELECT
orders.item_total,
orders.total,
payments.created_at,
payments.updated_at
FROM
public.payments,
public.orders,
public.line_items,
public.variants
WHERE
payments.order_id = orders.id AND
orders.id = line_items.order_id").to_hash

Rails 5 scope issue

In rails 5 with Postgresql, I'm writing a scope to select Status items by date. My model is:
class Status < ApplicationRecord
scope :in_month, ->(date){where("status_date = ?", date) if date.present?}
end
However, in rails console, I'm getting the following error when trying to use the scope.
```
2.4.0 :010 > Status.in_month("2018-06-01")
Status Load (11.7ms) SELECT "statuses".* FROM "statuses" WHERE (status.status_date = '2018-06-01') LIMIT $1 [["LIMIT", 11]]
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "status"
LINE 1: SELECT "statuses".* FROM "statuses" WHERE (status.status_da...
^
: SELECT "statuses".* FROM "statuses" WHERE (status.status_date = '2018-06-01') LIMIT $1
Note that for some reason, it's trying to access table "status", not "statuses".
What the heck am I doing wrong? This is pretty much straight out of the RailsGuide.
EDIT & "fix"
Humph... apparently the Rails console was in a bad state. I restarted the console after the comments below and found that my query worked perfectly. Note that the quotes are required for this format of the WHERE clause and that statuses is a correct pluralization of status.
And after restarting, the where(status_date: date) format described by praga2050 worked, as did the where("statuses.status_date = ?", date) suggested by Kedarnag Mukanahallipatna.
Lesson learned: reload! doesn't reload changes in models; you have to restart the console for Model changes to be recognized. See this SO answer: https://stackoverflow.com/a/5428421/446464
I think you missed the point of naming conventions in rails.
Model class names are singular, and will map automatically to the plural database table name.
According to this, you can change your model name to statuses.
Or set a table name to use in the rails model:
class Status < ApplicationRecord
self.table_name = 'status'
scope :in_month, ->(date) { where(status_date: date) if date.present? }
end
I am not sure why you are putting string inside the Where clause
scope :in_month, ->(date){where("status_date = ?", date) if date.present?}
Should be change like below.
Below one should work for you.
scope :in_month, ->(date){where(status_date: date) if date.present?}

Using joins to query by attribute on associated recored

I currently have this horribly written query:
membership_ids = User.where(skip_membership_renewal: true).includes(:memberships).map(&:membership_ids).flatten
Memberships.where(id: membership_ids)
I have been trying to use joins so that I can just make one query.
Membership.includes(:user).where("user.skip_membership_renewal", true)
However, this doesn't work since I keep getting the error: ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR.
My relationship is:
User has_many :memberships
Membership belongs_to :user
What am I doing incorrectly?
You just have a pluralization error. In Rails, you define models as singular (User) and the database table is pluralized (users).
Membership.includes(:user).where("users.skip_membership_renewal" => true)
That said, you don't need to resort to using SQL literals for such a simple case. There are a bunch of other ways of assembling this query, like the scope option David Aldridge suggested, or either of these:
non_renewing_users = User.where(skip_membership_renewal: true)
Membership.joins(:user).merge(non_renewing_users)
Membership.where(user: non_renewing_users)
What's more is that these both only execute a single SQL query for most adapters because they use subqueries:
SELECT "memberships".*
FROM "memberships"
WHERE "memberships"."user_id" IN (
SELECT "users"."id" FROM "users"
WHERE "users"."skip_membership_renewal" = true
)
You can probably aim to use:
Membership.where(:user => User.skip_membership_renewal)
Add a scope onto User ...
def self.skip_membership_renewal
where(skip_membership_renewal: true)
end
You should find that it runs as a single query.

Scope with "WHERE ... LIKE" on a related table

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 (?).

Resources