Find model records against Time.now - ruby-on-rails

I have a model with 2 specific fields named start and end.
Those are dates I want to check against current date ( Time.now ) in order to get only records where current time is comprised between start and end stored values
I tried a few things such as
Model.all.find_by(start: <Time.now , end: >Time.now)
But I get syntax errors all the time. Sometimes the field name of the second date ( end ) is even mixed up with the 'end' keyword inside my controller.

You can try something like this:
Model.where('start < ? and end > ?', Time.now, Time.now)
By the way, all is redundant in this situation.

Related

Rails Model Next/Prev Navigation Scope with datetime field

I have a model lets say Post which has a column called published_at (datetime). My model has two instance methods called next and prev.
I'm using mysql as a database adapter.
def next
self.class.unscope(:order).online
.where('published_at > ?', published_at)
.order('published_at ASC').first
end
def prev
self.class.unscope(:order).online
.where('published_at < ?', published_at)
.order('published_at DESC').first
end
With the code above the navigation works if every post has another date. If some posts are on the same date just one record of those gets shown.
Goal: Get the next or previous record ordered by published_at datetime column.
Any ideas?
Neither "next" , nor "previous" make sense in SQL. You have to explicitly define sort order by uniq key.
If you have non-uniq column as sort base, you have to add uniq one, id for example.
Then you have to change WHERE clause to accept rows wich have same value for your non-uniq column:
def next
condition = <<~SQL
(published_at = :published_at AND id > :id) OR
(published_at > :published_at)
SQL
self.class.unscope(:order).
online.
where(condition, published_at: published_at, id: id).
order(published_at: :asc, id: :asc).
take
end
prev is constructed similarly.

The smallest and the largest possible date

I am creating a filtering partial view, where user can pick a from-date and a to-date using a calendar. These dates are used then within model scope to perform SQL Where clause in database query. If a user does not pick one of dates, the default value should be assigned: minimal available date for from and maximal for to.
unless params[:from].blank? and params[:to].blank?
from = begin Date.parse(params[:from]) rescue ??? end
to = begin Date.parse(params[:to]) rescue ??? end
#model_instances = #model_instances.start_end from, to
end
(...)
scope :start_end, -> (start_date, end_date) { where('(:start_date <= "from" AND "from" <= :end_date ) OR' +
'(:start_date <= "to" AND "to" <= :end_date ) OR' +
'("from" <= :start_date AND :end_date <= "to")',
{start_date: start_date, end_date: end_date}) }
The from and to model Date attributes are also database fields in related table.
In Rails, Date class has a family of methods beginning_of (day, week, month, etc.), but there are no methods such beginning_of_time, end_of_time, Date.min, Date.max.
Are there any methods to obtain the minimal and maximal dates?
You could skip the condition on start and end dates if no params is given:
if params[:from].present? and params[:to].present?
#model_instances.start_end(params[:from], params[:to])
end
And then you will get results not depending on dates since no from and/or end dates have been filled.
You could compare ranges of dates to your model's values and setup default values if params not passed.
#setup default if desired, or you can skip if params are blank
from = params[:from] || default_from
to = params[:to] || default_to
#model_instances.start_end(from, to)
and your scope would look something like this if you used date ranges for activerecord
scope :start_end, ->(start_date, end_date){where(from: start_date..end_date, to:start_date..end_date)}

Search records between two dates - Ruby on Rails

I am trying to create a search that returns records between two dates (today and a future date).
I can get it to return several records no problem if I use the following code in my model (film.rb):
def self.date_search(search_string)
self.where("release_date >= ?", search_string )
However, when I try something like the following, I receive syntax errors:
def self.date_search(search_string)
date = Date.today
self.where("release_date = created_at > date.strftime("%F") AND created_at < ? ", search_string )
I am still very new to Ruby so any help sorting out my syntax and code would be much appreciated.
Try:
def self.date_search(search_string)
self.where({release_date: Time.now..search_string})
end
This will give you entries where release_date is between the current time and the search_string (inclusive of the search string since you use two dots(..), it would be exclusive of the search string if you used three dots (...)

Rails collection of current + past 30 days

So I've got a collection called #events and I am defining it as
#organization.owned_events.current.reverse
However, what I would actually like to do is have not actually current, but anything current or expired in the last 30 days. I tried
#organization.owned_events.current-30.days.reverse
But this just provided an empty collection. I think this is because what I actually want is not current-30.days, but actually current AND current-30.days
Not sure how to write that though. Can I somehow define another term instead of current like net_thirty and make that equal current or within last 30 days?
In your event model you can do:
scope :from, ->(duration){ where('your_datetime_field_here > ?', Time.zone.now - duration ) }
Since you want to filter by a date or datetime field, previous option is for a datetime field, if you want to filter by a date field:
scope :from, ->(duration){ where('your_date_field_here > ?', Time.zone.today - duration ) }
->(){} is a block you are passing to the scope.
You would use these scopes like this:
any_events.from(30.days)
In your case:
#organization.owned_events.from(30.days)
You could also want to filter events not after today:
scope :from, ->(duration){ where('your_date_field_here BETWEEN ? AND ? ', Time.zone.today - duration, Time.zone.today ) }

Ruby on Rails 3 Check In/Check Out ranges by hour

I'm using Ruby on Rails 3 and I have a "visit" model which stores a check_in and check_out datetime and I need to search through visits in a general date range and count the number of "visitors present" grouped by all hours of the day.
...i.e. I need something like:
8:00am - 8:59am : 12 visitors
9:00am - 9:59am : 5 visitors
10:00am - 10:59am : 4 visitors
...given a table of visits with a check in and check out time stored.
The idea is to take check-in and check-out times for "visits" and then determine how many visitors (assuming each visit logs one visitor, which it does by policy) were visiting during any given hour of the day in order to find out peak visiting times.
I've tried setting up queries like:
eight_am_visits = Visit.where("EXTRACT(HOUR_MINUTE FROM check_in) <= 859").where("EXTRACT(HOUR_MINUTE FROM check_out) >= 800")
...and haven't quite hit on it because Rails stores dates in such an odd fashion (in UTC, which it will convert on database query) and it doesn't seem to be doing that conversion when I use something like EXTRACT in SQL...
...any idea how I can do this?
Looks like you're not actually interested in the Visit objects at all. If you just want a simple summary then push AR out of the way and let the database do the work:
# In visit.rb
def self.check_in_summary(date)
connection.select_rows(%Q{
select extract(hour from check_in), count(*)
from visits
where cast(check_in as date) = '#{date.iso8601}'
group by extract(hour from check_in)
}).inject([ ]) do |a, r|
a << { :hour => r[0].to_i, :n => r[1].to_i }
end
end
Then a = Visit.check_in_summary(Date.today - 1) will give you the summary for yesterday without doing any extra work. That demo implementation will, of course, have holes in the array for hours without any checkins but that is easy to resolve (if desired):
def self.check_in_summary(date)
connection.select_rows(%Q{
select extract(hour from check_in), count(*)
from visits
where cast(check_in as date) = '#{date.iso8601}'
group by extract(hour from check_in)
}).each_with_object([0]*24) do |r, a| # Don't forget the arg order change!
a[r[0].to_i] = r[1].to_i
end
end
That version returns an array with 24 elements (one for each zero-based hour) whose values are the number of checkins within that hour.
Don't be afraid to drop down to SQL when it is convenient, AREL is just one tool and you should have more than one tool in your toolbox. Also, don't be afraid to add extra data mangling and summarizing methods to your models, your models should have an interface that allows you to clearly express your intent in the rest of your code.
Maybe something like that?!
t = Time.now
eight_am_visits = Visit.all(:conditions => ['check_in > ? and check_in < ?', Time.utc(t.year, t.month, t.day, 8), Time.utc(t.year, t.month, t.day, 8, 59)])
EDIT:
Or you can grab all visits by day and filter it in Rails:
t = Time.now
visits = Visit.all(:conditions => ['created_at > ? and created_at < ?', Time.utc(t.year, t.month, t.day - 1), Time.utc(t.year, t.month, t.day + 1)])
visits_by_hour = []
(0..23).each do |h|
visits_by_hour << visits.map {|e| e if e.created_at > Time.utc(t.year, t.month, t.day, h) && e.created_at < Time.utc(t.year, t.month, t.day, h, 59)}.count
end
And in view:
<% visits_by_hour.each_with_index do |h, v| %>
<%= "#{h}:00 - #{h}:59: #{v} visitors" %>
<% end %>
Thanks for your help Olexandr and mu, I managed to figure something out with the insight you gave me here.
I came up with this, and it seems to work:
#grab the data here, this is nice because
#I can get other stats out of it (which I don't show here)
#visits = Visit.where(:check_in => #start_date..#end_date, :check_out => #start_date..#end_date).where("check_out IS NOT NULL");
#Here we go
#visitors_present_by_hour = {}
(0..23).each do |h|
# o.o Ooooooh.... o_o Hee-hee! ^_^
#visitors_present_by_hour[h] = #visits.collect{|v| v.id if v.check_in.hour <= h and v.check_out.hour >= h}.compact.count
end
Then I can just dump out that hash in my view.
It seems the solution was a bit simpler than I thought, and doing it this way actually makes rails do the time conversions from UTC.
So, I could just collect all the visits which have hours in the hour range, then compact out the nils and count what's left. I was surprised once I hit on it. I didn't need any custom SQL at all as I thought I would (unless this is completely wrong, but it seems to be working with some test data).
Thanks guys!

Resources