How to setup a "watch" for notifications in Ruby on Rails? - ruby-on-rails

I would like to setup a simple notifications system for my web app that is written in Ruby on Rails. My website is a simple auction site, but each auction has 2 stages: a 5 day "no minimum bid" stage where anyone can bid and a 3 day "$1 minimum bid" stage where only the top 10% of the bidders from the previous stage can bid.
I want to notify my users when the auction goes from the first stage to the second. What is the best way to do this?
I've thought of two design options:
I don't know RoR has this, and whether this is an efficient system (because I want to build a system that will have thousands of users participating in hundreds of auctions - whether it gets that big or not is irrelevant, this is sort of a side project for me to learn to write high quality, scalable RoR) but basically, when a user bids on an auction, they become an observer of some sort on the auction. Then, somehow at the 5 day mark, all the listeners are notified. I don't know how that trigger happens though in RoR (is there some way to say "trigger after 5 days from creation" or some such thing?). To summarize, I would like to use the Observer pattern.
The key issue for me is trying to figure out how to trigger after a set period of time. The best way to do it, I think, would be for each auction to have some set state variable called "auction state". After a certain period of time, that would transition, and at that point every observer on the auction would get notified.
There is a rake task that runs everyday at some set time each day. It goes through each auction and if any auction is on day 6, the observers of that auction are notified. This seems like a pretty bad way of going about it - I like the idea of having a state-ful auction.
Thoughts?
Thanks!
Ringo

You can take a look at https://github.com/pluginaweek/state_machine, specifically transitions callbacks. Alternatively you can also use the standard active record callbacks http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html

Have a look at https://github.com/bvandenbos/resque-scheduler, if you haven't heard yet, resque is a gem for creating background processing, having queues in redis. This gem resque-scheduler, lets you specify when to run a task/job.
For example in your model you would have an after_create to schedule the task for changing the bid state.
after_create :shedule_state_change
private
def record_signup
Resque.enqueue_in(5.days, ChangeState)
end
And then you could notify all the bidders for that bid inside that task or create an observer for it.

Related

How can I create custom cron jobs past on a date picker?

I have the following feature on a web app:
For the picture above, I save the time specified on a Schedule model for some work to be performed. At the moment this works perfectly for "Simple" selections meaning that if a user selects Hourly, Weekly, Daily, or Monthly only, then I have a fleet of cron jobs that will start Hourly, Weekly, Daily, or Monthly. So just to be clear, I have 4 cron jobs already placed in my crontab that will run at the specified time and pick the corresponding schedule models.
The problem that I'm facing is the Custom feature. I'm stumped as to how I can effectively create a background task to run on custom input for a user. Here is an example scenario. Let's say I have 3 users who each select a custom time to perform some work.
User 1 selects a custom task to be done on January, February, and March only.
User 2 selects a custom task to be done on Mondays at 1, 3, 6, and 9 pm.
User 3 selects a scan to be done on Friday through Saturday every month only.
And the customization goes on and on.......
What would be an effective and feasible way of implementing this kind of behavior? For the moment, I'm stumped and haven't come up with a way to even approach this based on the requirement. From what I have read, I believe a delayed job will help me based on this stack overflow question custom job based on input but with so much variability, how would this be possible. Some help and guidance would be appreciated. Thank you.
I'd probably go with a mechanism that enqueues the job once for the next occurrence when the configuration is saved. Then, after it has been run, calculate the next occurrence and so on. Don't forget to first remove the already-scheduled run when making changes to the configuration. This can be tricky with some ActiveJob adapters which is why, in case you haven't made a decision towards a specific one yet, I'd like to recommend que. But you can do it in Sidekiq, too.
To facilitate calculation of the next occurrence, you could use, for example, the ice_cube gem.

Periodically updating the game state for all users

I'm working on writing a poker room game in ruby. I'd like to set a time limit on how long each player has to decide what their play is, at the end of which period the game will decide what happens next. (did this particular player time out? did everyone fold? is this the end of the game? etc)
I'd like to poll what's happening on the server from the user's side with JS, but how do I make the server run a background task that advances the state of the game every N minutes? (a value that could be different per each poker room)
This is quite a hard problem to solve. Here might be one of the easiest ways to solve it. Using a background scheduling. For example with sidekiq.
You can schedule a job to update the game state like:
class GameTimoutTrigger
include Sidekiq::Worker
def perform(game_id)
game = Game.find(game_id)
game.timeout!
end
end
And when a game round starts you schedule the timeout trigger
round_timeout = game.round_interval
GameTimoutTrigger.perform_in(round_timeout, game.id)
Be aware that sidekiq will poll jobs in the schedule on intervals (15 secs by default). So there will be always some delay on the timeout.
If the round time must be always exactly 60 seconds for example, you could save the round start and stop timestamps, and user's action timestamp. And only accept user action if it's between the round range. But this might be not necessary. It's just a heads up.
You got the right idea with setInterval().
window.setInterval("javascript function",milliseconds);
The "javascript function" can update the game on the client side and make a request to the server to update. Make sure to clearInterval() if you want to prevent the function from running.
If you're really looking for a way to make the server run a background task, you can maybe try the delayed-jobs gem, but websockets or setInterval is probably a better way to go.

Scalable way to set up a product subscription?

I'm not sure if its the best idea to create a background job that scans the database all the time for updated product prices, especially when in my application I expect to have 100,000s if not millions of products being observed for lower prices.
I have two models, Product and Price. a Product can have many different Prices. So the way I thought it should be done was:
Subscribe to a Product.
Create background job that scans database every hour for lower prices.
Notify users by email.
I'm not sure about #2 and if that's the best way for it to be done. What would you guys suggest?
If your Rails app is adding new Price objects to the Products in its database, why not have an ActiveRecord callback (after_create in this instance) every time a new Price object is added?
That ActiveRecord callback could start a delayed job to email the user about the new, lower price.
If it were me, I would use database triggers rather than a background job sweeper.
Here's an intro to triggers:
http://www.mysqltutorial.org/mysql-triggers.aspx

Modelling indefinitely-recurring tasks in a schedule (calendar-like rails app)

This has been quite a stumbling block. Warning: the following is not a question, rather explanation of what I came up with. My question is — do you have a better way to do this? Is there some common technique for this that I'm not familiar with? Seems like this is a trivial problem.
So you have Task model. You can create tasks, complete them, destroy them. Then you have recurring tasks. It's just like regular task, but it has a recurrence rule attached to it. However, tasks can recur indefinitely — you can go a year ahead in the schedule, and you should see the task show up.
So when a user creates a recurring task, you don't want to build thousands of tasks for hundred years into the future, and save them to database, right? So I started thinking — how do you create them?
One way would be to create them as you view your schedule. So, when the user is moving a month ahead, any recurring tasks will be created. Of course that means that you can't simply work with database records of tasks any longer. Every SELECT operation on tasks you ever do has to be in the context of a particular date range, in order to trigger recurring tasks in that date range to persist. This is a maintenance and performance burden, but doable.
Alright, but how about the original task? Every recurrent task gets associated with the recurrence rule that created it, and every recurrence rule needs to know the original task that started the recurrence. The latter is important, because you need to clone the original task into new dates as the user browses their schedule. I guess doable too.
But what happens if the original task is updated? It means that now as we browse the schedule, we will be creating recurring tasks cloned off of the modified task. That's undesirable. All the implicitly persisted recurring tasks should show up the way the original task looked like when recurrence was added. So we need to store a copy of the original task separately, and clone from that, in order for recurrence to work.
However, when the user navigates the tasks in the schedule, how do we know if at a particular point a new recurrence task needs to be created? We ask recurrence rule: "hey, should I persist a task for this day?" and it says yes or no. If there is already a task for this recurrence for this day, we don't create one. All nice, except a user shall also be able to simply delete one of the recurring tasks that has been automatically persisted. In that case following our logic, the system will re-create the task that has been deleted. Not good. So it means we need to keep storing the task, but mark it as deleted task for this recurrence. Meh.
As I said in the beginning, I want to know if somebody else tackled this problem and can provide architectural advice here. Does it have to be this messy? Is there anything more elegant I'm missing?
Update: Since this question is hard to answer perfectly, I will approve the most helpful insight into design/architecture, which has the best helpfulness/trade-offs ratio for this type of problem. It does not have to encompass all the details.
I know this is an old question but I'm just starting to look into this for my own application and I found this paper by Martin Fowler illuminating: Recurring Events for Calendars
The main takeaway for me was using what he calls "temporal expressions" to figure out if a booking falls on a certain date range instead of trying to insert an infinite number of events (or in your case tasks) into the database.
Practically, for your use case, this might mean that you store the Task with a "temporal expression" property called schedule. The ice_cube recurrence gem has the ability to serialize itself into an active record property like so:
class Task < ActiveRecord::Base
include IceCube
serialize :schedule, Hash
def schedule=(new_schedule)
write_attribute(:schedule, new_schedule.to_hash)
end
def schedule
Schedule.from_hash(read_attribute(:schedule))
end
end
Ice cube seems really flexible and even allows you to specify exceptions to the recurrence rules. (Say you want to delete just one occurrence of the task, but not all of them.)
The problem is that you can't really query the database for a task that falls in a specific range of dates, because you've only stored the rule for making tasks, not the tasks themselves. For my case, I'm thinking about adding a property like "next_recurrence_date" which will be used to do some basic sorting/filtering. You could even use that to throw a task on a queue to have something done on the next recurring date. (Like check if that date has passed and then regenerate it. You could even store an "archived" version of the task once its next recurring date passes.)
This fixes your issue with "what if the task is updated" since tasks aren't ever persisted until they're in the past.
Anyway, I hope that is helpful to someone trying to think this through for their own app.
Having done a calendar-like component for an internal social networking app, here's my approach to that problem.
Tiny bit of background: I needed to book boardrooms for meetings for the entire company. Every boardroom needed to be booked either as a one-off or on a recurring basis. As you've found out, it's the recurrence rules that kill you. The additional twist to my problem was that there could be conflicts, i.e. two people could try to book the same boardroom for the same date and time.
I split my models into Boardroom (obviously) and Event (which is the booking associated to a User). I think there was a join model, as well, but it's been a while. When a User would try to book a boardroom, this is the process taken:
Attempt to book on the first available date (done through the calendar UI by the user similar to how Google Calendar creates events)
If it's a one-off, you're done
If it's a recurring event, try to immediately book the next 6 events based on the rule given (weekly, bi-weekly, monthly); If it fails, due to conflict, book the ones you can, e-mail the conflicts to the user
Book for the next year or up to the date the recurrence is ending in a background job; Follow the conflict resolution rule from #3
When resolving the conflicts, the user had the option of either resolving them on a case-by-case basis or moving the remaining bookings to the new, available date and time.
If the user updated the original booking (e.g changed the time and date), he/she had the option of updating only the that one or every following recurrence. If the latter was selected, steps 3 and 4 are re-invoked after the deletion of existing events.
If this sounds a lot like Google Calendar, then you've fully understood my approach, :)
Hope this helps.
I personally think that (in python which I know well), and ruby (which I know less well, but it's a dynamic language, and so I think the concepts map 1:1), you should be using generators. How's that for a minimalistic answer? Now, when you generate your UI, you pass in a reference to the generator, and it generates the objects you need, as they are requested.
As an interface, it has next item, and previous item methods, and acts a bit like a cursor that can wade forward and backward through the various interations. It is in fact, a piece of code masquerading as an infinite series (array) without using infinite memory.
Why do you need to proliferate objects? What you really need are virtual data display controls (for the web or desktop) also known as "paging" I think, in web contexts, and you can think of your schedule as an infinite generated-on-demand spreadsheet, with no top row, and no bottom row. The only values you need to be able to calculate (calculate, not store) are the ones that appear right now, as visible to the user.

perform some logic before object is loaded from database

The situation:
The magazine accepts submissions. Once you submit, an editor will schedule your submission for review. Once it has been reviewed, you are no longer allowed to edit it.
So, I have submission in various states. "Draft", "queued", "reviewed", etc. Most of the switches into these various states are triggered by some action, e.g., a submission becomes queued when an editor schedules it. Easy peasey. However, the switch into the "reviewed" state is not triggered by any action, it just happens after a certain datetime has passed.
I have two thoughts on how to accomplish this:
Run a daily/hourly cron job to check up on all the queued submissions and switch them to reviewed if necessary. I dislike this because
I would prefer it to be hourly, so that I can edit my submission up to three hours before a meeting starts
Hourly cron jobs cost money on Heroku, and this application will either never make money or won't make money for months and months to come
Somehow construct a before_load ActiveRecord callback, that will perform some logic on submissions each time they are loaded. "Queued? No? Nevermind. Otherwise, switch it to 'Reviewed' if its meeting is less than three hours away."
I wanted to get people's input on the second idea.
Is that an atrociously smelly way to accomplish this?
If so, can you suggest an awesomer third way?
If 'no' to both of the above, can you give tips on how to perform such logic each time a record is loaded from the database? I would need to always perform some logic before doing a select from the submissions table (which is gearing up to be the most-queried table in the app...)
If there's no good way to accomplish Option Two (or, I hope!, Option Three), I will resort to Option One with a daily cron job. Being able to edit up to a day before a meeting will just have to suffice.
Maybe using after_find, although your performance will sort of suck, same goes if you do something as crazy as before_load, performance would suck, that said money might be more important than performance, if that is so, I would go with the after_find.

Resources