How can Heroku:cron handle a range of hours? - ruby-on-rails

I have a cron job on Heroku that needs to run for 8 hours a day. (Once an hour, eight times, each day.).
These do work as expected:
(all code in /lib/tasks/cron.rake)
if Time.now.hour == 6
puts "Ok it's 6am and I printed"
puts "6:00 PST put is done."
end
if Time.now.hour == 7
puts "Ok it's 7am and I printed"
puts "7:00 PST put is done."
end
if Time.now.hour == 8
puts "I printed at 8am"
puts "8:00 PST put is done."
end
if Time.now.hour == 9
puts "9:00 PST open put"
puts "9:00 PST put is done."
end
This code, however, doesn't work:
if Time.now.hour (6..14)
puts "I did the rake job"
end
I've also tried the following variants. Didn't work.
if Time.now.hour == (6..14)
if Time.now.hour = (6..14)
if Time.now.hour == 6..14
if Time.now.hour = 6..14
Anyone know how I can put a range in a Heroku cron job? Listing lots of jobs by hour just seems wrong.

You want to see if that range includes the hour:
if (6..14).include?(Time.now.hour)
puts "I did the rake job"
end

What you want is
if (6..14) === Time.now.hour
# Run command
end

Related

How to override Date.today when running rails tests?

I have some tests that were failing on specific dates, because someone wrote them to use Date.today. I want to reproduce the failures on previous select dates.
Is there a way to run rake test with an ENV variable that will override the system clock? So that calls to Date.today, Time.now, and 1.day.ago and 1.day.from_now will use the date I set?
Like, for example:
> DATE_TODAY='2017-01-04' rake test
For testing you can use timecop gem.
It offers you two useful methods Timecop.freeze and Timecop.travel.
For example, you can use freeze to statically set time in before hooks:
describe 'your tests' do
before do
Timecop.freeze(Time.new(2017, 1, 4))
end
after do
Timecop.return
end
it 'should do something' do
sleep(10)
Time.now # => 2017-01-04 00:00:00
end
end
Or in a block:
Timecop.freeze(Time.now - 3.months) do
assert product.expired?
end
While with the travel method, you change the starting moment, but time is still passing by.
Timecop.travel(Time.new(2017, 1, 4))
sleep(10)
Time.now # => 2017-01-04 00:00:10
As of Rails 4.1 you can do
travel_to Time.new(2004, 11, 24, 01, 04, 44)
The full API docs are here: http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html

Single spec duration

Is there a way in RSpec to show every single test duration and not just the total suite duration?
Now we have
Finished in 7 minutes 31 seconds (files took 4.71 seconds to load)
but I'd like to have something like
User accesses home and
he can sign up (finished in 1.30 seconds)
he can visit profile (finished in 3 seconds)
.
.
.
Finished in 7 minutes 31 seconds (files took 4.71 seconds to load)
You can use rspec --profile N, which would show you the top N slowest examples.
For a quick solution see #maximf's answer. For an alternative solution, you could write your own rspec formatter, which would give you greater control over what you are measuring.
For example, extnding rspec's base text formatter:
RSpec::Support.require_rpec_core "formatters/base_text_formatter"
module RSpec::Core::Formatters
class TimeFormatter < BaseTextFormatter
Formatters.register self, :example_started, :example_passed
attr_accessor :example_start, :longest_example, :longest_time
def initialize(output)
#longest_time = 0
super(output)
end
def example_started(example)
#example_start = Time.now
super(example)
end
def example_passed(example)
time_taken = Time.now - #example_start
output.puts "Finished #{example.example.full_description} and took #{Helpers.format_duration(time_taken)}"
if #time_taken > #longest_time
#longest_example = example
#longest_time = time_taken
end
super(example)
end
def dump_summary(summary)
super(summary)
output.puts
output.puts "The longest example was #{#longest_example.example.full_Description} at #{Helpers.format_duration(#longest_time)}"
end
end
end
Note that this will only log times on passed examples, but you could add an example_failed failed to do similar, it also only works with RSpec 3. This is based on my work on my own formatter: https://github.com/yule/dots-formatter
Instead of doing rspec --profile Neverytime we run specs (as #maximf said), we can add it to our RSpec configuration:
RSpec.configure do |config|
config.profile_examples = 10
end

model method trigger boolean to true?

How can I trigger method accomplished_challenge upon days_left_challenged == 0?
challege.rb
before_save :days_left_challenged_sets_deadline
# makes ongoing_challenge become past (before_save) THIS WORKS
def days_left_challenged_sets_deadline
if self.date_started != nil
if days_left_challenged <= 0
self.accomplished = true
self.deadline = self.date_started
end
end
end
# makes ongoing_challenge become past (whenever gem) THIS DOESN'T
def self.accomplished_challenge
self.all.each do |challenge|
if challenge.days_left_challenged <= 0
challenge.accomplished = true
challenge.deadline = self.date_started
end
end
end
# Counts down how many days left in days_challenged using committed
def days_left_challenged
self.days_challenged - ((date_started.to_date)..Date.yesterday).count do |date|
committed_wdays.include? date.wday
end + self.missed_days
end
Challenge.last
id: 1,
action: "Run",
committed: ["sun", "mon", "tue", "wed", "thu", "fri", "sat", ""],
date_started: Sat, 06 Feb 2016 00:00:00 EST -05:00,
deadline: nil,
accomplished: nil,
days_challenged: 10,
missed_days: 0,
I can't trigger it with a callback or validation I don't think since days_left_challenged can turn to 0 at any point in the life of a challenge.
I suggest you use a gem like Whenever to setup a cron job to run every day or so and do that checking for all Challenges. It would be something like:
every 1.day, :at => '0:01 am' do
runner "Challenge.accomplished_challenge"
end
And your accomplished_challenge must be a class method that checks all (or the one you choose using a filter) Challenges:
def self.accomplished_challenge
self.all.each do |challenge|
if challenge.days_left_challenged == 0
challenge.update_attributes(deadline: self.date_started, accomplished: true)
end
end
end
---- EDIT to work on Heroku ----
Create a task on /lib/tasks/scheduler.rake:
# /lib/tasks/scheduler.rake
desc "This task is called by the Heroku scheduler add-on"
task :check_accomplished_challenges => :environment do
puts "Checking accomplished challenges..."
Challenge.accomplished_challenge
puts "done."
end
Go to your heroku app Resources page and add 'Heroku Scheduler'. Open the scheduler and add the task:
rake check_accomplished_challenges
Set it to run every day.
More details: https://devcenter.heroku.com/articles/scheduler

select! always returns nil on heroku

Everything works fine on local.
This doesn't work on Heroku:
class Ticket
def self.how_many_today
todays_tickets = Ticket.all.to_a.select!{ |t| t.created_at.to_date == Date.today }
todays_tickets == nil ? 0 : todays_tickets.count
end
# This method is scheduled with cron
def self.reset_todays_nr
#todays_nr = nil
end
def self.set_todays_nr
if #todays_nr.nil?
#todays_nr = how_many_today + 1
else
#todays_nr += 1
end
end
end
Namely, playing on heroku run console reveals this inconsistency:
irb(main):023:0* set_todays_nr
=> 1
irb(main):024:0> set_todays_nr
=> 2
irb(main):025:0> set_todays_nr
=> 3
irb(main):026:0> Ticket.all.to_a.select!{ |t| t.created_at.to_date == Date.today }
=> nil
irb(main):028:0> Ticket.first.created_at
=> Sat, 20 Dec 2014 16:19:31 UTC +00:00
irb(main):029:0> Ticket.first.created_at.to_date
=> Sat, 20 Dec 2014
irb(main):030:0> Date.today
=> Sat, 20 Dec 2014
irb(main):031:0> Date.today.to_date
=> Sat, 20 Dec 2014
irb(main):032:0> Date.today == Ticket.first.created_at.to_date
=> true
irb(main):033:0> Date.today.to_date == Ticket.first.created_at.to_date
=> true
irb(main):034:0>
irb(main):035:0* Ticket.all.to_a.select!{ |t| t.created_at.to_date == Date.today }
=> nil
irb(main):036:0> Ticket.all.map(&:created_at)
=> [Sat, 20 Dec 2014 16:19:31 UTC +00:00, Sat, 20 Dec 2014 16:21:12 UTC +00:00]
irb(main):037:0> _[0].to_date == Date.today
=> true
It looks like the condition for select! is properly parsed, manual check shows there are some elements to that condition, but select! does not return any array. Once again, this does work locally.
Database has been migrated and fixtures loaded just as on local.
Although self.reset_todays_nr is scheduled with cron which might cause problems, this method is not triggered in this case, so it's rather irrelevant for the problem, but I posted it here just in case this problem is more advanced than I suppose.
Could anyone help me out here, please?
That is weird indeed. Particularly because I ran some commands in my Rails console just now and array.select!{} shouldn't return nil unless the array was empty to begin with.
[1].select!{ |t| false } #=> []
[].select!{ |t| false } #=> nil
So recheck what the output of Ticket.all.to_a is.
Also, your select condition can be set simply as:
var = Ticket.select{ |t| t.created_at.to_date == Date.today }
That will select all tickets itself and then filter.
But it would be preferable to filter and count in the query rather than load up everything in memory and then do further operations for comparisons. Check this out:
Ticket.where("DATE(created_at) = DATE(?)", Date.today).count
Or change the DATE(?) part with your SQL's "get today's date" function.
Alternatively, you could:
now = DateTime.now
range = (today.beginning_of_day)..(today.end_of_day)
Ticket.where(created_at: range).count
Keep the possible discrepancy of time-zone in mind i.e. the created_at column might have a different time-zone than generated by DateTime.now. You'll have to check.
#Humza, thank you very much! It isn't the precise solution, but helped me to solve the case. Thanks a lot for Ticket.where("DATE(created_at) = DATE(?)", Date.today).count - I was thinking exactly the same, i.e. not to load the entire array and only then evaluate it, so thanks for a way of doing it. If you look at my code, you see that in set_todays_nr I purposedly place how_many_today in a condition so as to run the search at most as often as cron is scheduled to reset #todays_nr. After changing it, bug became more visible: because of the flow of the app, the new how_many_today was returning 1 - the ticket is created before this method is called. Though the mystery of strange heroku behaviour remains unsolved, I didn't sleuth further as changing the method to the below form solved the problem. Now it looks like this:
def self.how_many_today
Ticket.where("DATE(created_at) = DATE(?)", Date.today).count
end
# This method is scheduled with cron; check config/schedule.rb
def self.reset_todays_nr
#todays_nr = nil
end
def self.set_todays_nr
if #todays_nr.nil?
#todays_nr = how_many_today
else
#todays_nr += 1
end
end

Random space in Rails DateTime when using RSpec?

I have a method in my Event model:
def when
self.starts_at.strftime("%a %b %e # %-I:%M")
end
which outputs correctly in the rails console or on a webpage.
However, doing an rspec test like this:
it "has simple date display" do
game = FactoryGirl.build(:event, starts_at: DateTime.parse("1/1/2014 3:30pm"))
game.when.should == "Wed Jan 1 # 3:30"
end
fails, because of:
1) Event has simple date display
Failure/Error: event.when.should == "Wed Jan 1 # 3:30"
expected: "Wed Jan 1 # 3:30"
got: "Wed Jan 1 # 3:30" (using ==)
Why is there a random space in my formatted DateTime? Shouldn't the same DateTime code be loaded/running for tests and console?
You're using the %e format directive, which is for the blank-padded day of month. It sounds like you want the unpadded day of the month directive, which is %-d. Here are the docs.
The reason your test is failing is because the %e directives in strftime is:
%e - Day of the month, blank-padded ( 1..31)
So the statement DateTime.parse("1/1/2014 3:30pm").strftime("%a %b %e # %-I:%M") yields Wed Jan 1 # 3:30 (with extra space prepended for days between 1 through 9).
You could use %d directive instead which gives day of month, zero-padded. E.g.
# Event model
def when
self.starts_at.strftime("%a %b %d # %-I:%M")
end
Then in your spec:
it "has simple date display" do
game = FactoryGirl.build(:event, starts_at: DateTime.parse("1/1/2014 3:30pm"))
game.when.should == "Wed Jan 01 # 3:30"
end

Resources