ActiveRecord: Get all users who should be banned today - ruby-on-rails

I have two models:
class User < ActiveRecord::Base
has_many :ban_messages, dependent: :destroy
end
class BanMessage < ActiveRecord::Base
belongs_to :user
end
Table BanMessage contains a field t.datetime :ban_date that stores the date, when user should be banned. User table contains a field status, that contains one of the status values (active, suspended, banned). When I send BanMessage to User, his status field changes from 'active' to 'suspended'. Also I can activate user back, so he would not be banned at ':ban_date', but his BanMessage wouldn't be destroyed.
So I need to create query for selecting all Users with status 'suspended', who have in their newest BanMessages records 'ban_date' field, with DateTime value, which is between 'DateTime.now.beginning_of_day' and 'DateTime.now.end_of_day'.

You can filter your users with where query followed by join method for associated ban_messages with where method to further filter with the date range.
User.where(status: 'suspended').joins(:ban_messages).where("ban_date >= ? AND ban_date <= ?", DateTime.now.beginning_of_day, DateTime.now.end_of_day )

Try something like this.
User.joins(:ban_messages).where(status: :suspended).where('ban_messages.ban_date BETWEEN :begin_time AND :end_date', begin_time: DateTime.now.beginning_of_day, end_time: DateTime.now.end_of_day)

Here's what I needed:
User.joins(:ban_messages)
.where('users.status = ?', :suspended)
.where('ban_messages.created_at = (SELECT MAX(ban_messages.created_at) FROM ban_messages WHERE ban_messages.user_id = users.id)')
.where('ban_messages.ban_date BETWEEN ? and ?', DateTime.now.beginning_of_day, DateTime.now.end_of_day)
.group('users.id')
UPDATE
MrYoshiji: The caveat in your code is that if you miss one day, then
the next day you won't see the people that should have been banned the
day before. You might want to get all User where the ban_date is
lesser than DateTime.now.end_of_day, either in the same view or
another one.
So final query might be something like this
User.joins(:ban_messages)
.where('users.status = ?', :suspended)
.where('ban_messages.created_at = (SELECT MAX(ban_messages.created_at) FROM ban_messages WHERE ban_messages.user_id = users.id)')
.where('ban_messages.ban_date < ?', DateTime.now.end_of_day)
.group('users.id')

Related

Ruby on Rails lambda with left join and condition

Room has_many bookings
Booking belongs_to room
I have this code and it works fine:
available_rooms = Room.select {|room| room.bookings.where("day = ?", date).count < 3 || room.bookings.empty?}
but I wonder if it is possible to rewrite it like a lambda with left join
scope :available, lambda {|date| joins('LEFT OUTER JOIN bookings on bookings.room_id = rooms.id').......
I tried this but got back only Rooms WITH at least one Booking, so totally empty rooms WITHOUT Bookings at all were excluded:
def self.available(date)
# You can use `Arel.star` instead of `:id` on postgres.
b = Booking.arel_table[:id]
group(:id)
.left_joins(:bookings)
.where(bookings: { day: date })
.having(b.count.lt(3)) # COUNT(bookings.id) < 3
end
UPDATE
To satisfy the requirement you are actually going to need a complex join please try the following instead
class Room < ApplicationRecord
def self.available(date)
b = Booking.arel_table
join_statement = Arel::Nodes::OuterJoin.new(b,
Arel::Nodes::On.new(
arel_table[:id].eq(b[:room_id])
.and(b[:day].eq(Arel::Nodes.build_quoted(date)))
))
group(:id)
.joins(join_statement)
.having(b[:id].count.lt(3))
end
end
Here we are LEFT JOINing bookings based on the relation to room and the date requested. Now if a room is not booked for that date or it has less than 3 bookings for that date it should show up as requested.

Sort data in ruby on rails

Association is like
class User < ActiveRecord::Base
has_many :products
has_many :ratings
I want to sort products according to user ratings.
Let suppose I want to sort all those product whose ratings is greater than 4.
I cant find any way to do that.
I do something like
User.joins(:ratings).where("ratings.rate > ?", 4).includes(:ratings)
From that I get all user whose ratings is greater than 4 but how join with product and sort them?
User.joins(:ratings).where("ratings.rate > ?", 4).order('ratings DESC')
I am not sure what includes(:ratings) doing at the last.
Should just use something like this and it should probably work:
User.includes(:ratings).where("ratings.rate > ?", 4).order('ratings DESC')
Reference: issue in order clause and limit in active record
User.joins(:ratings).where("ratings.rate > ?", 4).order('ratings.rate')
And if you want to find associated products then this should work:
Product.joins(user: :ratings).where("ratings.rate > ?", 4).order('ratings.rate')

How to return the next record?

In my Rails app I have a function next which I am using in my show templates to link to the next product (ordered by price):
class Product < ActiveRecord::Base
belongs_to :user
def next
Product.where("user_id = ? AND price > ?", user_id, price).order("price ASC").first
end
end
The problem is that this function works only when all products have different prices. The moment there are multiple products with the same price, the next function picks one product randomly (?) thereby skipping all the others.
Is there a way to make this function return the next product, even though it has the same price? (if the price is the same, the products could be ordered simply by ID or date)
I tried replacing > with >= but that didn't work.
Thanks for any help.
You have to use id in where clause and in order as well
def next
Product.where("user_id = ? AND price >= ? AND id > ?", user_id, price, id).order("total, id").first
end

Ambiguous column name in Ruby on Rails with SQLite database?

I am getting this error in my Rails app:
ActiveRecord::StatementInvalid in PaymentsController#index
SQLite3::SQLException: ambiguous column name: date: SELECT COUNT(*) FROM "payments" INNER JOIN "invoices" ON "payments"."invoice_id" = "invoices"."id" WHERE "invoices"."user_id" = 1 AND (date >= '2013-01-01' and date <= '2013-12-31')
The problem seems to be that I have a date field in my invoices as well as my payments table.
Yet I still don't know how to fix this error.
class User < ActiveRecord::Base
def number_of_payments_in(year)
payments.where("payments.date >= ? and payments.date <= ?", "#{year}-01-01", "#{year}-12-31").count
end
end
class Payment < ActiveRecord::Base
def self.search(year)
if year
where("date >= ? and date <= ?", "#{year}-01-01", "#{year}-12-31")
end
end
end
Can anybody help?
This answer may be a bit vague as it's not clear from your question which class the self.search(year) method is in, but let me know if this doesn't help and we can try and go further.
My guess is that, unlike in the number_of_payments_in method, in the self.search method you haven't specified an appropriate table in the where call.
In number_of_payments_in, you specify payments.date, but in self.search you just use date. You said that you have a date field in your invoices as well as your payments table, so a join call across both tables will need every reference to date to be scoped by table.
Adding the appropriate table in front of date in self.search (as you have done in number_of_payments_in) may solve the problem.

Self-referential find in controller count relations

I'm having real trouble pulling out a set of records that are self-referentially related to a user in order to show these on a user's 'show' page.
Here's the idea:
Users (current_user) rate the compatibility between two other users (user_a and user_b). They can rate compatibility either positively or negatively: rating two users "compatible" creates a positive_connection between user_a and user_b, and rating them "incompatible" creates a negative_connection. So there are models for positive_connection, negative_connection and user.
Now I need to display only users that are overall_positively_connected_to(#user) (i.e. where positive_connections_to(#user).count > negative_connections_to(#user).count).
This is where I've got to, but I can't get any further:
User model:
def overall_positive_connected_to(user)
positive_connections_to(user).count > negative_connections_to(user).count
end
def positive_connections_to(user)
positive_connections.where("user_b_id = ?", user)
end
def negative_connections_to(user)
negative_connections.where("user_b_id = ?", user)
end
Controller
#user.user_bs.each do |user_b|
if user_b.overall_pos_connected_to(#user)
#compatibles = user_b
end
end
The code in the controller is clearly wrong, but how should I go about doing this? I'm completely new to rails (and sql), so may have done something naive.
Any help would be great.
So am I right in saying you have 3 models
User (id, name)
PositiveConnection (user_a_id, user_b_id)
NegativeConnection (user_a_id, user_b_id)
Or something of that sort.
I think you just want 2 models
and for convenience I'm going to rename the relations as "from_user" and "to_user"
User (id, name)
Connection (value:integer, from_user_id, to_user_id)
Where value is -1 for a negative
and +1 for a positive.
Now we can have do something like
(note: you need to sort out the exact syntax, like :foreign_key, and :source, and stuff)
class User
has_many :connections, :foreign_key => "from_user_id"
has_many :connected_users, :through => :connections, :source => :to_user
def positive_connections
connections.where(:value => 1)
end
def negative_connections
...
end
end
But we also now have a framework to create a complex sql query
(again you need to fill in the blanks... but something like)
class User
def positive_connected_users
connected_users.joins(:connections).group("from_user_id").having("SUM(connections.value) > 0")
end
end
this isn't quite going to work
but is kind of pseudo code for a real solution
(it might be better to think in pure sql terms)
SELECT users.* FROM users
INNER JOIN connections ON to_user_id = users.id
WHERE from_user_id = #{user.id}
HAVING SUM(connections.value) > 0

Resources