Create a scope in rails based on HABTM association count - sql

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

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....

Building a trending algorithm based on post count and frequency

Say I have a Board model. Boards have many Posts. I simply want to find the boards that have the highest post count within the span of (x) days. Below is my extremely naive approach to this. With the code provided I get the error:
ActiveRecord::StatementInvalid (PG::UndefinedTable: ERROR: missing FROM-clause entry for table "posts")
LINE 1: SELECT "boards".* FROM "boards" WHERE (board.posts.created_...
^
: SELECT "boards".* FROM "boards" WHERE (board.posts.created_at >= '2019-06-05 12:14:30.661233') LIMIT $1
Please let me know if there's a better way to do this in addition the error I'm receiving.
class Board < ApplicationRecord
has_many :posts
scope :trending, -> { includes(:posts).where('board.posts.created_at >= ?', Time.now-7.days).order(posts_count: :desc) }
end
class Post < ApplicationRecord
belongs_to :board, counter_cache: true
end
Update:
So I managed to come up with a working scope but not 100% sure if it's the most optimal. Your thoughts would be appreciated:
scope :trending, -> { includes(:posts).where(posts: { created_at: Time.now - 7.days }).order(posts_count: :desc) }
Update:
Board.joins(:posts)
.select("boards.*, count(posts.id) as latest_posts_count")
.where('posts.created_at >= ?', 7.days.ago)
.order('latest_posts_count desc')
.group('boards.id')
Try this, you will need to join it and group them by board_id
Board.joins(:posts)
.select("boards.*, count(posts.id) as posts_count")
.where('posts.created_at >= ?', 7.days.ago)
.order('posts_count desc')
.group('boards.id')
Explanation:
We joined (inner join) the tables so by default you get only boards which has at least one post associated with it
we ordered them based on posts count
we grouped them based on boards.id

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

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

Complex Query with Has many and belongs to for RAILS 3

I am trying to do a QUERY in my controller to get a list of suppliers with a category ID.
I have my models set up like this.
class Supplier < ActiveRecord::Base
has_and_belongs_to_many :sub_categories
end
class Category < ActiveRecord::Base
has_many :sub_categories
end
class SubCategory < ActiveRecord::Base
belongs_to :category
has_and_belongs_to_many :suppliers
end
A supplier can have Many sub_categories that are under one single category. So i can grab the category of a supplier by doing this.
#supplier.sub_categories.first.category.name
This returns the category that the supplier comes under because they have to have at least 1 sub category which is then linked to a category.
What i am trying to do is by passing a category_id i wish to return all suppliers that come under that category.
I had it written like this but it doesnt seem to be working.
#category = Category.find(params[:category_id])
#suppliers = Supplier.where('sub_category.first.category.id = ?', #category.id)
i get the following sql error
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.id = 20)' at line 1: SELECT `suppliers`.* FROM `suppliers` WHERE (sub_category.first.category.id = 20)
Well, that's certainly an sql error. The stuff inside the where() call gets translated directly to SQL, and that's not sq;l. :)
You need to join tables together. I'm assuming there's a sub_category_suppliers table that completes the habtm association. (BTW, I much prefer to use has_many :through exclusively)
I think it would be something like this:
Supplier.joins(:sub_category_suppliers => :sub_categories).
where('sub_categories.category_id =?', #category.id).
group('suppliers.id')
As Caley Woods suggested, this should be placed in the Supplier model as a scope:
scope :by_category, lambda { |category_id|
joins(:sub_category_suppliers => :sub_categories).
where('sub_categories.category_id =?', category_id).
group('suppliers.id')
}
and then called as Supplier.by_category(#category.id)

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.