Rails: How to loop through month? - ruby-on-rails

I made a scope help me to select objects
scope :best_of_the_month, ->(year, month) do
time = Time.new(year, month)
start_time = time.beginning_of_month
end_time = time.end_of_month
where("created_at > ? AND created_at < ?", start_time, end_time).where("likes > ?", 15).where("rating > ?", 4.85).order_by_rating.to_a.uniq(&:author)
end
Then, I want to loop through this method, from 2014/1 to now. How can I do it?
Maybe something like this:
start_date = Date.create(2014,1).month
end_date = Date.today.month
#monthly_videos = []
(start_date..end_date).each do |year, month|
videos = Video.best_of_the_month(year, month)
#monthly_videos << videos
end
I find a solution here, How to Loop through Months in Ruby on Rails. But it seems about looping through the days. Not the month

With the best_of_the_month scope defined to take month and year as params, the following code should work:
date = Date.new(2014,1,1)
#monthly_videos = []
while true
videos = Video.best_of_the_month(date.year, date.month)
#monthly_videos << videos
date += 1.month
break if date == Date.today.beginning_of_month
end

You could use the Date#next_month method
date = Date.new(2014,1,1)
final_date = Date.new(Date.today.year, Date.today.month)
#monthly_video = []
loop do
#monthly_videos << Video.best_of_the_month(date.year, date.month)
break if date == final_date
date = date.next_month
end

Related

How to get user's birthday based on day and month only

I want to have a member list for today's birthday and upcoming birthday(tommorow).
In users.controller.rb
def birthday
#birthday_users = User.all.order("created_at DESC").where(:birthday => Date.today)
end
def upcoming_birthday
#upcoming_birthday_user = User.all.order("created_at DESC").where(:birthday => 1.days.from_now )
end
These codes work only when the day, month and year are the same as today.
Example:
User 1's birthday = October 3 2018 (Showing on the birthday list)
User 2's birthday = October 3 2000 (Not showing on the birthday list)
In User model
PostgreSql
scope :today_birthday, { where('EXTRACT(month FROM birthday) = ? AND EXTRACT(day FROM birthday) = ?', Date.today.month, Date.today.day).order("created_at DESC") }
scope :upcoming_birthday, { where('EXTRACT(month FROM birthday) = ? AND EXTRACT(day FROM birthday) = ?', 1.days.from_now.month, 1.days.from_now.day).order("created_at DESC") }
MySql
scope :today_birthday, { where('MONTH(birthday) = ? AND DAY(birthday) = ?', Date.today.month, Date.today.day).order("created_at DESC") }
scope :upcoming_birthday, { where('MONTH(birthday) = ? AND DAY(birthday) = ?', 1.days.from_now.month, 1.days.from_now.day).order("created_at DESC") }
I'm assuming you want to find all users having a birthday today. So, you might want to ignore year and only consider month and day while querying. The below code might help you with that:
def birthday
#birthday_users = User.where('EXTRACT(month FROM birthday) = ? AND EXTRACT(day FROM birthday) = ?', Date.today.strftime("%m"), Date.today.strftime("%d"))
end
def upcoming_birthday
tomorrow = Date.today + 1.day
#upcoming_birthday_users = User.where('EXTRACT(month FROM birthday) = ? AND EXTRACT(day FROM birthday) = ?', tomorrow.strftime("%m"), tomorrow.strftime("%d"))
end

Rails AR Date Range Query - Daylight Saving - Overlap

I have a Rails 3 application, how would one avoid an overlap happening due to daylight saving?
My problem is that I am having a form that generate reports. Auditing an inconsistency I noticed that a bunch of transactions show up in the week ending in March 11th also show up in the Week starting on March 12th.
The problem boils down to some thing like this...
Time.zone.parse('2018-03-11').to_datetime.end_of_day.utc
=> Mon, 12 Mar 2018 07:59:59 +0000
Time.zone.parse('2018-03-12').to_datetime.beginning_of_day.utc
=> Mon, 12 Mar 2018 07:00:00 +0000
The 1 hour overlap above seem to be where my problem lies. When checking date ranges (see actual code below) how can I avoid this overlap.
Actual Code
Here is the actual code that resemble filtering by date.
scope :filter_date, lambda { |starts, ends, date, transaction_type = :transaction|
_scope = scoped
starts = Time.zone.parse(starts).to_datetime if starts.class == String and starts.present?
ends = Time.zone.parse(ends).to_datetime.tomorrow if ends.class == String and ends.present?
begin
case date
when 'settled'
transaction_type == "batch" ? date_field = 'deposited_at' : date_field = 'settled_at'
_scope = _scope.order('transactions.'+date_field+' DESC')
_scope = _scope.where("transactions."+date_field+" >= ?", starts) if starts.present?
_scope = _scope.where("transactions."+date_field+" < ?", ends) if ends.present?
else # created, nil, other
_scope = _scope.order('transactions.created_at DESC')
_scope = _scope.where("transactions.created_at >= ?", starts) if starts.present?
_scope = _scope.where("transactions.created_at < ?", ends) if ends.present?
end
end
_scope
}
Stack
Ruby 2.1
Rails 3.2
PG
Question
How can I overcome this overlap of time where the daylight saving takes effect.
Try this solution. I think it should solve your issue:
scope :filter_date, lambda { |starts, ends, date, transaction_type = :transaction|
_scope = scoped
if starts.class == String and starts.present?
starts = Time.zone.parse(starts)
starts += 1.hour if starts.dst?
starts = starts.to_datetime
end
if ends.class == String and ends.present?
ends = Time.zone.parse(ends) + 1.day
ends += 1.hour if ends.dst?
ends = ends.to_datetime
end
begin
case date
when 'settled'
transaction_type == "batch" ? date_field = 'deposited_at' : date_field = 'settled_at'
_scope = _scope.order('transactions.'+date_field+' DESC')
_scope = _scope.where("transactions."+date_field+" >= ?", starts) if starts.present?
_scope = _scope.where("transactions."+date_field+" < ?", ends) if ends.present?
else # created, nil, other
_scope = _scope.order('transactions.created_at DESC')
_scope = _scope.where("transactions.created_at >= ?", starts) if starts.present?
_scope = _scope.where("transactions.created_at < ?", ends) if ends.present?
end
end
_scope
}

Greater/Lower than works but equals doesn't on >= and <= on .where()

I have a Rails 4 app and I'm trying to make a simple search for my invoices with 3 optional arguments: Name of the client, Start Date, End Date.
The search works fine mostly, if I put a start date and an end date it works for < and >, but eventhough i used >= and <=, if the invoice date is the same to either start or end, it just won't show on the result list.
The tables used look like this:
Client Table
ID
Name
The rest of the fields aren't necessary
Invoice Table
ID
Client_ID
Total_Price
Created_At *only here for relevance*
My Invoice Controller Search method looks like this:
def search
if request.post?
#count = 0
#invoices = Invoice.all
if params[:start_date].present?
#invoices = Invoice.invoices_by_date(#invoices, params[:start_date], 'start')
if #invoices.present?
#count = 1
else
#count = 2
end
end
if params[:end_date].present?
#invoices = Invoice.invoices_by_date(#invoices, params[:end_date], 'end')
if #invoices.present?
#count = 1
else
#count = 2
end
end
if params[:name].present?
#invoices = Invoice.invoices_by_client(#invoices, params[:name])
if #invoices.present?
#count = 1
else
#count = 2
end
end
if #count == 2
flash.now[:danger] = "No results found."
#invoices = nil
end
#name = params[:name]
#start_date = params[:start_date]
#end_date = params[:end_date]
end
end
And the Invoice Model methods i use look like this:
def self.invoices_by_client(invoices, name)
invoices= invoices.includes(:client)
.select('invoices.created_at', 'invoices.total_price', 'clients.name')
.where("clients.name LIKE ?", "%#{name}%")
.references(:client)
return invoices
end
def self.invoices_by_date(invoices, date, modifier)
if modifier == 'start'
invoices = invoices.includes(:client)
.select('invoices.created_at', 'invoices.total_price', 'clients.name')
.where("invoices.created_at >= ?", date)
.references(:client)
elsif modifier == 'end'
invoices = invoices.includes(:client)
.select('invoices.created_at', 'invoices.total_price', 'clients.name')
.where("invoices.created_at <= ? ", date)
.references(:client)
end
return invoices
end
It probably isn't the best solution overall and I don't know if i did anything wrong so it would be great if you guys could help me with this.
I followed Alejandro's advice and messed around with the time aswell as the date, something like this:
if modifier == 'start'
invoices = invoices.includes(:client)
.select('invoices.created_at', 'invoices.total_price', 'clients.name')
.where("invoices.created_at >= ?", "#{date} 00:00:00") // Added the start time
.references(:client)
elsif modifier == 'end'
invoices = invoices.includes(:client)
.select('invoices.created_at', 'invoices.total_price', 'clients.name')
.where("invoices.created_at <= ? ", "#{date} 23:59:59") // Added end time aswell
.references(:client)
end
I forced the time for the start date as 00:00:00 and the time for the end date as 23:59:59 and it worked as desired. Thank you for the help man and i hope this helps other people!

For a given period, getting the smallest list of dates, using jokers

I use Elasticsearch where I have one index per day, and I want my Ruby on Rails application to query documents in a given period by specifying the smallest and most precise list of indices.
I can't find the code to get that list of indices. Let me explain it:
Consider a date formatted in YYYY-MM-DD.
You can use the joker * at the end of the date string. E.g. 2016-07-2* describes all the dates from 2016-07-20 to 2016-07-29.
Now, consider a period represented by a start date and an end date.
The code must return the smallest possible array of dates representing the period.
Let's use an example. For the following period:
start date: 2014-11-29
end date: 2016-10-13
The code must return an array containing the following strings:
2014-11-29
2014-11-30
2014-12-*
2015-*
2016-0*
2016-10-0*
2016-10-10
2016-10-11
2016-10-12
2016-10-13
It's better (but I'll still take a unoptimized code rather than nothing) if:
The code returns the most precise list of dates (i.e. doesn't return dates with a joker that describes a period starting before the start date, or ending after the end date)
The code returns the smallest list possible (i.e. ["2016-09-*"] is better than ["2016-09-0*", "2016-09-1*", "2016-09-2*", "2016-09-30"]
Any idea?
Okay, after more thinking and the help of a coworker, I may have a solution. Probably not totally optimized, but still...
def get_indices_from_period(start_date_str, end_date_str)
dates = {}
dates_strings = []
start_date = Date.parse(start_date_str)
end_date = Date.parse(end_date_str)
# Create a hash with, for each year and each month of the period: {:YYYY => {:MMMM => [DD1, DD2, DD3...]}}
(start_date..end_date).collect do |date|
year, month, day = date.year, date.month, date.day
dates[year] ||= {}
dates[year][month] ||= []
dates[year][month] << day
end
dates.each do |year, days_in_year|
start_of_year = Date.new(year, 1, 1)
max_number_of_days_in_year = (start_of_year.end_of_year - start_of_year).to_i + 1
number_of_days_in_year = days_in_year.collect{|month, days_in_month| days_in_month}.flatten.size
if max_number_of_days_in_year == number_of_days_in_year
# Return index formatted as YYYY-* if full year
dates_strings << "#{year}-*"
else
days_in_year.each do |month, days_in_month|
formatted_month = format('%02d', month)
if Time.days_in_month(month, year) == days_in_month.size
# Return index formatted as YYYY-MM-* if full month
dates_strings << "#{year}-#{formatted_month}-*"
else
decades_in_month = {}
days_in_month.each do |day|
decade = day / 10
decades_in_month[decade] ||= []
decades_in_month[decade] << day
end
decades_in_month.each do |decade, days_in_decade|
if (decade == 0 && days_in_decade.size == 9) ||
((decade == 1 || decade == 2) && days_in_decade.size == 10)
# Return index formatted as YYYY-MM-D* if full decade
dates_strings << "#{year}-#{formatted_month}-#{decade}*"
else
# Return index formatted as YYYY-MM-DD
dates_strings += days_in_decade.collect{|day| "#{year}-#{formatted_month}-#{format('%02d', day)}"}
end
end
end
end
end
end
return dates_strings
end
Test call:
get_indices_from_period('2014-11-29', '2016-10-13')
=> ["2014-11-29", "2014-11-30", "2014-12-*", "2015-*", "2016-01-*", "2016-02-*", "2016-03-*", "2016-04-*", "2016-05-*", "2016-06-*", "2016-07-*", "2016-08-*", "2016-09-*", "2016-10-0*", "2016-10-10", "2016-10-11", "2016-10-12", "2016-10-13"]

Donations over past 24 months with keys and sums

Having pulled donations from the past two years, I'm trying to derive the sum of those donations per month, storing the keys (each month) and the values (the sum of donations for each month) in an array of hashes. I would like the keys to be numbers 1 to 24 (1 being two years ago and 24 being this month) and if there are no donations for a given month, the value would be zero for that month. How would I do this as an array of hashes in Ruby/Rails?
This is my variable with the donations already in it.
donations = Gift.where(:date => (Date.today - 2.years)..Date.today)
the following gives you a hash, with keys '2013/09" , etc...
monthly_donations = {}
date = Time.now
while date > 2.years.ago do
range = date.beginning_of_month..date.end_of_month
monthly_donations[ "{#date.year}/#{date.month}" ] = Giftl.sum(:column, :conditions => {created_at >= range})
date -= 30.days
end
To select the records in that time-span, this should be enough:
donations = Gift.where("date >= #{2.years.ago}")
you can also do this:
donations = Gift.where("date >= :start_date AND date <= :end_date",
{start_date: 2.years.ago, end_date: Time.now} )
See also: 2.2.1 "Placeholder Conditions"
http://guides.rubyonrails.org/active_record_querying.html
To sum-up a column in the database record, you can then do this:
sum = Gift.sum(:column , :conditions => {created_at >= 2.years.ago})
First, we need a function to find the difference in months from the current time.
def month_diff(date)
(Date.current.year * 12 + Date.current.month) - (date.year * 12 + date.month)
end
Then we iterate through #donation, assuming that :amount is used to store the value of each donation:
q = {}
#donations.each do |donation|
date = month_diff(donation.date)
if q[date].nil?
q[date] = donation.amount
else
q[date] += donation.amount
end
end
I found a good solution that covered all the bases--#user1185563's solution didn't bring in months without donations and #Tilo's called the database 24 times, but I very much appreciated the ideas! I'm sure this could be done more efficiently, but I created the hash with 24 elements (key: beginning of each month, value: 0) and then iterated through the donations and added their amounts to the hash in the appropriate position.
def monthly_hash
monthly_hash = {}
date = 2.years.ago
i = 0
while date < Time.now do
monthly_hash["#{date.beginning_of_month}"] = 0
date += 1.month
i += 1
end
return monthly_hash
end
#monthly_hash = monthly_hash
#donations.each do |donation|
#monthly_hash["#{donation.date.beginning_of_month}"] += donation.amount
end

Resources