Ransack sort order scope with arguments - ruby-on-rails

I have a Site model with relation Notification.
The site has several notifications every day. I try to calculate the sum of notifications for a few days and want to sort the sites by this sum.
class Site
has_many :notifications
scope :sort_by_notifications_between_dates_asc, -> (start_date, end_date) { left_joins(:notifications).merge(Notification.between_dates(start_date, end_date)).order(Arel.sql("count(notifications.*) asc")) }
scope :sort_by_notifications_between_dates_desc, -> (start_date, end_date) { left_joins(:notifications).merge(Notification.between_dates(start_date, end_date)).order(Arel.sql("count(notifications.*) desc")) }
end
class Notification
belongs_to :site
scope :between_dates, -> (start_date, end_date) {where(self.arel_table[:created_at].gteq(start_date.at_beginning_of_day).and(self.arel_table[:created_at].lteq(end_date.at_end_of_day)))}
end
It is possible for ransack to create a scope for sorting, but I have not found a way to pass the arguments (start_date and end_date) to this scope.
= sort_link(#q, :notifications_between_dates, t('.notifications_between_dates'), default_order: :desc)
I do not need to filter sites by date. I need to sort the sites by the sum of the notifications for the period of time. Is this even possible?

I found half the solution. I created ransacker with arguments
ransacker :notifications_between_dates, args: [:parent, :ransacker_args] do |parent, args|
start_date, end_date = args
query = <<-SQL
COALESCE(
(SELECT COUNT(notifications.*)
FROM notifications
WHERE notifications.site_id = sites.id
AND notifications.created_at >= '#{start_date}'
AND notifications.created_at <= '#{end_date}'
GROUP BY notifications.site_id)
,0)
SQL
Arel.sql(query)
end
Then we can sort records in this way
q = Site.ransack(sorts: [{name: :notifications_between_dates,
dir: 'asc',
ransacker_args: [Time.now-20.days,Time.now]}])
But looks like sort_link helper does not support passing ransacker_args via GET request in s parameter.

Related

handle ruby send nil

scope :
class Car < ApplicationRecord
scope :sold_between, -> (start_date, end_date,exclude_used_cars=true){
_used = exclude_used_cars ? "exclude_used_cars" : ""
Car.where("start_date <= ?", start_date ).and("end_date <= ?", end_date ).send(_used)
}
scope :exclude_used_cars, -> {
where.not(state: :used)
}
Problem:
stuck with .send(_used) I need to pass some valid symbols, but actually I have nil value exclude_used_cars when it is false.
Any better way solving this. Thanks
This can be solved by using a normal if and taking advantage of the chaining nature of queries.
Note that your where clause isn't quite right. Values need to be passed in using placeholders. While there is a .or there is no .and. Additional .where calls will go together with and.
Also note that I've avoided hard coding the Car class name in the scope. This ensures it will work with subclasses.
scope :sold_between, -> (start_date, end_date,exclude_used_cars=true){
query = where(
"start_date <= :start_date and end_date >= :end_date",
{ start_date: start_date, end_date: end_date }
)
if exclude_used_cars
query = query.exclude_used_cars
end
query
}
Because you can chain queries and scopes like this consider whether there's a need for a special exclude_used_cars parameter, especially one that defaults to true. The user of sold_between can as easily add the scope themselves. This is simpler and more explicit.
scope :sold_between, -> (start_date, end_date) {
where(
"start_date <= :start_date and end_date >= :end_date",
{ start_date: start_date, end_date: end_date }
)
}
# All cars
Cars.sold_between(start, end)
# Without used cars
Cars.sold_between(start, end).exclude_used_cars

Rails scope filter by date range

There are many questions relate to rails date range problem but mine is a little more complicated.
I have two models: house and booking. A House has_many bookings. A Booking has two attributes in date format: check_in and check_out.
What I want to achieve: Giving a valid date range, show all houses that are available during this range. In detail:
The start date of the range should not be in any booking.
The end date of the range should not be in any booking.
There should not be any booking between the start and the end.
Can this be done using the rails scope?
UPDATE:
I found the code below that can check scope date interval that overlaps.
named_scope :overlapping, lambda { |interval| {
:conditions => ["id <> ? AND (DATEDIFF(start_date, ?) * DATEDIFF(?, end_date)) >= 0", interval.id, interval.end_date, interval.start_date]
}}
How can I transfer this to my problem?
scope :overlapping, (lambda do |start_date, end_date|
House.includes(:bookings).where("bookings.check_in < ? AND bookings.check_out > ?",
start_date, end_date).references(:bookings).uniq
end)
I went ahead and deleted the >= and <= operators in favor of > and < to explicitly show these bookings being outside of the given range, but you can adjust them per your needs!
Update
Changed query to use #includes instead of #joins, since we're querying the attached table.
Yes it is possible to have this query through scope. Put this scope in house model.
scope :overlapping, -> (start_date, end_date) {
includes(:bookings).where('bookings.check_in < ? AND bookings.check_out > ?',
start_date.to_date, end_date.to_date)
}
And call as House.overlapping('2015-07-01', '2015-07-09')

Find and display nearest date in RoR

I am new to ruby on rails and I'm not sure where to start with this. I have a model for users, and one for projects. Users have many projects, and projects have one user. There is an end_date column in the projects table (as well as a name column).
What I want to do is find the project with the nearest end_date and display it's name and end date on the user's show page.
I tried putting this code in the projects controller, but I do not know if it is working, because I don't know how to access it and display the project name in the view.
def next_deadline(after = DateTime.now, limit = 1)
find(:all, :conditions => ['end_date > ?', after], :limit => limit)
end
Any help would be appreciated. Let me know if more information is needed.
As #Dan mentioned, you do need the :order clause to get the first one, but you should add it to your query and not replace the :conditions (otherwise you'll get the project with the earliest end_date irrespective of your after argument). The way you're defining this method is a bit off though. It should be defined in your Project model (and definitely not the controller) as a class method, or, what I think is a better approach, as a scope. In Rails < 3 (which it seems that you're using):
class Project < ActiveRecord::Base
named_scope :next_deadline, Proc.new { |after = DateTime.now, limit = 1| {:conditions => ['end_date > ?', after], :order => "end_date ASC", :limit => limit} }
...
end
Or in Rails >= 3:
class Project < ActiveRecord::Base
scope :next_deadline, Proc.new { |after = DateTime.now, limit = 1| where('end_date > ?', after).order("end_date ASC").limit(limit) }
...
end
Also, you can always test this kind of code using the Rails console: script/console in Rails < 3, rails c in Rails >= 3.
#projects = Project.find_by_sql("SELECT projects.* FROM projects
JOIN users ON users.id = projects.user_id AND projects.user_id = " + #user.id.to_s + "
WHERE projects.end_date > now()
ORDER BY projects.end_date ASC
LIMIT " + limit)
or
#projects = Project.where(:user_id => #user.id)
.where("end_date > ?", DateTime.now)
.order("end_date ASC")
You want to use :order, not :conditions.
Model.find(:all , :order => "end_date ASC")
Then the first result will be the item with the closest end_date
As Dan said, the condition you wrote won't get the nearest end date, but the dates that are greater than today, or the date passed in as a parameter.
In your User model you could write
def next_deadline_project
self.projects.first
end
as long as you give projects a default scope that orders records by end_date
In order to show information on the view you must set it in an instance variable in the User's controller show method. Instance variables are passed to views and you can access them to display the data.
#project = next_deadline_project
And in your show.html.erb you can use something like:
<%= #project.name %> - <%= #project.end_date %>

Rails 3: How to merge queries or scopes for complex query?

I'm building an events app that is very simple, it has a title and start_date and end_date. I would like to filter my query by mixing some of the values, like: if the start_date has passed but the end_date has not, the event is active and should be displayed. If both dates have passed, it should be omitted, too. I think that scopes is the aswer, but I only was able to filter the records within the view using some methods shown below.
I really would like to filter the query that is passed to the controller (#events). I want to show all events that are active, have a future start_date, or a past start_date but are still in progress (Today's date is in range between start_date and end_date)
EDITED
I have made some scopes which return each part of the query. Chaining them actually substracts the results instead of merging them. So i have used this code and actually works do I do not know how solid or DRY this is. Looks kind of ugly to me... is this a decent way to merge queries in rails 3?
scope :active, where("active = ?", true)
scope :not_over_or_in_progress, lambda { where("start_date < ? AND end_date > ? OR end_date IS NULL AND start_date > ? OR end_date IS NOT NULL AND start_date > ?", Date.today, Date.today, Date.today, Date.today) }
scope :valid, not_over_or_in_progress.active.order("start_date DESC")
Try using scopes:
class Event < AR::Base
scope :active, lambda { |date| where("start_date < ? AND end_date > ?", date) }
scope :future, lambda { |date| where("end_date < ?", date }
...
end
# Console
> #active_events = Event.active(Date.today)
> #future_events = Event.future(Date.today)
See http://guides.rubyonrails.org/active_record_querying.html

Selecting table entries where a given date is between the :start date and :end date

I have an object that has a start date and an end date, in order to represent the time that the object is valid.
Given a date, is there a way to only select those objects that have valid ranges that contain the date?
I tried fiddling with between, but couldn't get the syntax right.
Thanks!
This is often implemented using a named scope that does the appropriate restriction that identifies which records are visible at the current point in time:
class MyRecord < ActiveRecord::Base
named_scope :visible,
:conditions => 'visible_from<=UTC_TIMESTAMP() AND visible_to>=UTC_TIMESTAMP'
end
This can be altered to use place-holders for more arbitrary dates:
class MyRecord < ActiveRecord::Base
named_scope :visible_at, lambda { |date| {
:conditions => [
'visible_from<=? AND visible_to>=?',
date, date
]
}}
end
Presumably your dates are stored as UTC, as it is a considerable nuisance to convert from one local-time to another for the purposes of display.
You can select all visible models like this:
#records = MyRecord.visible.all
#records = MyRecord.visible_at(2.weeks.from_now)
If you were doing this for "given_date".
select *
from table
where start_date <= given_date
and end_date >= given_date
This is how you'd do it using active record.
Foo.find(:all, :conditions => ['valid_from <= ? and valid_to >= ?', valid_date, valid_date])

Resources