Rails 5 query for a condition in a third table using joins? - sql

this are my models - first:
class Negocio < ApplicationRecord
has_many :cuenta_clientes
end
second
class CuentaCliente < ApplicationRecord
belongs_to :negocio
has_many :pago_clientes
end
and third:
class PagoCliente < ApplicationRecord
belongs_to :cuenta_cliente
end
I want to select all the PagoCliente that a Negocio has. But there is no references between Negocio and PagoCliente (and i cannot modify tables and relations) so this is my attempt:
pagos = PagoCliente.joins(cuenta_cliente: :negocio).where(negocio: {id: params[:negocio_id}])
but this is my error output:
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "negocio"
LINE 1: ...cios"."id" = "cuenta_clientes"."negocio_id" WHERE "negocio"....
so, Which is the correct syntax for this query? thanks. I'm using Postgres...

The table is plural, try:
pagos = PagoCliente.joins(cuenta_cliente: :negocio).where(negocios: {id: params[:negocio_id}])

if you are getting params[:negocio_id] you can do this way
1-
cuenta_cliente_ids= Negocio.find(params[:negocio_id]).cuenta_clientes.pluck(:id)
2-
pago_clientes = PagoCliente.where("cuenta_cliente_id IN (?)", cuenta_cliente_ids)
however with your query i think there is typo here i modify it
pagos = PagoCliente.joins(cuenta_cliente: :negocio).where(negocios: {id: params[:negocio_id]})
ps correct me if i'm wrong

Related

Ransack / Rails 6 : PG::UndefinedTable for a query through a belongs_to

I can't chain a where and a Ransack query, I get a PG::UndefinedTable error. I'm using Rails 6.1.3 and Ransack 2.4.2.
I have seen this issue : https://github.com/activerecord-hackery/ransack/issues/1119 where everybody agrees that the problem has been fixed with the upgrade to rails 6.1. Sadly, not for my case.
My models looks like this :
class Appointment < ApplicationRecord
belongs_to :slot
end
class Slot < ApplicationRecord
belongs_to :place
end
This query
Appointment.joins(slot: [:place])
.where('slot.date' => Date.today)
.ransack(slot_place_id_eq: 2)
.result
returns this error :
ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: invalid reference to FROM-clause entry for table "slots")
LINE 1: ...ointments"."slot_id" WHERE "slot"."date" = $1 AND "slots"."p...
^
HINT: Perhaps you meant to reference the table alias "slot".
The generated query is the following, and indeed, slots.place_id can't work for a belongs_to.
SELECT appointments.*
FROM appointments
INNER JOIN slots slot ON slot.id = appointments.slot_id
INNER JOIN places ON places.id = slot.place_id
WHERE slot.date = '2021-06-30' AND slots.place_id = 2
Any clue ?
I just solved this : it was a different issue, with the same error message than the other one.
This :
.where('slot.date' => Date.today)
is supposed to be this :
.where('slots.date' => Date.today)
Two hour of research, 10 minutes to write a SO question, finding the solution by yourself 30 seconds after clicking publish....

Rails Many-to-many relationship with extension generating incorrect SQL

I'm having an issue where a many-to-many relationship with an "extension" is generating incorrect SQL.
class OrderItem < ApplicationRecord
belongs_to :buyer, class_name: :User
belongs_to :order
belongs_to :item, polymorphic: true
end
class User < ApplicationRecord
has_many :order_items_bought,
-> { joins(:order).where.not(orders: { state: :expired }).order(created_at: :desc) },
foreign_key: :buyer_id,
class_name: :OrderItem
has_many :videos_bought,
-> { joins(:orders).select('DISTINCT ON (videos.id) videos.*').reorder('videos.id DESC') },
through: :order_items_bought,
source: :item,
source_type: :Video do
def confirmed
where(orders: { state: :confirmed })
end
end
end
user.videos_bought.confirmed generates this SQL:
Video Load (47.0ms) SELECT DISTINCT ON (videos.id) videos.* FROM
"videos" INNER JOIN "order_items" "order_items_videos_join" ON
"order_items_videos_join"."item_id" = "videos"."id" AND
"order_items_videos_join"."item_type" = $1 INNER JOIN
"orders" ON "orders"."id" = "order_items_videos_join"."order_id" INNER JOIN
"order_items" ON "videos"."id" = "order_items"."item_id" WHERE
"order_items"."buyer_id" = $2 AND ("orders"."state" != $3) AND "order_items"."item_type" = $4 AND
"orders"."state" = $5 ORDER BY videos.id DESC, "order_items"."created_at" DESC LIMIT $6
Which returns some Video records which are joined with orders that do NOT have state confirmed. I would expect all orders to have state confirmed.
If I use raw SQL everything works fine:
has_many :videos_bought,
-> {
joins('INNER JOIN orders ON orders.id = order_items.order_id')
.select('DISTINCT ON (videos.id) videos.*')
.reorder('videos.id DESC')
},
through: :order_items_bought,
source: :item,
source_type: :Video do
def confirmed
where(orders: { state: :confirmed })
end
end
Now user.videos_bought.confirmed generates this SQL:
Video Load (5.4ms) SELECT DISTINCT ON (videos.id) videos.* FROM
"videos" INNER JOIN "order_items" ON
"videos"."id" = "order_items"."item_id" INNER JOIN orders ON
orders.id = order_items.order_id WHERE
"order_items"."buyer_id" = $1 AND ("orders"."state" != $2) AND
"order_items"."item_type" = $3 AND "orders"."state" = $4 ORDER BY
videos.id DESC, "order_items"."created_at" DESC LIMIT $5
Which seems more succinct because it avoids the auto generated order_items_videos_join name. It also only returns orders that have state confirmed.
Any idea what is going on? Does ActiveRecord just generate faulty SQL sometimes?
Using rails 5.1.5. Upgrading to latest made no difference.
I'm hoping to get an explanation on why Rails generates the order_items_videos_join string in the first case but not in the second case. Also, why the second SQL query produces incorrect results. I can edit the question with more code and data samples if needed.
ActiveRecord does not just generate faulty SQL sometimes, but there's a little nuance to it such that starting simple is best when it comes to defining relationships. For example, let's rework your queries to get that DISTINCT ON out of there. I've never seen a need to use that SQL clause.
Before chaining highly customized association logic, let's just see if there's simpler way to query first, and then check to see whether there's a strong case for turning your queries into associations.
Looks like you've got a schema like this:
User
id
Order
id
state
OrderItem
id
order_id
buyer_id (any reason this is on OrderItem and not on Order?)
item_type (Video)
item_id (videos.id)
Video
id
A couple of tidbits
No need to create association extensions for query conditions that would make perfectly good scopes on the model. See below.
A perfectly good query might look like
Video.joins(order_item: :order).
where(order_items: {
buyer_id: 123,
order: {
state: 'confirmed'
}).
# The following was part of the initial logic but
# doesn't alter the query results.
# where.not(order_items: {
# order: {state: 'expired'}
# }).
order('id desc')
Here's another way:
class User < ActiveRecord::Base
has_many :order_items, foreign_key: 'buyer_id'
def videos_purchased
Video.where(id: order_items.videos.confirmed.pluck(:id))
end
end
class OrderItem < ActiveRecord::Base
belongs_to :order, class_name: 'User', foreign_key: 'buyer_id'
belongs_to :item, polymorphic: true
scope :bought, -> {where.not(orders: {state: 'cancelled'})}
scope :videos, -> {where(item_type: 'Video')}
end
class Video < ActiveRecord::Base
has_many :order_items, ...
scope :confirmed, -> {where(orders: {state: 'confirmed'})}
end
...
user = User.first()
user.videos_purchased
I might have the syntax a little screwy when it comes to table and attribute names, but this should be a start.
Notice that I changed it from one to two queries. I suggest running with that until you really notice that you have a performance problem, and even then you might have an easier time just caching queries, than trying to optimize complex join logic.

Create a scope in rails based on HABTM association count

I'm trying to create a rails scope based on the count of a model's HABTM assocation, but I'm struggling with the SQL.
I want Match.open to return matches with less than two users. I also have Match.upcoming, which returns matches with a 'future_date' in the future, which is working well.
My code:
class Match < ActiveRecord::Base
has_and_belongs_to_many :users
scope :open, joins('matches_users').
select('*').
group('matches.id').
having('count(matches_users.user_id) < 2')
scope :upcoming, lambda {
where("proposed_date between ? and ?", Date.today, Date.today.next_month.beginning_of_month)
}
I'm currently getting the error:
SQLite3::SQLException: no such column: matches_users.user_id: SELECT * FROM "matches" matches_users GROUP BY matches.id HAVING count(matches_users.user_id) < 2
ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: matches_users.user_id: SELECT * FROM "matches" matches_users GROUP BY matches.id HAVING count(matches_users.user_id) < 2
I'm currently achieving this with a class method:
def self.open
self.select{|match| match.users.length < 2}
end
Which works, but I'd really like to move this into a scope for speed, and so that I can chain the scopes like Match.open.upcoming.
What am I doing wrong here? What's the correct way to do this? Any help would be appreciated.
Give this a shot - I've used something similar before and it seems to work for me:
class Match < ActiveRecord::Base
has_and_belongs_to_many :users
scope :open, joins(:matches_users)
.select('matches.*')
.group('matches.id')
.having('count(matches_users.id) < 2')
...
end

Rails 4 Generating Invalid Column Names

I have a fairly simple query to return the first record in a many-to-many relation or create one if it doesn't exist.
UserCategorization.where(category_id: 3, user_id: 5).first_or_create
My model looks like:
class UserCategorization < ActiveRecord::Base
belongs_to :user
belongs_to :category
self.primary_key = [:user_id, :category_id]
end
However it generates an invalid column name in the SQL:
SQLite3::SQLException: no such column: user_categorizations.[:user_id, :category_id]:
SELECT "user_categorizations".* FROM "user_categorizations" WHERE
"user_categorizations"."category_id" = 3 AND "user_categorizations"."user_id" = 5
ORDER BY "user_categorizations"."[:user_id, :category_id]" ASC LIMIT 1
If I remove self.primary_key = [:user_id, :category_id] from the model, it can retrieve the record correctly but cannot save because it doesn't know what to use in the WHERE clause:
SQLite3::SQLException: no such column: user_categorizations.:
UPDATE "user_categorizations" SET "score" = ?
WHERE "user_categorizations"."" IS NULL
Has anyone seen this before?
I think one of these two suggestions will work:
First, try adding the following migration:
add_index :user_categorizations, [:user_id, :category_id]
Make sure to keep self.primary_key = [:user_id, :category_id] in your UserCategorization model.
If that doesn't work, destroy the UserCategorization table and run this migration:
def change
create_table :user_categorizations do |t|
t.references :user
t.references :category
t.timestamps
end
end
references are new to Rails 4. They add a foreign key and index to the specified columns.
Good Luck!
So it looks like Rails 4 ActiveRecord doesn't do composite keys very well so many-to-many models create the issues above. I fixed it by using this extension to ActiveRecord: http://compositekeys.rubyforge.org/

Rails3: Getting "no attribute named..." in simple join

I'm trying join in Rails 3 for the first time, and can't get the most basic thing to work, so must be missing something major. Given
class Member < ActiveRecord::Base
belongs_to :status
...
end
class Status ActiveRecord::Base
has_many :members
...
end
when I try to use a join, testing in the console an example adapted from the Rails Guides
time_range = (Time.now.midnight - 1.day)..Time.now.midnight
Status.joins(:members).where('member.created_at' => time_range)
I get an error message,
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'member.created_at' in
'where clause': SELECT `statuses`.* FROM `statuses` INNER JOIN `members` ON
`members`.`status_id` = `statuses`.`id` WHERE (`member`.`created_at` BETWEEN
'2011-03-26 23:00:00' AND '2011-03-27 23:00:00')
Both models do have created_at columns and this works: Member.where(:created_at => time_range). I've tried several permutations and used different columns but get the same Unknown column error every time I try the join.
It should be members.created_at :) When you explicitly use the db table name, remember that it's in plural by rails convention.