I'm using a ActiveRecord::Transitions in Rails 3 and have my state machine defines as:
state_machine do
state :initial # first one is initial state
state :active
state :disabled
event :activate do
transitions :to => :active, :from => [:initial, :disabled]
end
event :disable do
transitions :to => :disabled, :from => [:initial, :active]
end
end
How do I see a list of available transitions for a current object and state?
For example if I have a #product in state "active" it should tell me
"disabled" is the only state available, or
"disable" is the only event available
I can't see any obvious way to enumerate possible-next-states, but you can query the available events like this:
YourClass.state_machines[:default].events_for(:active)
=> [:disable]
(If you have more than one state machine there will be additional members in the YourClass.state_machines Hash)
This answer is now more relevant
Basically - you have access to #product.state_evants, #product.state_transitions and #product.state_paths
Related
given the below;
aasm do
state :available, :intitial => true
state :presented
state :invited
event :present do
transitions :from => :available, :to => :presented
end
event :invite do
transitions :from => :presented, :to => :invited
end
event :provide do
transitions :from => [:presented, :invited], :to => :available
end
end
what is an optimal pattern for setting the time period that an object 'lives under' a given state ?
ie, once the 'present' event occurs, I'd like the object to maintain the 'presented' state for exactly two hours, i'm feeling like I will have to mangle the way aasm works to achieve this, any thoughts ?
extra: this aasm code is being inserted into an active record class in a rails app, postgres is the db. Thx!
I'm using state_machine to manage approvals on an ActiveRecord::Base class.
I'm using custom state values so I can store states as integers. This works well, except that I am getting the following warning (from StateMachine::Machine) on Rails startup:
# Both MyClass and its :state machine have defined a different default
# for "state". Use only one or the other for defining defaults to avoid
# unexpected behaviors.
I'm not observing any unexpected behaviors, so this isn't a big deal. I know that I could remove the :default value from my table schema (e.g., :default => 0) to make this error go away, but I would prefer to do it on the state_machine side.
Here is the state_machine code (in my_class.rb):
# ...
States = {
:pending => 0,
:approved => 1,
:rejected => 2,
}
state_machine :state, :initial => :pending do
States.each do |name, value|
state name, :value => value
end
event :approve do
transition all => :approved
end
event :reject do
transition all => :rejected
end
end
The problem is that state_machine wants to set the initial/default value to "pending" before it realizes that the default value should actually be 0.
Is it possible to define my states pre-initialization? It would be nice if I could pass State objects or a StateCollection to the Machine initializer, but it doesn't look like that is possible (looking at the source at https://github.com/pluginaweek/state_machine/blob/master/lib/state_machine/machine.rb).
We have a Ruby on Rails application.
We're using Ruby's aasm gem to manage states of objects.
has_state
aasm do
state :created, :initial => true
state :submitted
state :rejected
state :approved
event :submit do
transitions :to => :submitted, :from => [:created]
end
event :mark_as_incomplete do
transitions :to => :created, :from => [:submitted]
end
event :approve do
transitions :to => :approved, :from => [:submitted]
end
event :reject do
transitions :to => :rejected, :from => [:submitted]
end
end
If we know an object's current state, which can be obtained using
object.aasm_current_state
and we also know the state to transition to, how can we invoke the event?
Please note, the from-state and to-state are variables, so we need to do the above
dynamically. Of course, with certain to-state and from-state combination, the transition isn't available, in that case we should detect an error.
We're also assuming between any two state combination (to-state and from-state), there's only 1 event, I think theoretically there can be more than 1.
I think this can be achieved by delving into the innards of aasm source code,
which, arguably, may not be a good practice. Any thoughts?
Just wonder if anyone has done this before.
Thanks!
There is no way provided by AASM to do this, but your own answer is getting already close enough to where you want to go. AASM is built around the assumption, that state machines allow multiple different transitions from one state to another.
If the event name is not relevant for you, you could reuse the to-state name as event name, like this:
aasm do
...
event :approved do
transitions :from => :submitted, :to => :approved
end
...
end
By this you can fire the event by just knowing the to-state name
approval_request.send(to_state)
By default, AASM raises an exception if this transition is not allowed. If you don't like the exception, set whiny_transitions to false, like this:
aasm :whiny_transitions => false do
...
end
This is the code I have. to_state and from_state are the states from and to.
ApprovalRequest.aasm_events.each do |event_key, event_obj|
if event_obj.transitions_from_state?(from_state) &&
event_obj.transitions_to_state?(to_state)
self.approval_request.send "#{event_key.to_s}!"
end
end
Any comments on this implementation?
This code shows what I'd like to do, but of course won't work because the Parent does not yet have an id:
class Parent < ActiveRecord::Base
has_many :children
after_initialize :find_children, :if => Proc.new {|parent| parent.new_record?}
private
def find_children
Child.where("blah blah blah").each do |child|
child.parent = self
#etc, etc, etc
end
end
end
It's almost as if my controller's "new" action needs to save the Parent before displaying the new form. This doesn't feel right. What is a good approach to this problem?
Update
The child objects in my specific case are BillTransactions (think fees and credits) and the parents are Bills. Throughout a billing period, these transactions are accrued on an Account. At the end of the billing period, the user creates a bill for a given period, hence the need for a bill to find its children when it's created.
I've been thinking about this some more after I posted the question. Since the Bill and BillTransactions can exist in many different states (pending, draft, active, emailed, etc) I'm going to use a state machine to manage the object's lifecycle. So far this is what I've come up with:
class Bill < ActiveRecord::Base
belongs_to :account
has_many :bill_transactions
attr_accessible :account, :bill_period_start, :bill_period_end
after_initialize :find_fees, :if => Proc.new {|bill| bill.new_record?}
validates_presence_of :account, :bill_period_start, :bill_period_end
state_machine :initial => :pending do
after_transition :pending => :canceled, :do => :destroy_self
before_transition :active => :emailed, :do => :email_bill
event :save_draft do
transition :pending => :draft
end
event :activate do
transition [:pending, :draft] => :active
end
event :email do
transition :active => :emailed
end
event :apply_payment do
transition [:active, :emailed] => :partial
transition [:active, :emailed, :partial] => :paid
end
event :cancel do
transition [:pending, :draft] => :canceled
end
end
private
def find_fees
self.save
unless [account, bill_period_start, bill_period_end].any? {|attr| attr.nil? }
BillTransaction.where(:account_id => account.id, :transaction_date => bill_period_start..bill_period_end, :transaction_type => BillTransaction::TRANS_TYPES['Fee']).each do |fee|
fee.stage self
end
end
end
def destroy_self
self.bill_transactions.each do |trans|
trans.unstage
end
self.destroy
end
end
So after a Bill is initialized for the first time, it basically saves itself, finds all relevant transactions, and "stages" them. This means BillTransaction's state is set to staged (which can transition back to unbilled if the new bill is destroyed) and its bill_id is set to the current Bill's id. You can see that if a Bill in the pending state is canceled, all of the transactions are unstaged (returned to the unbilled state).
The problem with this solution is that sending a GET request to BillsController#new is supposed to be idempotent. This solution isn't strictly idempotent and I'm having a hard time seeing how I can ensure that the server's state will be rolled back if the user navigates away from the new form.
Am I heading down a painful path here?
I would create a new "creator" method on Bill that returns a new bill with associated transactions attached. Something like:
def self.NewWithTransactions
bill = Bill.new
bill_transactions = find_candidate_transactions
bill
end
Then from your controller's new action, just do:
bill = Bill.NewWithTransactions
Throw that back to your view and you should be able to create the new bill with transactions attached when submitted. If that doesn't work, you probably have to do as one of the commentors suggested and send the unassociated transactions to your view and reassociate them in the create action.
I have a User model, and I want to have a user_status attribute.
I want this attribute to be stored as an integer in the database.
I was thinking of then creating a enum, and then mapping that enum to the integer value so I could do things like:
if user.status == MyEnum::RequiresApproval
..
..
Using active_record, at the model level, is there something I can do with the enum?
What is normally done in this kind of situation?
Enums are not very Rails like. State Machines are.
Check out the 'transitions' gem (link) (which was almost part of Rails Core)
And then you can do the following ...
#GemFile
gem "transitions", :require => ["transitions", "active_record/transitions"]
#And in your Model, do something like the following:
include ActiveRecord::Transitions
field :state, type: String
scope :active, where(state: 'active')
state_machine do
state :active
state :inactive
event :inactivate do
transitions :from => :active, :to => :inactive
end
event :activate do
transitions :from => :inactive, :to => :active
end
end
It was a transition for me too not to use enums and type tables -- but I haven't missed them