how do I enable recurring reminders for different users in ruby? - ruby

Currently, the users for my app set a specific date and time for a single reminder (using the Chronic gem).
I have a cron job which runs every 10 minutes and checks if the time in the reminder is < Time.now at which point, it sends the reminder and marks that reminder as sent.
However, I want them to be able to specify recurring reminders. The customer should be able to say, "Every other day at 10am".
I can use ice_cube for the recurring part it seems. Then I use Chronic to come up with the start time which will have the day and recurring time.
But I don't have a good way to make it recurring since these are not separate events in the data base.
Here is what I have tried:
```
reminder = response.body['results'] #array of recurring reminders with start_epoch and 'via'
d {reminder}
reminder.count.times do |i|
schedule = IceCube::Schedule.new(now = Time.at(reminder[i]['value']['start_epoch'])) # get the initial start day and time
schedule.add_recurrence_rule(IceCube::Rule.daily) #makes this a daily recurring, but how can I allow this to be customizable?
if schedule.next_occurrence(Chronic.parse('today at 12:01AM')) < Time.now # Check if today's occurence has happened yet
bot_response = BotResponse.get_bot_response(bot_client_id, reminder[i]['value']['bot_input_keyword'])
if reminder[i]['value']['via'] == 'slack'
slack_response = BotMessage.send_slack(reminder[i]['value']['slack_channel'],
bot_response[:bot_response])
d {slack_response}
end #if
end #if
end #do
```
Questions:
Is there a way to tell when a reminder has been sent without writing each specific daily reminder to a database?
Is there a more elegant way for the user in a text string to define the recurrence?

Have you considered trying the whenever gem to implement recurring tasks through cron jobs? I think you should be able to set the schedule times dynamically in the whenever schedule.rb file, see related issue here: Rails - Whenever gem - Dynamic values

Related

how can I run ruby script for n-occurrences every X-minutes

I am looking at https://github.com/jmettraux/rufus-scheduler which nicely allows me to schedule and chain events.
But I want the events to stop after, say, the 10th occurrence or after the 24 days.
How do I do this?
My case would be:
run a script which creates the recurring jobs based on intervals and then stops after a given date or occurrence.
This is what I have done.
def run_schedule(url, count, method, interval)
puts "running scheduler"
scheduler = Rufus::Scheduler.new
scheduler.every interval do
binding.pry
attack_loop(url, count, method)
end
end
I am testing my site and want the attack_loop to be scheduled in memory to run against the interval.
But it appears it never hits the binding.pry line.
Normally these schedulers are running via cron jobs. Then the problem with your requirement is cron job doesn't know whether you hit the 10th occurrence or the 24 days as it doesnt keep a track. One possible solution would be to create a separate table to update the cron job details.
I'm thinking a table like,
scheduler_details
- id
- occurrence_count
- created_date
- updated_date
_ scheduler_type
So, now when you run a script, you can create or update the details. (You can search the script by scheduler_type, that way
you can check the number of occurrences
with created date, you can calculate the 24 days
HTH
Good day, you can specify the number of times a job should run:
https://github.com/jmettraux/rufus-scheduler/#times--nb-of-times-before-auto-unscheduling
If you need a more elaborate stop condition, you could do:
scheduler.every interval do |job|
if Time.now > some_max_date
job.unschedule
else
attack_loop(url, count, method)
end
end
But it appears it never hits the binding.pry line.
What has it to do with your issue title? Are you mixing issues?

Set monthly recurring job dynamically with resque-schedular

Banging my head against the wall with this one. Trying to dynamically add jobs with resque-scheduler. What's the syntax for creating a monthly job? For example, the code below will set up a job to run every minute.
config[:class] = "job_name"
config[:args] = "arg"
config[:every] = "1m"
config[:persist] = true
What would the syntax here be for every month? Would it be config[:every] = "1 month"? I can't seem to find any answers on the resque-scheduler docs for this.
Thanks.
For dynamic schedules resque-scheduler uses rufus-scheduler, as it is explained on the documentation, which handles not only the actual scheduling business but also the parse of the :every option.
You can see that when resque-scheduler runs it basically loads all schedule information from redis and then passes on to rufus.
The supported letter/durations are documented on rufus here as a map between letters and durations in seconds and you can see more complex rules on the specs for duration parsing.
For one month, you can use 1M or you can also use 4w, there's also 30d...

Using a Rufus-Scheduler event to set more Rufus-Scheduler events

I've got a script where I am trying to use Rufus-Scheduler to create daily schedules at 1am (based around sunrise and sunset). When I run the code it seems to only run the first scheduler event and won't run any events after it. My understanding is Rufus-Scheduler is supposed to spawn a new thread with each schedule but it looks like it is blocking. Do I need to spawn the schedules off on a new thread? Do I need to create a new scheduler instance for each schedule I'm going to create? I've added a test scheduler at the bottom of the code and it doesn't get created.
Here's the portion of code which relates to rufus-scheduler
def get_todays_show()
this_year = Time.now.year
check_sunset
#the time format that comes back isn't reconginzed by rufus scheduler so recreate it with chronic
sunrise = Chronic.parse("#{#local_sunrise.to_s[0,10]} #{#local_sunrise.to_s[11,8]}" )
sunset = Chronic.parse("#{#local_sunset.to_s[0,10]} #{#local_sunset.to_s[11,8]}" )
schedule_array = create_array_from_csv
schedule_array.each do |sub_array|
earlier = Chronic.parse("#{sub_array[0]} #{this_year} 12:00:01 am")
later = Chronic.parse("#{sub_array[1]} #{this_year} 11:59:59")
range = earlier..later
if range.cover?(Time.now)
#scheduler.at sunset do
madrix_call(sub_array[2].strip)
end
#scheduler.at sunrise do
madrix_call(#off_slot)
end
end
end
end
#set up the scheduler
#scheduler = Rufus::Scheduler.start_new(:frequency => 30.0)
#run it once to handle today
get_todays_show
#the schedule to handle the future
#scheduler.cron '1 1 * * *' do
get_todays_show
p #scheduler.jobs
end
#scheduler.cron '* * * * *' do
p "test schedule - #{Time.now}"
end
#scheduler.join
Do I need to spawn the schedules off on a new thread?
No, rufus-scheduler does it for you.
Do I need to create a new scheduler instance for each schedule I'm going to create?
No, not at all.
Have you tried without setting a frequency (using rufus-scheduler's default frequency)?
Although you are not hitting a rufus-scheduler issue, please read: http://www.chiark.greenend.org.uk/~sgtatham/bugs.html
You are not giving any detail about your environment, it's very hard to help you.
Try to iterate from small to big. Have a small schedule thinggy work and then proceed one step after the other.

Find Recurring Outlook Events Happening Today

I have a Ruby script using Win32OLE to read through my Outlook events and find events ocurring today (based on the Start date value).
events_today = ''
calendar.Items.each do |appointment|
appt_date = Time.parse(appointment.Start)
if appt_date > today && appt_date < tomorrow
events_today << "<p><strong>#{appointment.Subject}:</strong> #{appt_date.strftime("%I:%M %p")}</p>"
end
end
It catches one-time events that occur today, but it doesn't seem to catch recurring events (ie, events that started last week and occur daily including today).
Is there better field to use to search for the event (other than appointment.Start)?
Use the Items.IncludeRecurrences property:http://msdn.microsoft.com/en-us/library/office/aa171434(v=office.11).aspx

Ruby and Rails Async

I need to perform long-running operation in ruby/rails asynchronously.
Googling around one of the options I find is Sidekiq.
class WeeklyReportWorker
include Sidekiq::Worker
def perform(user, product, year = Time.now.year, week = Date.today.cweek)
report = WeeklyReport.build(user, product, year, week)
report.save
end
end
# call WeeklyReportWorker.perform_async('user', 'product')
Everything works great! But there is a problem.
If I keep calling this async method every few seconds, but the actual time heavy operation performs is one minute things won't work.
Let me put it in example.
5.times { WeeklyReportWorker.perform_async('user', 'product') }
Now my heavy operation will be performed 5 times. Optimally it should have performed only once or twice depending on whether execution of first operaton started before 5th async call was made.
Do you have tips how to solve it?
Here's a naive approach. I'm a resque user, maybe sidekiq has something better to offer.
def perform(user, product, year = Time.now.year, week = Date.today.cweek)
# first, make a name for lock key. For example, include all arguments
# there, so that another perform with the same arguments won't do any work
# while the first one is still running
lock_key_name = make_lock_key_name(user, product, year, week)
Sidekiq.redis do |redis| # sidekiq uses redis, let us leverage that
begin
res = redis.incr lock_key_name
return if res != 1 # protection from race condition. Since incr is atomic,
# the very first one will set value to 1. All subsequent
# incrs will return greater values.
# if incr returned not 1, then another copy of this
# operation is already running, so we quit.
# finally, perform your business logic here
report = WeeklyReport.build(user, product, year, week)
report.save
ensure
redis.del lock_key_name # drop lock key, so that operation may run again.
end
end
end
I am not sure I understood your scenario well, but how about looking at this gem:
https://github.com/collectiveidea/delayed_job
So instead of doing:
5.times { WeeklyReportWorker.perform_async('user', 'product') }
You can do:
5.times { WeeklyReportWorker.delay.perform('user', 'product') }
Out of the box, this will make the worker process the second job after the first job, but only if you use the default settings (because by default the worker process is only one).
The gem offers possibilities to:
Put jobs on a queue;
Have different queues for different jobs if that is required;
Have more than one workers to process a queue (for example, you can start 4 workers on a 4-CPU machine for higher efficiency);
Schedule jobs to run at exact times, or after set amount of time after queueing the job. (Or, by default, schedule for immediate background execution).
I hope it can help you as you did to me.

Resources