Data tables warning in Rails - ruby-on-rails

I have a User model and user_note model. UserNote model having user_id and note_id.
has_one :user_note, dependent: :destroy, required: false
has_one :note, through: :user_note
Having a scope for getting recent_data
scope :recent_data, -> { joins(order_nulls_last) }
While filtering
when 'recent_data'
joins('INNER JOIN user_note ON user_note.user_id = user.user_id
ORDER BY CASE WHEN user_note.imp_date IS NULL THEN 1 ELSE 0 END,
user.last_name ASC, user.first_name ASC')
def self.order_nulls_last
Arel.sql("INNER JOIN user_note ON user_note.user_id =
user.user_id ORDER BY CASE WHEN user_note.imp_date IS NULL THEN 1
ELSE 0 END")
end
In User datatable
def get_raw_records
records = User.all
records = records.includes(:note,:user_note).references(:note,:user_note)
records = records.send(filter) if filter.present? #recent_data
records
end
But hitting from UI I am getting this issue.
ActiveRecord::StatementInvalid (PG::InvalidColumnReference: ERROR: for SELECT
DISTINCT, ORDER BY expressions must appear in select list
LINE 1: ...user_note.user_id = user.user_id ORDER BY CASE WHEN ...
I am trying from last few days but could not be able to fix it. Please help me in fixing this issue.

Related

PostgreSQL ordering by another attribute by default and then by whatever I define?

I've got an IndexQuery where I enrich a set of Users and then I apply different filters to it.
class Users::IndexQuery
def initialize(users)
#users = users
end
def call
#users.kept.created_desc
.select(
"users.*",
"COALESCE(paid_invoices.paid_amount_cents,0) AS paid_amount_cents",
"COALESCE(unpaid_invoices.unpaid_amount_cents,0) AS unpaid_amount_cents",
)
.joins(joins_paid_invoices, joins_unpaid_invoices, :subscription)
.includes(:subscription)
end
private
def joins_paid_invoices
#joins_paid_invoices ||= <<-SQL
LEFT OUTER JOIN (
SELECT invoices.user_id as user_id,
SUM(invoices.amount_cents) as paid_amount_cents
FROM invoices
WHERE invoices.status = 'paid'
GROUP BY user_id
)
AS paid_invoices
ON paid_invoices.user_id = users.id
SQL
end
def joins_unpaid_invoices
#joins_unpaid_invoices ||= <<-SQL
LEFT OUTER JOIN (
SELECT invoices.user_id AS user_id,
SUM(invoices.amount_cents) as unpaid_amount_cents
FROM invoices
WHERE invoices.status = 'unpaid'
GROUP BY user_id
)
AS unpaid_invoices
ON unpaid_invoices.user_id = users.id
SQL
end
end
This is what happens in the controller:
enriched_users = ::Users::IndexQuery.new(User.all.includes(:account)).call
if params.dig(:q, :extra_params, :preset_filter).present?
enriched_users = enriched_users.where("subscriptions.status = ?", "in_trial").order("subscriptions.trial_end ASC")
end
However, running the query in the console with a .to_sql at the end prints the following:
ORDER BY \"users\".\"id\" DESC, subscriptions.trial_end ASC"
Nowhere in the controller nor the IndexQuery I specify that it should order by Users.id. What could be wrong and why is it ordering by that attribute by default?
EDIT: I do have a GROUP BY users.id in the IndexQuery. Could that be the problem?
On Postgres 9.4+ you can simplefy this greatly by using FILTER which lets you put conditions on the result of aggregate functions:
# see https://github.com/rubocop/ruby-style-guide#namespace-definition
module Users
class IndexQuery
def initialize(users)
#users = users
end
def call
#users.kept.created_desc
.joins(:invoices)
.group(:id)
.select(
User.arel_table[:*],
'SUM(invoices.amount_cents) FILTER (WHERE invoices.paid = true) AS paid_amount_cents',
'SUM(invoices.amount_cents) FILTER (WHERE invoices.paid = false) AS unpaid_amount_cents'
)
end
# conventiance method so that you don't need to call
# Users::IndexQuery.new(users).call
def self.call(users)
new(users).call
end
end
end
But honestly I don't see why you would extract this into its own object instead of just creating a scope / class method in the model as its a very overcomplicated solution.

Mysql2::Error: Unknown column in where clause Rails

I receive an error of
Mysql2::Error: Unknown column 'requests.access_level_id' in 'where clause':
SELECT `requests`.*
FROM `requests` LEFT OUTER JOIN `users` ON `users`.`id` = `requests`.`from_id`
WHERE `requests`.`access_level_id` = 1
ORDER BY id DESC
Model
class Request < ApplicationRecord
belongs_to :user, foreign_key: :from_id
end
Controller
#req = Request.left_outer_joins(:user).where(access_level_id: 1).order('id DESC')
How can I remove requests from the WHERE clause requests.access_level_id = 1? I just want access_level_id = 1 to be in the where statement.
As you requested, you can add where clause without requests as,
#req = Request.left_outer_joins(:user).where('access_level_id = ?', 1).order('id DESC')
But its good to keep relative aliasing for access_level_id. If its users then please use it like,
#req = Request.left_outer_joins(:user).where(users: { access_level_id: 1 }).order('id DESC')
Assuming that access_level_id is field for user, you can replace your query with following:
#req = Request.left_outer_joins(:user).where('users.access_level_id = ?', 1).order('id DESC')
By default the fields in where conditions are considered to be belonging to Request method in your query.
Hope this helps you.
Please let me know if you face any issue.

column is there, but I receive: SQLite3::SQLException: no such column: requestor.birthyear

I have a meetings table, which has a requestor (user) with an attribute birthyear.
In the terminal I can do Meeting.find(1).requestor.birthyear
But in the meeting controller, it says no such column: requestor.birthyear
I tried adding something like users_attributes: [:birthyear] in the meetings_params, but it didn't make any difference.
What am I missing?
Here are the full files:
meeting controller
def community
if current_user.birthyear != nil
#meeting_requests = Meeting.where('meeting_time >= ? AND requestee_id IS ? AND status = ? AND (requestor.birthyear <= ? AND requestor.birthyear >= ?)', Date.today, nil, "Active", current_user.birthyear + 10, current_user.birthyear - 10).order('meeting_time asc')
end
end
meeting model
class Meeting < ApplicationRecord
belongs_to :requestor, class_name: "User"
belongs_to :requestee, class_name: "User"
end
error:
SQLite3::SQLException: no such column: requestor.birthyear: SELECT "meetings".* FROM "meetings" WHERE (meeting_time >= '2017-03-06' AND requestee_id IS NULL AND status = 'Active' AND (requestor.birthyear <= 1944 AND requestor.birthyear >= 1924)) ORDER BY meeting_time asc
Let me know if you need anything further - thank in advance!
UPDATE
trying to join the tables in the query, but not too knowledgeable on that. I'm trying:
#meeting_requests = Meeting.where('meeting_time >= ? AND requestee_id IS ? AND status = ? ', Date.today, nil, "Active").order('meeting_time asc').joins("INNER JOIN users on requestor.birthyear >= current_user.birthyear + 10")
but get:
ActionView::Template::Error (SQLite3::SQLException: ambiguous column name: status: SELECT "meetings".* FROM "meetings" INNER JOIN users on requestor.birthyear >= current_user.birthyear + 10 WHERE (meeting_time >= '2017-03-07' AND requestee_id IS NULL AND status = 'Active' ) ORDER BY meeting_time asc):
*****UPDATE*****
I posted another question to get the syntax. If it helps anyone:
Rails: Two 'where' queries - each works individually, but not together
requestor and requestee are Rails associations. This means that Rails understands how to get to requestor from a Meeting...
However - your database does not know what a requestor is. So you can't put it into the string-part of a SQL-query like you have done here:
AND (requestor.birthyear <= ? AND requestor.birthyear >= ?)'
Instead you will need to use joins to add these to your query, then figure out how rails names them in order to access the columns from them.

Write and Activerecord join query

I'm writing an activerecord join query but It doesn't work.
I have these two classes
class User
belongs_to :store, required: true
end
class Store < ActiveRecord::Base
has_many :users, dependent: :nullify
has_one :manager, -> { where role: User.roles[:manager] }, class_name: 'User'
end
I need to get all the stores with a manager and all the stores without a manager.
I write these two queries
Store.includes(:users).where('users.role <> ?', User.roles[:manager]).references(:users).count
Store.includes(:users).where('users.role = ?', User.roles[:manager]).references(:users).count
and the result is
2.2.1 :294 > Store.includes(:users).where('users.role <> ?', User.roles[:manager]).references(:users).count
(6.6ms) SELECT COUNT(DISTINCT "stores"."id") FROM "stores" LEFT OUTER JOIN "users" ON "users"."store_id" = "stores"."id" WHERE (users.role <> 1)
=> 201
2.2.1 :295 > Store.includes(:users).where('users.role = ?', User.roles[:manager]).references(:users).count
(4.0ms) SELECT COUNT(DISTINCT "stores"."id") FROM "stores" LEFT OUTER JOIN "users" ON "users"."store_id" = "stores"."id" WHERE (users.role = 1)
=> 217
Now I know that I have 219 stores, and using
with_manager = 0
without_manager = 0
Store.all.each do |s|
if s.manager.present?
with_manager = with_manager +1
else
without_manager = without_manager +1
end
end
I know also that I have 217 stores with manager and 2 store without manager. One query is working, the second (stores without manager) fails.
So I must fix the query, but I cannot understand how can I fix it...
Usually I use the following thing for, getting al stores with a manager:
Store.joins(:manager)
Joins will use a inner join and not a left join that is includes case.
In the opposite case, that's tricky, but I do in this way:
Store.includes(:manager).where(users: { id: nil })
It's a left join with manager and getting all stores without a user included.

How do I write a UNION chain with ActiveRelation?

I need to be able to chain an arbitrary number of sub-selects with UNION using ActiveRelation.
I'm a little confused by the ARel implementation of this, since it seems to assume UNION is a binary operation.
However:
( select_statement_a ) UNION ( select_statement_b ) UNION ( select_statement_c )
is valid SQL. Is this possible without doing nasty string-substitution?
You can do a bit better than what Adam Lassek has proposed though he is on the right track. I've just solved a similar problem trying to get a friends list from a social network model. Friends can be aquired automatically in various ways but I would like to have an ActiveRelation friendly query method that can handle further chaining. So I have
class User
has_many :events_as_owner, :class_name => "Event", :inverse_of => :owner, :foreign_key => :owner_id, :dependent => :destroy
has_many :events_as_guest, :through => :invitations, :source => :event
def friends
friends_as_guests = User.joins{events_as_guest}.where{events_as_guest.owner_id==my{id}}
friends_as_hosts = User.joins{events_as_owner}.joins{invitations}.where{invitations.user_id==my{id}}
User.where do
(id.in friends_as_guests.select{id}
) |
(id.in friends_as_hosts.select{id}
)
end
end
end
which takes advantage of Squeels subquery support. Generated SQL is
SELECT "users".*
FROM "users"
WHERE (( "users"."id" IN (SELECT "users"."id"
FROM "users"
INNER JOIN "invitations"
ON "invitations"."user_id" = "users"."id"
INNER JOIN "events"
ON "events"."id" = "invitations"."event_id"
WHERE "events"."owner_id" = 87)
OR "users"."id" IN (SELECT "users"."id"
FROM "users"
INNER JOIN "events"
ON "events"."owner_id" = "users"."id"
INNER JOIN "invitations"
ON "invitations"."user_id" =
"users"."id"
WHERE "invitations"."user_id" = 87) ))
An alternative pattern where you need a variable number of components is demonstrated with a slight modification to the above code
def friends
friends_as_guests = User.joins{events_as_guest}.where{events_as_guest.owner_id==my{id}}
friends_as_hosts = User.joins{events_as_owner}.joins{invitations}.where{invitations.user_id==my{id}}
components = [friends_as_guests, friends_as_hosts]
User.where do
components = components.map { |c| id.in c.select{id} }
components.inject do |s, i|
s | i
end
end
end
And here is a rough guess as to the solution for the OP's exact question
class Shift < ActiveRecord::Base
def self.limit_per_day(options = {})
options[:start] ||= Date.today
options[:stop] ||= Date.today.next_month
options[:per_day] ||= 5
queries = (options[:start]..options[:stop]).map do |day|
where{|s| s.scheduled_start >= day}.
where{|s| s.scheduled_start < day.tomorrow}.
limit(options[:per_day])
end
where do
queries.map { |c| id.in c.select{id} }.inject do |s, i|
s | i
end
end
end
end
Because of the way the ARel visitor was generating the unions, I kept getting SQL errors while using Arel::Nodes::Union. Looks like old-fashioned string interpolation was the only way to get this working.
I have a Shift model, and I want to get a collection of shifts for a given date range, limited to five shifts per day. This is a class method on the Shift model:
def limit_per_day(options = {})
options[:start] ||= Date.today
options[:stop] ||= Date.today.next_month
options[:per_day] ||= 5
queries = (options[:start]..options[:stop]).map do |day|
select{id}.
where{|s| s.scheduled_start >= day}.
where{|s| s.scheduled_start < day.tomorrow}.
limit(options[:per_day])
end.map{|q| "( #{ q.to_sql } )" }
where %{"shifts"."id" in ( #{queries.join(' UNION ')} )}
end
(I am using Squeel in addition to ActiveRecord)
Having to resort to string-interpolation is annoying, but at least the user-provided parameters are being sanitized correctly. I would of course appreciate suggestions to make this cleaner.
I like Squeel. But don't use it. So I came to this solution (Arel 4.0.2)
def build_union(left, right)
if right.length > 1
Arel::Nodes::UnionAll.new(left, build_union(right[0], right[1..-1]))
else
Arel::Nodes::UnionAll.new(left, right[0])
end
end
managers = [select_manager_1, select_manager_2, select_manager_3]
build_union(managers[0], managers[1..-1]).to_sql
# => ( (SELECT table1.* from table1)
# UNION ALL
# ( (SELECT table2.* from table2)
# UNION ALL
# (SELECT table3.* from table3) ) )
There's a way to make this work using arel:
tc=TestColumn.arel_table
return TestColumn.where(tc[:id]
.in(TestColumn.select(:id)
.where(:attr1=>true)
.union(TestColumn.select(:id)
.select(:id)
.where(:attr2=>true))))

Resources