StateMachine transitions based on model attributes - ruby-on-rails

I'm using finite machine to handle the state of Projects. Each project belongs to a Category and each category has many Steps.
Now I'd like to define in my ProjectStateMachine the next event which transits a project to the next steps according to its current state and category of belonging.
For instance, let's assume I have the "new lab" project (in the submitted state) associated with the equipment category. The equipment category has ['submitted', 'started', 'completed'] steps.
When the next event happens for the new lab project, its state becomes started and then when the next event happens again, the project goes in the completed state.
Is there a way to achieve so?

I'm unfamiliar with that specific gem (more on that later), but I would think you could do something like:
events {
self.category.steps.each_cons(2) do |set|
event :next, set[0].to_sym => set[1].to_sym
end
}
But with that said, the gem you referenced has not been updated in over a year. I'd recommend going with something more recent such as this (if you're using ActiveRecord which is based upon the state-machines gem). This last link takes you to a specific section on dynamic definitions, which will certainly give you what you want.
Cheers

Related

Identify IceCube recurring events by ID once they have occurred

I am attempting to use the ice_cube gem to set up a schedule for recurring shifts. What I am struggling with is the recurring events are not real instances of my model with an ID; I need to be able to reference each event to associate it with a shift_confirmation method (detailing things like the actual end time vs. the booked end time, whether the shift has been paid, etc.).
Where I'm struggling is that if I add an actual instance of my model, as opposed to a recurring instance, I will end up with both - the instance I can refer to with an ID, and also a recurring instance.
I have considered whether I can add an IceCube exception that removes a shift from the recurrence schedule each time a user wants to edit it (e.g. to mark it complete, paid, etc.), and create an instance of my model, but this seems very unclean - there must be a better way.
Is there any way to refer definitively to an specific event in an ice_cube recurring schedule, so that I can do things like mark it paid (which would be in an associated model)?
When you create an schedule, the schedule has an idea, probably you can use this in your structure.
I viewed this post, because i've a similar problem.
I've an table activities, with start-, end datum and duration and an related table schedule with the reference 'activity_id'. I didn't want to store the ice-cube to_yaml string, because I want the option to add 'closing dates' in the future on a central place, without parsing every record again.
If you show your structure, maybe I can be of some help.
Regards, sander
Check out the schedulable gem, it lets you persist event_occurrences in the database and let's your "schedule" edit only future occurrences.

How to break logic and avoid coupling in this scenerio

I have a item review system where people vote on an item.
The UI also displays how they voted, up/down.
The user can get points when they vote.
The item object that they vote on, has to reflect the # of voters and other stats.
So there is allot of things going on here, like if someone voted previously, I have to change their vote and therefore update their statistics and the statistics on the object voted on.
How can I possible break these different tasks out, since they all seem to be highly coupled with each other and I cant' figure out a way how to avoid having my code have all these different interactions mixed together.
Example method:
Set_Vote
1. is this an old vote?
-> if old vote, change old vote to new vote value
-> otherwise, insert new vote
2. update user stats
3. update item voted on stats
4. return a json response to the UI with information to update the screen via ajax etc.
Now it may seem easy to de-couple until you get to coding. Like for example, I can't really set the user stats unless I know the outcome of the logic from #1 since if its an old vote, I have to reverse what happened earlier, and then apply the new values (same with #3).
Should the method in #1 have a very rich return object that tells me things like 1) it was an old vote, or it wasn't? what was the outcome of the attempt to vote? (maybe the vote was rejected for whatever reason in my logic).

Record changes pend approval by a privileged user; Its like versioning combined with approvals

I have a requirement that certain attribute changes to records are not reflected in the user interface until those changes are approved. Further, if a change is made to an approved record, the user will be presented with the record as it exists before approval.
My first try...
was to go to a versioning plugin such as paper_trail, acts_as_audited, etc. and add an approved attribute to their version model. Doing so would not only give me the ability to 'rollback' through versions of the record, but also SHOULD allow me to differentiate between whether a version has been approved or not.
I have been working down this train of thought for awhile now, and the problem I keep running into is on the user side. That is, how do I query for a collection of approved records? I could (and tried) writing some helper methods that get a collection of records, and then loop over them to find an "approved" version of the record. My primary gripe with this is how quickly the number of database hits can grow. My next attempt was to do something as follows:
Version.
where(:item_type => MyModel.name, :approved => true).
group(:item_type).collect do |v|
# like the 'reify' method of paper_trail
v.some_method_that_converts_the_version_to_a_record
end
So assuming that the some_method... call doesn't hit the database, we kind of end up with the data we're interested in. The main problem I ran into with this method is I can't use this "finder" as a scope. That is, I can't append additional scopes to this lookup to narrow my results further. For example, my records may also have a cool scope that only shows records where :cool => true. Ideally, I would want to look up my records as MyModel.approved.cool, but here I guess I would have to get my collection of approved models and then loop over them for cool ones would would result in the very least in having a bunch of records initialized in memory for no reason.
My next try...
involved creating a special type of "pending record" that basically help "potential" changes to a record. So on the user end you would lookup whatever you wanted as you normally would. Whenever a pending record is apply!(ed) it would simply makes those changes to the actual record, and alls well... Except about 30 minutes into it I realize that it all breaks down if an "admin" wishes to go back and contribute more to his change before approving it. I guess my only option would be either to:
Force the admin to approve all changes before making additional ones (that won't go over well... nor should it).
Try to read the changes out of the "pending record" model and apply them to the existing record without saving. Something about this idea just doesn't quite sound "right".
I would love someone's input on this issue. I have been wrestling with it for some time, and I just can't seem to find the way that feels right. I like to live by the "if its hard to get your head around it, you're probably doing it wrong" mantra.
And this is kicking my tail...
How about, create an association:
class MyModel < AR::Base
belongs_to :my_model
has_one :new_version, :class_name => MyModel
# ...
end
When an edit is made, you basically clone the existing object to a new one. Associate the existing object and the new one, and set a has_edits attribute on the existing object, the pending_approval attribute on the new one.
How you treat the objects once the admin approves it depends on whether you have other associations that depend on the id of the original model.
In any case, you can reduce your queries to:
objects_pending_edits = MyModel.where("has_edits = true").all
then with any given one, you can access the new edits with obj.new_version. If you're really wanting to reduce database traffic, eager-load that association.

Creating a dashboard in Rails

Let's say I had an app that was an address book. I'd like to have a page dedicated to a "dashboard". On this page, I'd like to have a running list of the events that happen within the app itself.
Event examples could be:
A user adds a contact.
A user deletes a contact.
A user updates a contact.
What would be the best way to create this type of functionality? Originally I felt that I could do some creative database calls with existing data, but I wouldn't be able to deal with events that deleted data, like when a contact is deleted.
So now I'm thinking it would have to be a separate table that simply stored the events as they occurred. Would this be how most sites accomplish this?
I could go throughout my app, and each time a CRUD operation is performed I could create a new item in the table detailing what happened, but that doesn't seem very DRY.
I supposed my question would be - what's the best way to create the dashboard functionality within an already existing application such as an address book?
Any guidance would be greatly appreciated.
The easiest way to do this is to user Observers in addition to a "logger" table in your database.
Logger
id
model_name
model_id
message
This way you can set up an Observer for all models that you want to log, and do something like this:
after_delete(contact)
Logger.create({:model_name => contact.class.to_s,
:model_id => contact.id,
:message => "Contact was deleted at #{Time.now}"})
end
Now you can log any event in a way you deem fit. Another great addition to this kind of structure is to implement "Logical Deletes", which means you never really delete a record from the table, you simple give it a flag so that it no longer shows up in regular result sets. There's a plugin that does this called acts_as_paranoid.
If you implement both things above, the dashboard can log all important actions, and if you ever need to see what happened or view the data of those events, it's all in the system and can be accessed via the Console (or controllers, if you set them up).
You may want to check out Timeline Fu: http://github.com/jamesgolick/timeline_fu:
class Post < ActiveRecord::Base
belongs_to :author, :class_name => 'Person'
fires :new_post, :on => :create,
:actor => :author
end
I've created similar functionality in the past using acts_as_audited.
This helps you track changes to your models, which you can then present to the user.
It basically just tracks the events in a separate table, as you suggested.
You can use Observers in order to handle the events.
Then just store event with the information needed in the database from those Observers.
Here is a quick link to get you started.
user paper_trail plugin, it is awesome!. We modified it though, it is used for all our audit system for complicated release process.

Triggers/Callbacks in Ruby on Rails

We are creating a system in Ruby on Rails and we want to be able to offer our users a bit of control about notifications and actions that can take place when some pre-defined trigger occurs. In addition, we plan on iterating through imported data and allowing our users to configure some actions and triggers based on that data.
Let me give you a few examples to better clarify:
Trigger - Action
------------------------------------------------------------------------
New Ticket is Created - User receives an e-mail
New Ticket Parsed for Keyword 'evil' - Ticket gets auto-assigned to a
particular group
User Missed 3 Meetings - A ticket is automatically created
Ideally, we would like some of the triggers to be configurable. For instance, the last example would possibly let you configure how many meetings were missed before the action took place.
I was wondering what patterns might help me in doing this event/callback situation in Ruby on Rails. Also, the triggers and actions may be configurable, but they will be predefined; so, should they be hard coded or stored in the database?
Any thoughts would be greatly appreciated. Thanks!
Update 1: After looking at it, I noticed that the badges system on SO is somewhat similar, based on these criteria, I want to do this action. It's slightly different, but I want to be able to easily add new criteria and actions and present them to the users. Any thoughts relating to this?
I think that what you are looking for are the Observers.
In your examples the Observers could handle the first and the third example (but not the second one, since an Observer only observes the object, not interact with it, even though it is technically possible).
Some code to show how I mean:
class TicketObserver < ActiveRecord::Observer
def after_create(ticket)
UserMailer.deliver_new_ticket_notification
end
end
class UserObserver < ActiveRecord::Observer
def after_update(user)
Ticket.new if user.recently_missed_a_meeting and user.missed_meetings > 3
end
end
And then add the observers to environment.rb
config.active_record.observers = :user_observer, :ticket_observer
Of course you will have to fill in the logic for the missed_meetings, but one detail to mention.
Since the after_update will trigger after every time that the user is being updated, the recently_missed_a_meeting attribute is useful. I usually follow the thinking of restful-authentication and have an instance variable that is being set to true everytime I want to trigger that row. That can be done in a callback or in some custom logic depends on how you track the meetings.
And for the second example, I would put it in a before_update callback, perhaps having the keywords in a lookup table to let users update which words that should trigger the move to a specific group.
You should look at the "callback" methods in Rails
For docs see - Callbacks
Your first rule would be implemented via the after_create method.
If you want them to be configurable, I would suggest using a model / table to store the possible actions and doing a lookup within the callback.
If this is high volume, be sure to consider caching the configuration since it would end up doing a db lookup on each callback.
Maybe something like a state-machine can help. Try AASM gem for RoR.

Resources