How to increment counter in Ruby loop - ruby-on-rails

For my Rails application I am trying to build a rake task that will populate the database with 10 invoices for each user:
def make_invoices
User.all.each do |user|
10.times do
date = Date.today
i = user.invoices.create!(:number => user.next_invoice_number,
:date => date += 1.year)
i.save
end
end
end
How can I increment the date by one year?

Change your loop to:
10.times do |t|
puts t + 1 # will puts 1,2,3,4,5,6,7,8,9,10
end
And now you can set your year.

Here it is using Date#next_year:
require 'date'
d = Date.today
d # => #<Date: 2013-09-21 ((2456557j,0s,0n),+0s,2299161j)>
d.next_year # => #<Date: 2014-09-21 ((2456922j,0s,0n),+0s,2299161j)>

def make_invoices
User.all.each do |user|
date = Date.today
10.times do
user.invoices.create!(:number => user.next_invoice_number,
:date => (date += 1.year))
end
end
end
Because you have date = Date.today in the 10.times loop, it will be reseted in each loop. Just move date = Date.today outside the loop.

You can take advantage of Date#next_year, which takes a parameter meaning how many years you want to advance :
def make_invoices
User.all.each do |user|
10.times.with_object(Date.today) do |i, date|
user.invoices.create!(
number: user.next_invoice_number,
date: date.next_year(i)
)
end
end
end
Numeric#times pass an index to block.
Enumerator#with_object allow to pass additional parameters to block, which we here use to avoid setting a local variable outside the block (since we don't need it outside).

Related

rake task to expire customers points balance

i am trying to work out how to write a rake tasks that will run daily and find where the days remaining is 0 to update the column amount to zero.
I have the following methods defined in my model, though they don't exactly appear to be working as I am getting the following error in the view
undefined method `-#' for Mon, 27 Jun 2016:Date
def remaining_days
expired? ? 0 : (self.expire_at - Date.today).to_i
end
def expired?
(self.expire_at - Date.today).to_i <= 0
end
def expire_credits
if expired?
self.update(:expire_at => Date.today + 6.months, :amount => 0)
end
end
with the rake tasks i have never written of these and i thought i would be able to call a method of StoreCredit that would expire the points if certain conditions are met but i am not sure how this all works
task :expire_credits => :environment do
puts 'Expiring unused credits...'
StoreCredit.expire_credits
puts "done."
end
# model/store_credit.rb
# get all store_credits that are expired on given date, default to today
scope :expire_on, -> (date = Date.current) { where("expire_at <= ?", date.beginning_of_day) }
class << self
def expire_credits!(date = Date.current)
# find all the expired credits on particular date, and update all together
self.expire_on(date).update_all(amount: 0)
end
end
Since it's a rake task, I think it's more efficient to update all expired ones together
#rake file
result = StoreCredit.expire_credits!
puts "#{result} records updated"
Retrieve Record Count Update
class << self
def expire_credits!(date = Date.current)
# find all the expired credits on particular date, and update all together
records = self.expire_on(date)
records.update_all(amount: 0)
records.length
end
end
You call class method but define instance method. You will need to define class method:
def self.expire_credits

How to define time intervals in Rails?

There is the following task: I need to store in db (PostgreSQL) some time intervals; for example, 1 PM - 2 PM in order to I can test if some time exists in some interval. How should I do it properly? May be there is some gem for it? Thanks for it.
You can save the columns as #Santosh said and then if you need to check something against a time range you can use this useful class:
Time-of-day range in Ruby?
class TimeRange
private
def coerce(time)
time.is_a? String and return time
return time.strftime("%H:%M")
end
public
def initialize(start,finish)
#start = coerce(start)
#finish = coerce(finish)
end
def include?(time)
time = coerce(time)
#start < #finish and return (#start..#finish).include?(time)
return !(#finish..#start).include?(time)
end
end
which can be used this way:
irb(main):013:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,02,30))
=> true
irb(main):014:0> TimeRange.new("02:00","01:00").include?(Time.mktime(2010,04,01,01,30))
=> false
irb(main):015:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,01,30))
=> true
irb(main):016:0> TimeRange.new("01:00","02:00").include?(Time.mktime(2010,04,01,02,30))
=> false

Rails: controller won't update model correctly

I apologize in advance, this is going to be a long question.
Short version:
I have a Meeting model that has a date, start_time, and end_time. These are time objects, which of course are a pain for users to input, so I'm using virtual attributes to accept strings which are parsed by Chronic before save.
I have a plain vanilla rails controller that receives these virtual attributes from the form and passes them along to the model. Here is the controller:
def create
#meeting = #member.meetings.build(params[:meeting])
if #meeting.save
redirect_to member_meetings_path(#member), :notice => "Meeting Added"
else
render :new
end
end
def update
#meeting = #member.meetings.find(params[:id])
if #meeting.update_attributes(params[:meeting])
redirect_to member_meetings_path(#member), :notice => "Meeting Updated"
else
render :new
end
end
I've verified that the controller receives the correct parameters from the form, for instance params[:meeting][:date_string] is set as expected.
Problems:
On create, the date gets set correctly, but the times are assigned to the year 2000, set in UTC, and won't display in local time on the front end.
On update, the date won't update. The times update but stay in UTC for 2000-01-01.
Longer Version
What makes this super bizarre to me is I have decent test coverage indicating all of this works at the model layer.
Here is the model:
# DEPENDENCIES
require 'chronic'
class Meeting < ActiveRecord::Base
# MASS ASSIGNMENT PROTECTION
attr_accessible :name, :location, :description, :contact_id, :member_id, :time_zone,
:date, :start_time, :end_time, :date_string, :start_time_string, :end_time_string
# RELATIONSHIPS
belongs_to :member
belongs_to :contact
# CALLBACKS
before_save :parse_time
# Time IO Formatting
attr_writer :date_string, :start_time_string, :end_time_string
# Display time as string, year optional
def date_string(year=true)
if date
str = "%B %e"
str += ", %Y" if year
date.strftime(str).gsub(' ',' ')
else
""
end
end
# Display time as string, AM/PM optional
def start_time_string(meridian=true)
if start_time
str = "%l:%M"
str += " %p" if meridian
start_time.strftime(str).lstrip
else
""
end
end
# Display time as string, AM/PM optional
def end_time_string(meridian=true)
if end_time
str = "%l:%M"
str += " %p" if meridian
end_time.strftime(str).lstrip
else
""
end
end
# Display Date and Time for Front-End
def time
date.year == Date.today.year ? y = false : y = true
start_time.meridian != end_time.meridian ? m = true : m = false
[date_string(y),'; ',start_time_string(m),' - ',end_time_string].join
end
private
# Time Input Processing, called in `before_save`
def parse_time
set_time_zone
self.date ||= #date_string ? Chronic.parse(#date_string).to_date : Date.today
self.start_time = Chronic.parse #start_time_string, :now => self.date
self.end_time = Chronic.parse #end_time_string, :now => self.date
end
def set_time_zone
if time_zone
Time.zone = time_zone
elsif member && member.time_zone
Time.zone = member.time_zone
end
Chronic.time_class = Time.zone
end
end
Here is the spec. Note that to test the parse_time callback in isolation I'm calling #meeting.send(:parse_time) in these tests whenever I'm not actually creating or updating a record.
require "minitest_helper"
describe Meeting do
before do
#meeting = Meeting.new
end
describe "accepting dates in natural language" do
it "should recognize months and days" do
#meeting.date_string = 'December 17'
#meeting.send(:parse_time)
#meeting.date.must_equal Date.new(Time.now.year,12,17)
end
it "should assume a start time is today" do
#meeting.start_time_string = '1pm'
#meeting.send(:parse_time)
#meeting.start_time.must_equal Time.zone.local(Date.today.year,Date.today.month,Date.today.day, 13,0,0)
end
it "should assume an end time is today" do
#meeting.end_time_string = '3:30'
#meeting.send(:parse_time)
#meeting.end_time.must_equal Time.zone.local(Date.today.year,Date.today.month,Date.today.day, 15,30,0)
end
it "should set start time to the given date" do
#meeting.date = Date.new(Time.now.year,12,1)
#meeting.start_time_string = '4:30 pm'
#meeting.send(:parse_time)
#meeting.start_time.must_equal Time.zone.local(Time.now.year,12,1,16,30)
end
it "should set end time to the given date" do
#meeting.date = Date.new(Time.now.year,12,1)
#meeting.end_time_string = '6pm'
#meeting.send(:parse_time)
#meeting.end_time.must_equal Time.zone.local(Time.now.year,12,1,18,0)
end
end
describe "displaying time" do
before do
#meeting.date = Date.new(Date.today.year,12,1)
#meeting.start_time = Time.new(Date.today.year,12,1,16,30)
#meeting.end_time = Time.new(Date.today.year,12,1,18,0)
end
it "should print a friendly time" do
#meeting.time.must_equal "December 1; 4:30 - 6:00 PM"
end
end
describe "displaying if nil" do
it "should handle nil date" do
#meeting.date_string.must_equal ""
end
it "should handle nil start_time" do
#meeting.start_time_string.must_equal ""
end
it "should handle nil end_time" do
#meeting.end_time_string.must_equal ""
end
end
describe "time zones" do
before do
#meeting.assign_attributes(
time_zone: 'Central Time (US & Canada)',
date_string: "December 1, #{Time.now.year}",
start_time_string: "4:30 PM",
end_time_string: "6:00 PM"
)
#meeting.save
end
it "should set meeting start times in the given time zone" do
Time.zone = 'Central Time (US & Canada)'
#meeting.start_time.must_equal Time.zone.local(Time.now.year,12,1,16,30)
end
it "should set the correct UTC offset" do
#meeting.start_time.utc_offset.must_equal -(6*60*60)
end
after do
#meeting.destroy
end
end
describe "updating" do
before do
#m = Meeting.create(
time_zone: 'Central Time (US & Canada)',
date_string: "December 1, #{Time.now.year}",
start_time_string: "4:30 PM",
end_time_string: "6:00 PM"
)
#m.update_attributes start_time_string: '2pm', end_time_string: '3pm'
Time.zone = 'Central Time (US & Canada)'
end
it "should update start time via mass assignment" do
#m.start_time.must_equal Time.zone.local(Time.now.year,12,1,14,00)
end
it "should update end time via mass assignment" do
#m.end_time.must_equal Time.zone.local(Time.now.year,12,1,15,00)
end
after do
#m.destroy
end
end
end
I have even specifically mixed in creating and updating records via mass assignment in later test methods to ensure that those work as expected. All those tests pass.
I appreciate any insight into the following:
Why doesn't the date update in the controller#update action?
Why aren't times getting the year from the date that is set? This works in the model and in specs, but not when submitted via form through the controller.
Why don't times get set to the time zone that is passed in from the form? Again, these specs pass, what is wrong on the controller?
Why won't times display in their time zone on the front end?
Thanks for the help, I feel like I must be losing the forest for the trees on this one as I've been going at it for hours.
Update:
Thanks to the help of AJcodez, I saw some of the issues:
Was assigning date wrong, thanks AJ! Now using:
if #date_string.present?
self.date = Chronic.parse(#date_string).to_date
elsif self.date.nil?
self.date = Date.today
end
I was using Chronic correctly, my mistake was at the database layer! I set the fields in the database to time instead of datetime, which ruins everything. Lesson to anyone reading this: never ever use time as a database field (unless you understand exactly what it does and why you're using it instead of datetime).
Same problem as above, changing the fields to datetime fixed the problem.
The problem here has to do with accessing time in the model vs. the view. If I move these time formatting methods into a helper so they're called in the current request scope they will work correctly.
Thanks AJ! Your suggestions got me past my blind spot.
Well here goes..
1 . Why doesn't the date update in the controller#update action?
I see two potential issues. Looks like you're not parsing the dates again. Try this:
def update
#meeting = #member.meetings.find(params[:id])
#meeting.assign_attributes params[:meeting]
#meeting.send :parse_time
if #meeting.save
...
assign_attributes sets but doesnt save new values: http://apidock.com/rails/ActiveRecord/AttributeAssignment/assign_attributes
Also, in your parse_time method, you use this assignment: self.date ||= which will always set self.date back to itself if it is assigned. In other words you can't update the date unless its falsey.
2 . Why aren't times getting the year from the date that is set? This works in the model and in specs, but not when submitted via form through the controller.
No idea, looks like you are using Chronic#parse correctly.
3 . Why don't times get set to the time zone that is passed in from the form? Again, these specs pass, what is wrong on the controller?
Try debugging time_zone and make sure it is returning whats in params[:meeting][:time_zone]. Again it looks correct by Chronic.
Side note: if you pass an invalid string to Time#zone= it will blow up with an error. For instance Time.zone = 'utc' is all bad.
4 . Why won't times display in their time zone on the front end?
See Time#in_time_zone http://api.rubyonrails.org/classes/Time.html#method-i-in_time_zone and just explicitly name your time zone every time.
Not sure if you're already doing this, but try to explicitly save Times in UTC on the database, and then display them in local time.

Get random time objects between certain hours in Rails

I would want a method that:
def time_between(from, to)
***
end
time_between 10.am.of_today, 3.pm.of_today # => 1pm Time object
time_between 10.am.of_today, 3.pm.of_today # => 3pm Time object
time_between 10.am.of_today, 3.pm.of_today # => 10:30am Time object
# I mean random
There are two questions here: how to implement ***? and how to implement x.pm.of_today?
ruby 1.9.3
require 'rubygems'
require 'active_support/all'
def random_hour(from, to)
(Date.today + rand(from..to).hour + rand(0..60).minutes).to_datetime
end
puts random_hour(10, 15)
This is a first try:
def time_between(from, to)
today = Date.today.beginning_of_day
(today + from.hours)..(today + to.hours).to_a.sample
end
Although it works like:
time_between(10, 15) # => a random time between 10 am and 3 pm
I think is enough, but I'll be open for better solutions.
To get the random time slot you will need to calculate the distance between the two times. Get a random value with that distance span. And finally add it to your from time.
Something like: (but I am not going to test it)
def time_between(from, to)
if from > to
time_between(to, from)
else
from + rand(to - from)
end
end
As for creating a DSL for time. You could look at how Rails does it. But to get something like what you are wanting. Just create a class that represents the hours in the day. Instantiate it with the am or pm call on a Fixnum. Then write the methods for of_today (and any others that you would want).
class Fixnum
def am
TimeWriter.new(self)
end
def pm
TimeWriter.new(self + 12)
end
end
class TimeWriter
MINUTES_IN_HOUR = 60
SECONDS_IN_MINUTE = 60
SECONDS_IN_HOUR = MINUTES_IN_HOUR * SECONDS_IN_MINUTE
def initialize hours
#hours = hours
end
def of_today
start_of_today + (hours * SECONDS_IN_HOUR)
end
private
attr_reader :hours
def start_of_today
now = Time.now
Time.new(now.year, now.month, now.day, 0, 0)
end
end
You should add some error handling for hours more than 24.
This code respects minutes and hours as input.
require 'rubygems'
require 'active_support/all'
def random_time(from, to)
from_arr = from.split(':')
to_arr = to.split(':')
now = Time.now
rand(Time.new(now.year, now.month, now.day, from_arr[0], rom_arr[1])..Time.new(now.year, now.month, now.day, to_arr[0], to_arr[1]))
end
puts random_time('09:15', '18:45')
Another short way to do the same:
require 'rubygems'
require 'active_support/all'
def random_time(from, to)
now = Time.now
rand(Time.parse(now.strftime("%Y-%m-%dT#{from}:00%z"))..Time.parse(now.strftime("%Y-%m-%dT#{to}:00%z")))
end
puts random_time('09:15', '18:45')

Rails: Find tasks that were created on a certain day?

I have a list of tasks (name, starts_at) and I'm trying to display them in a daily view (just like iCal).
def todays_tasks(day)
Task.find(:all, :conditions => ["starts_at between ? and ?", day.beginning, day.ending]
end
I can't figure out how to convert Time.now such as "2009-04-12 10:00:00" into the beginning (and end) of the day dynamically so I can make the comparison.
def todays_tasks(now = Time.now)
Task.find(:all, :conditions => ["starts_at > ? AND < ?", now.at_beginning_of_day, now.at_end_of_day])
end
Voila!
Expanding on augustl's answer a little bit. In your Task model you could define a method to fetch these tasks for you.
def self.todays_tasks(t = Time.now)
Task.all(:conditions => ["created_at > ? AND created_at < ?", t.at_beginning_of_day, t.tomorrow.at_beginning_of_day])
end
This helps keep your controller skinny. I also compare the end of the day to the beginning of tomorrow. This makes sure that if a user creates a task at 11:59:59 PM it remains in the correct day.
Wouldn't it be:
Task.all(:conditions => ["created_at >= ? AND created_at < ?", Time.now.at_beginning_of_day, Time.now.tomorrow.at_beginning_of_day] )
Or else you would never check for 00:00:00 on the day of the tasks and 11:59:59 is the last value at the end of the day of tasks.
def todays_tasks(day)
Task.where(:created_at => day.beginning_of_day .. day.end_of_day)
end

Resources