handle ruby send nil - ruby-on-rails

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

Related

Ransack sort order scope with arguments

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.

Rails scopes, filtering all by date and child data

I have a model called "Campaign" and it belongs_to a "Schedule".
A Campaign has a start and end date, and the Schedule contains information such as "Every Monday", "Every day", etc.
I'm wanting to select all the Campaigns that are valid for a specific date.
# campaign.rb
scope :active, -> { where(status: true) }
scope :inactive, -> { where(status: false) }
def valid_for(date)
includes(:schedules).where("
( start_at >= ? AND (end_at IS NULL OR end_at <= ? ) )
AND schedules.days_of_week = ?",
date,
date,
date.wday
)
end
And in my Campaign spec (the Campaign and Schedule does exist in the test):
expect(Campaign.active.valid_for("2015-06-15".to_date).size).to eq 1
The error I'm getting is:
undefined method `valid_for' for #<ActiveRecord::Relation []>
I cannot use a class method for self.valid_for because I need to be able to access the attributes start_at and end_at, as well as the parent schedule. However, it doesn't seem to be working as an instance method either.
Make it a scope and use a scope argument:
scope :valid_for, ->(date) {
includes(:schedules).where("
( start_at >= ? AND (end_at IS NULL OR end_at <= ? ) )
AND schedules.days_of_week = ?,
start_at,
end_at,
date.wday
")
}
This will limit your access to the start_at and end_at though. You'll need to get those from the database or pass them in as args to your scope.

Dates and scopes are inconsistent

I have the following scope:
scope :this_month, :conditions => ["created_at >= ?", Date.today.beginning_of_month]
Which makes the SQL output of a.response_sets.this_month.to_sql:
SELECT "response_sets".* FROM "response_sets" WHERE created_at >= '2012-05-01'
But since today is actually June 1, that date seems wrong. So, I tried bypassing the scope and just doing a condition directly, like so:
a.response_sets.where(["created_at >= ?", Date.today.beginning_of_month]).to_sql
Which then, outputs:
SELECT "response_sets".* FROM "response_sets" WHERE created_at >= '2012-06-01'
Which is correct. So why is there a difference between doing Date.today.beginning_of_month in a scope and doing it directly in where?
When working with dates in scopes you should use a lambda so the scope gets evaluated every time it is called:
scope :this_month, -> { where("created_at >= ?", Date.today.beginning_of_month) }

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

Use the same parameters many times in a find conditions: hash

I have a model who holds 2 properties: valid_from and valid_to.
I need to select all instances that are currently valid, i.e. valid_from <= today and valid_to >= today.
i have the following find :
Mymodel.find(:all, :conditions => ["valid_from <= ? and valid_to >= ?", Date.today, Date.today])
I already thought about storing Date.today in a variable and calling that variable, but i still need to call it twice.
my_date = Date.today
Mymodel.find(:all, :conditions => ["valid_from <= ? and valid_to >= ?", my_date, my_date])
Is there a way to improve and do only one call to the variable to match all the "?" in the :conditions ?
thanks,
P.
I would use named_scope. In model add:
named_scope :valid,
:conditions =>
["valid_from <= ? and valid_to >= ?", Date.today, Date.today]
And then in your controller you can call:
#mymodels = Mymodel.valid
I think that focusing on reducing two calls to Date.today to only one call is wasting of time. It won't make your application faster or using less memory.
I'm not aware of a way to do what you're asking, but even if you could I don't think it would buy you much. I would create a named scope within your model class.
In this example, you can pass the date to the named scope, or it will default to today's date if no date is specified:
named_scope :by_valid_date, lambda { |*args|
{ :conditions => ["valid_from <= ? and valid_to >= ?",
(args.first || Date.today), (args.first || Date.today)]} }

Resources