scope :created_after, ->(start_time,end_time) { where("start_time > ? and end_time <?", start_time,end_time) }
I want to add if condition if start time present then only start_time query run if end_time then its query run
I think created_between is better name for what you want to do with your created_after scope.
You can create two helper scopes to filter results using start_time and end_time column.
scope :created_after, ->(start_time) do
where("start_time > ?", start_time)
end
scope :created_before, ->(end_time) do
where("end_time < ?", end_time)
end
Then you can combine them in a way you wanted:
scope :created_between, ->(start_time, end_time) do
query = self.all
query = query.created_after(start_time) if start_time
query = query.created_before(end_time) if end_time
query
end
If you execute query giving nil for any scope argument it will ignore them. For example: created_between(nil, nil) will result in no additional query.
You can just chain it in the condition
scope :created_after, ->(start_time, end_time) { start_time && end_time && where("start_time > ? and end_time <?", start_time, end_time) }
This will add the where condition only if start_time and end_time is present
Related
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
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.
I have this scope:
scope :in_range, lambda { |begin_time, end_time, excluded_sales = nil|
where(
'start_at BETWEEN ? AND ? OR finish_at BETWEEN ? AND ? OR ? BETWEEN start_at AND finish_at OR ? BETWEEN start_at AND finish_at',
begin_time, end_time, begin_time, end_time, begin_time, end_time)
.where.not(id: excluded_sales)
}
This produces
> Sale.in_range(Time.now, Time.now + 1)
Sale Load (0.6ms) SELECT "sales".* FROM "sales" WHERE (start_at BETWEEN '2014-12-12 10:45:47.065712' AND '2014-12-12 10:45:48.065714' OR finish_at BETWEEN '2014-12-12 10:45:47.065712' AND '2014-12-12 10:45:48.065714' OR '2014-12-12 10:45:47.065712' BETWEEN start_at AND finish_at OR '2014-12-12 10:45:48.065714' BETWEEN start_at AND finish_at) AND ("sales"."id" IS NOT NULL)
I need to rewrite this scope using Arel because I believe I should not use raw SQL syntax here.
But I do not know how to use in method with table column reference. I've tried this but got exception:
> s = Sale.arel_table
> begin_time = Time.now
> Sale.where(begin_time.in(s[:start_at]..s[:finish_at]))
--> ArgumentError: bad value for range
Also tried this, but had no luck:
> s = Sale.arel_table
> begin_time = Time.now
> Sale.where(begin_time.in('sales.start_time'..'sales.end_time')
--> NoMethodError: undefined method `round' for "sales.end_time".."sales.start_time":Range
Please advise how to refer a table column as clause condition with Arel?
Thank you!
UPDATED:
There are four conditions connected with OR statement:
start_at in range of begin_time and end_time
finish_at in range of begin_time and end_time
begin_time in range of start_time and finish_time
end_time in range of start_time and finish_time
Basically it is just a date ranges overlap.
I rewrote it to
scope :in_range, lambda { |date_range, excluded_sales = nil|
s = Sale.arel_table
where(
s.grouping(s[:starts_at].gteq(date_range.first).and(s[:starts_at].lteq(date_range.last)))
.or(s.grouping(s[:ends_at].gteq(date_range.first).and(s[:ends_at].lteq(date_range.last))))
.or(s.grouping(s[:starts_at].lteq(date_range.first).and(s[:ends_at].gteq(date_range.last))))
.or(s.grouping(s[:starts_at].gteq(date_range.first).and(s[:ends_at].lteq(date_range.last))))
).where.not(id: excluded_sales)
}
Maybe there is a simplier way to achieve my goal?
well as far as I understood, you wish to select all record, which range have an intersection to the specified one? If so, just select records, which have start_at field values that are before the end of range, and have finish_at field values that are after the begin of range. So with arel it should be:
scope :in_range, proc do |begin_time, end_time, excluded_sales|
where(arel_table[:start_at].lteq(end_time)
.and(arel_table[:finish_at].gteq(begin_time))
.and(arel_table[:id].not_eq(excluded_sales)))
end
I need to do a bunch of queries.
In this case I think I query a result by many times,
Not completed in one query.
How to make my search results can be done by one query ?
q = WeatherLog.nearby(100, longitude, latitude)
if start_time and end_time
#weather_logs = q.where(datetime: start_time..end_time)
elsif start_time
#weather_logs = q.where("datetime > ?", start_time)
elsif end_time
#weather_logs = q.where("datetime < ?", end_time)
end
#weather_logs = #weather_logs.order(datetime: :asc).first(2000)
The first thing to realize is that ActiveRecord does not execute a query until it aboultely has to (lazy loading). While there are a number of lines of code building up the query, the query is only executed on methods like .all, .each, .first etc. So from a performance standpoint your code is ok as your only executing one query to the database and not many.
However you can tweak the code to make it more human readable and maintainable:
class WeatherLog < ActiveRecord::Base
# ...
class << self
def between_times(times)
after_time(times[:start_time]).before_time(times[:end_time])
end
def after_time(time)
return self.all if time.nil?
where('datetime > ?', time)
end
def before_time(time)
return self.all if time.nil?
where('datetime < ?', time)
end
end
end
Using self.all effectively skips the query condition while still enabling query chaining. This makes it possible to remove of all the if/else logic. Then you can chain the queries (or create a helper method within WeatherLog):
WeatherLog.nearby(100, longitude, latitude).between_times(start_time: start_time, end_time: end_time)
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