Ruby on Rails - order not working with distinct.pluck - ruby-on-rails

app/model/line_item.rb
class LineItem < ApplicationRecord
default_scope { order(:order_date, :line_item_index) }
scope :sorted, -> { order(:order_date, :line_item_index) }
scope :open_order_names, -> { distinct.pluck(:order_name) }
end
What I have tried:
LineItem.open_order_names # Way 1
LineItem.sorted.open_order_names # Way 2
LineItem.open_order_names.sorted # Way 3
But I am always getting this error.
ActiveRecord::StatementInvalid (PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
LINE 1: ...ne_items"."order_name" FROM "line_items" ORDER BY "line_item...
^
):
Anyone can help me?

The issue is that you need to specify how they should be distinct, the following should work for you, the select may not be needed.
scope :open_order_names, -> { select(:order_name).distinct(:order_name).pluck(:order_name) }

So it's database restriction. For example we have users table with (id, email).
You can do:
SELECT DISTINCT "users"."email" FROM "users"
or
SELECT "users"."email" FROM "users" ORDER BY "users"."id" ASC
but can not:
SELECT DISTINCT "users"."email" FROM "users" ORDER BY "users"."id" ASC
i.e. you can not order by column which abcent in the SELECT part of query if you use the DISTINCT.
As mentioned above the
scope :open_order_names, -> { select(:order_name).distinct(:order_name).pluck(:order_name) }
could be nice solution.

Related

Rails - pass argument to chained scopes

I'm trying to figure out how to chain multiple scopes together. What I'm trying to do is have a search box that will pass params[:user_search] to the controller which calls the by_keyword scope in the user model. the by_keyword scope is working as i have it now, but i would like to make it also search all of the other scopes i have as well. So essentially the by_keyword scope should query all scopes for whatever keyword a user entered.
in my users_controller index action
if params[:user_search].present?
#users = #users.by_keyword(params[:user_search])
end
in my user model i have
scope :by_keyword, -> (keyword) { where('experience LIKE ? OR current_job_title LIKE ?', "%#{keyword}%", "%#{keyword}%" ).order(updated_at: :desc) if keyword.present? }
i would like to find a way to chain all of these to the by_keyword scope
# these scopes call child classes of User such as skills, languages, patents, etc...
scope :by_skill, -> (sk) { joins(:skills).distinct.where( 'skills.name LIKE ?', "%#{sk}%" ).order(updated_at: :desc) if sk.present? }
scope :by_language, -> (lang) { joins(:languages).distinct.where( 'languages.language LIKE ?', "%#{lang}%" ).order(updated_at: :desc) if lang.present? }
scope :by_certification_or_cert_authority, -> (cert) { joins(:certifications).distinct.where( 'certifications.certification_name LIKE ? OR certifications.certification_authority LIKE ?', "%#{cert}%", "%#{cert}%" ).order(updated_at: :desc) if cert.present? }
scope :by_education_level, -> (ed) { joins(:qualifications).distinct.where( 'qualifications.education LIKE ?', "%#{ed}%" ).order(updated_at: :desc) if ed.present? }
scope :by_university_major, -> (maj) { joins(:qualifications).distinct.where( 'qualifications.university_major LIKE ?', "%#{maj}%" ).order(updated_at: :desc) if maj.present? }
i read over http://guides.rubyonrails.org/active_record_querying.html#scopes
and the example they give is this, but I'm not sure how to do this with more than just 2 scopes chained together.
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
scope :published_and_commented, -> { published.where("comments_count > 0") }
end
You can do it by make a function in user controller which call send and give it an array like here
In user model
def self.send_chain(methods)
methods.inject(self, :send)
end
Then call it like
User.send_chain(["by_skill", "by_language"])
If you have to send params you can do it like this:
scopes = ["by_skill", "by_language"]
parameters = ["clever", "English"]
result = []
scopes.each_with_index do |scope, index|
result = result + User.send(scope, parameters[index])
end
Hope this helps.
Try this:
scope :all_clild, -> (val) { |val| by_skill(val).or.by_language(val).or.by_certification_or_cert_authority(val).........# all other scopes}
merged_scope = by_keyword.merge(all_clild)
I was able to pass an argument to the scopes like this
scope :by_keyword, -> (k) { by_skill(k) | by_language(k) | by_certification_or_cert_authority(k) | by_education_level(k) | by_university_major(k)}
I'm not sure if this is really considered "chaining" them though. I'm guessing there is probably a better way to do this, if there is please let me know.
it's making a ton of queries so I'm not sure if this is advisable performance wise to even have this many scopes being called the way they are. This is the result when searching on the term "CCNA"
User Load (0.5ms) SELECT DISTINCT "users".* FROM "users" INNER JOIN "skills" ON "skills"."user_id" = "users"."id" WHERE (skills.name LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
User Load (0.5ms) SELECT DISTINCT "users".* FROM "users" INNER JOIN "languages" ON "languages"."user_id" = "users"."id" WHERE (languages.language LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
User Load (0.6ms) SELECT DISTINCT "users".* FROM "users" INNER JOIN "certifications" ON "certifications"."user_id" = "users"."id" WHERE (certifications.certification_name LIKE '%CCNA%' OR certifications.certification_authority LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
User Load (0.5ms) SELECT DISTINCT "users".* FROM "users" INNER JOIN "qualifications" ON "qualifications"."user_id" = "users"."id" WHERE (qualifications.education LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
User Load (0.5ms) SELECT DISTINCT "users".* FROM "users" INNER JOIN "qualifications" ON "qualifications"."user_id" = "users"."id" WHERE (qualifications.university_major LIKE '%CCNA%') ORDER BY "users"."updated_at" DESC
Also from a security standpoint I'm not sure if it's all that good to allow the user to enter one query that touches so many tables.

How to unscope default_scope in join/eager_load?

I have two models:
class User
default_scope -> { where(deleted_at: nil) }
end
class Order
belongs_to :user
end
And I want to get orders with deleted or not deleted users:
Order.joins(:user).merge(User.unscoped)
Order.joins(:user).merge(User.unscope(where: :deleted_at))
# SELECT "orders".* FROM "orders"
# INNER JOIN "users" ON "users"."id" = "orders"."user_id" AND "users"."deleted_at" IS NULL
# ORDER BY "orders"."id" DESC LIMIT 1
Order.eager_load(:user).merge(User.unscoped)
Order.eager_load(:user).merge(User.unscope(where: :deleted_at))
# SELECT "orders"."id" AS t0_r0, "orders"."user_id" AS t0_r1,
# "users"."id" AS t1_r0, "users"."deleted_at" AS t1_r1 FROM "orders"
# LEFT OUTER JOIN "users" ON "users"."id" = "orders"."user_id" AND "users"."deleted_at" IS NULL
# ORDER BY "orders"."id" DESC LIMIT 1
None of these work.
Every query adds "AND "users"."deleted_at" IS NULL" into join statement.
Nothing changes if I specify association scope:
class Order
belongs_to :user, -> { unscoped }
end
However includes works as expected:
Order.includes(:user).merge(User.unscoped).last
# SELECT "orders".* FROM "orders" ORDER BY "orders"."id" DESC LIMIT 1
# SELECT "users".* FROM "users" WHERE "users"."id" = 1054
How can I make rails to unscope association in a join?
You can try like this. It works in Rails >= 3
User.unscoped { Order.joins(:user) }
I solved this issue by writing join query manually. For your case it should look like:
Order.joins('INNER JOIN users ON users.id=orders.user_id')
Although Order.includes(:user).merge(User.unscoped)solution, that you found, looks a bit nicer, unless you really want to have only one query
You can define another association on same model to target specifically those deleted users:
This example works assuming you use act-as-paranoid to handle soft-deletion.
class Order
belongs_to :user
belongs_to :all_user, -> { with_deleted }, foreign_key: 'user_id'
end
Then choose your weapons:
Order.includes(:user).pluck(:email) # Only non soft-deleted emails
Order.includes(:all_user).pluck(:email) # All emails including where deleted_at is null
```

Adding a scope to a has_many through association in Rails

I have a Project and User models joined by a Membership model. I want to retrieve a project's members except one user.
project.members.where.not(id: current_user)
Like this answer, I want to use a scope:
class User < ActiveRecord::Base
scope :except, ->(user) { where.not(id: user) }
end
But this doesn't work.
p.members.except(User.find(1))
User Load (1.0ms)
SELECT "users".* FROM "users"
WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
User Load (0.4ms)
SELECT "users".* FROM "users"
INNER JOIN "memberships" ON "users"."id" = "memberships"."user_id"
WHERE "memberships"."project_id" = $1 [["project_id", 2]]
As you can see this results in two queries, not one. And returns all the members, not taking into account the exclusion.
Why doesn't this work?
Try renaming the scope to something else, like except_for or all_except. Name except is already used by active_record
http://api.rubyonrails.org/classes/ActiveRecord/SpawnMethods.html#method-i-except
Also you get 2 queries because you are doing User.find(1) which results in the first query.

Distinct Records with joins and order

I have a simple relationship between User and Donations in that a user has many donations, and a donation belongs to a user. What I'd like to do is get a list of users, ordered by the most recent donations.
Here's what I'm trying:
First I want to get the total number of uniq users, which is working as expected:
> User.joins(:donations).order('donations.created_at').uniq.count
(3.2ms) SELECT DISTINCT COUNT(DISTINCT "users"."id") FROM "users" INNER JOIN "donations" ON "donations"."user_id" = "users"."id"
=> 384
Next, when I remove the count method, I get an error that "ORDER BY expressions must appear in select list":
> User.joins(:donations).order('donations.created_at').uniq
User Load (0.9ms) SELECT DISTINCT "users".* FROM "users" INNER JOIN "donations" ON "donations"."user_id" = "users"."id" ORDER BY donations.created_at
PG::InvalidColumnReference: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
LINE 1: ...ON "donations"."user_id" = "users"."id" ORDER BY donations....
Then I tried fixing the Postgres error by explicitly setting the SELECT clause which at first glance appears to work:
> User.select('DISTINCT "users".id, "users".*, "donations".created_at').joins(:donations).order('donations.created_at')
User Load (17.6ms) SELECT DISTINCT "users".id, "users".*, "donations".created_at FROM "users" INNER JOIN "donations" ON "donations"."user_id" = "users"."id" ORDER BY donations.created_at
However, the number of records returned does not take into account the DISTINCT statement and returns 692 records:
> _.size
=> 692
How do I get the expected number of results (384) while also sorting by the donation's created_at timestamp?
Try this:
User.select('users.*,MAX(donations.created_at) as most_recent_donation').
joins(:donations).order('most_recent_donation desc').group('users.id')
I suppose an user has many donations, this would select the most recent created donation and would select distinct users filtering by their id.
I have not tested this though.

Rails 4: Modify eager load query when using .includes(:association)

I have two models:
class User < ActiveRecord::Base
has_many :purchases
# Perform joins and attach some calculations to the User object
scope :add_stats, -> { group("users.id").joins(:purchases).select("users.*, SUM(purchases.price) AS total_purchases") }
end
class Purchase < ActiveRecord::Base
belongs_to :user
end
The add_stats scope represents heavy calculations attached to the User objects. So if I want to get all User objects with stats, I just write User.all.add_stats.
So far so good. Now I want to fetch some Purchase objects and eager load the Users with stats as well. I've tried this:
belongs_to :user, -> { add_stats }
But then when Rails eager load the users, it seems to remove .group("user.id").joins(:purchases) and complain on purchases.price - "purchases table unknown". So the .select() is the only thing preserved from the scope.
How do I apply a scope (with working .group().joins()) to the eager load query of all included belongs_to :user objects?
i just tried it in my rails4 app and it works
class User < ActiveRecord::Base
scope :add_stats, -> { group("users.id").joins(:events).select("users.*, SUM(events.id) AS total_events") }
class Event
belongs_to :user, -> { add_stats }
in rails console
Event.includes(:users).first.user.total_events
Reloading...
Event Load (0.1ms) SELECT "events".* FROM "events" WHERE "events"."label" = 'hamburg' ORDER BY "events"."id" ASC LIMIT 1
Participant Load (0.3ms) SELECT "participants".* FROM "participants" WHERE "participants"."event_id" IN (2)
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" IN (1, 2, 3, 4, 8, 10, 11, 12, 14, 16, 17, 18, 19, 20)
User Load (0.3ms) SELECT users.*, SUM(events.id) AS total_events FROM "users" INNER JOIN "events" ON "events"."user_id" = "users"."id" WHERE "users"."id" = ? GROUP BY users.id ORDER BY "users"."id" ASC LIMIT 1 [["id", 2]]
=> 68
Event.first.user.total_events
Reloading...
Event Load (0.2ms) SELECT "events".* FROM "events" WHERE "events"."label" = 'hamburg' ORDER BY "events"."id" ASC LIMIT 1
User Load (0.2ms) SELECT users.*, SUM(events.id) AS total_events FROM "users" INNER JOIN "events" ON "events"."user_id" = "users"."id" WHERE "users"."id" = ? GROUP BY users.id ORDER BY "users"."id" ASC LIMIT 1 [["id", 2]]
=> 68
i guess that this is not what you want, as this does not use the scope for the include.

Resources