-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
353 additions
and
361 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
module AASM | ||
class Event | ||
|
||
attr_reader :name, :options | ||
|
||
def initialize(name, options = {}, &block) | ||
@name = name | ||
@transitions = [] | ||
update(options, &block) | ||
end | ||
|
||
# a neutered version of fire - it doesn't actually fire the event, it just | ||
# executes the transition guards to determine if a transition is even | ||
# an option given current conditions. | ||
def may_fire?(obj, to_state=nil, *args) | ||
_fire(obj, true, to_state, *args) # true indicates test firing | ||
end | ||
|
||
def fire(obj, to_state=nil, *args) | ||
_fire(obj, false, to_state, *args) # false indicates this is not a test (fire!) | ||
end | ||
|
||
def transitions_from_state?(state) | ||
transitions_from_state(state).any? | ||
end | ||
|
||
def transitions_from_state(state) | ||
@transitions.select { |t| t.from == state } | ||
end | ||
|
||
def transitions_to_state?(state) | ||
transitions_to_state(state).any? | ||
end | ||
|
||
def transitions_to_state(state) | ||
@transitions.select { |t| t.to == state } | ||
end | ||
|
||
def all_transitions | ||
@transitions | ||
end | ||
|
||
def fire_callbacks(callback_name, record, *args) | ||
invoke_callbacks(@options[callback_name], record, args) | ||
end | ||
|
||
def ==(event) | ||
if event.is_a? Symbol | ||
name == event | ||
else | ||
name == event.name | ||
end | ||
end | ||
|
||
private | ||
|
||
def update(options = {}, &block) | ||
@options = options | ||
if block then | ||
instance_eval(&block) | ||
end | ||
self | ||
end | ||
|
||
# Execute if test? == false, otherwise return true/false depending on whether it would fire | ||
def _fire(obj, test, to_state=nil, *args) | ||
if @transitions.map(&:from).any? | ||
transitions = @transitions.select { |t| t.from == obj.aasm_current_state } | ||
return nil if transitions.size == 0 | ||
else | ||
transitions = @transitions | ||
end | ||
|
||
result = test ? false : nil | ||
transitions.each do |transition| | ||
next if to_state and !Array(transition.to).include?(to_state) | ||
if transition.perform(obj, *args) | ||
if test | ||
result = true | ||
else | ||
result = to_state || Array(transition.to).first | ||
transition.execute(obj, *args) | ||
end | ||
|
||
break | ||
end | ||
end | ||
result | ||
end | ||
|
||
def invoke_callbacks(code, record, args) | ||
case code | ||
when Symbol, String | ||
record.send(code, *args) | ||
true | ||
when Proc | ||
record.instance_exec(*args, &code) | ||
true | ||
when Array | ||
code.each {|a| invoke_callbacks(a, record, args)} | ||
true | ||
else | ||
false | ||
end | ||
end | ||
|
||
## DSL interface | ||
def transitions(trans_opts) | ||
# Create a separate transition for each from state to the given state | ||
Array(trans_opts[:from]).each do |s| | ||
@transitions << AASM::Transition.new(trans_opts.merge({:from => s.to_sym})) | ||
end | ||
# Create a transition if to is specified without from (transitions from ANY state) | ||
@transitions << AASM::Transition.new(trans_opts) if @transitions.empty? && trans_opts[:to] | ||
end | ||
|
||
[:after, :before, :error, :success].each do |callback_name| | ||
define_method callback_name do |*args, &block| | ||
options[callback_name] = Array(options[callback_name]) | ||
options[callback_name] << block if block | ||
options[callback_name] += Array(args) | ||
end | ||
end | ||
end | ||
end # AASM |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
module AASM | ||
class Localizer | ||
def human_event_name(klass, event) | ||
checklist = ancestors_list(klass).inject([]) do |list, ancestor| | ||
list << :"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}" | ||
list | ||
end | ||
translate_queue(checklist) || I18n.translate(checklist.shift, :default => event.to_s.humanize) | ||
end | ||
|
||
def human_state_name(klass, state) | ||
checklist = ancestors_list(klass).inject([]) do |list, ancestor| | ||
list << item_for(klass, state, ancestor) | ||
list << item_for(klass, state, ancestor, :old_style => true) | ||
list | ||
end | ||
translate_queue(checklist) || I18n.translate(checklist.shift, :default => state.to_s.humanize) | ||
end | ||
|
||
private | ||
|
||
def item_for(klass, state, ancestor, options={}) | ||
separator = options[:old_style] ? '.' : '/' | ||
:"#{i18n_scope(klass)}.attributes.#{i18n_klass(ancestor)}.#{klass.aasm_column}#{separator}#{state}" | ||
end | ||
|
||
def translate_queue(checklist) | ||
(0...(checklist.size-1)).each do |i| | ||
begin | ||
return I18n.translate(checklist.shift, :raise => true) | ||
rescue I18n::MissingTranslationData | ||
# that's okay | ||
end | ||
end | ||
nil | ||
end | ||
|
||
# added for rails 2.x compatibility | ||
def i18n_scope(klass) | ||
klass.respond_to?(:i18n_scope) ? klass.i18n_scope : :activerecord | ||
end | ||
|
||
# added for rails < 3.0.3 compatibility | ||
def i18n_klass(klass) | ||
klass.model_name.respond_to?(:i18n_key) ? klass.model_name.i18n_key : klass.name.underscore | ||
end | ||
|
||
def ancestors_list(klass) | ||
klass.ancestors.select do |ancestor| | ||
ancestor.respond_to?(:model_name) unless ancestor == ActiveRecord::Base | ||
end | ||
end | ||
end | ||
end # AASM |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
module AASM | ||
class State | ||
attr_reader :name, :options | ||
|
||
def initialize(name, clazz, options={}) | ||
@name = name | ||
@clazz = clazz | ||
update(options) | ||
end | ||
|
||
def ==(state) | ||
if state.is_a? Symbol | ||
name == state | ||
else | ||
name == state.name | ||
end | ||
end | ||
|
||
def <=>(state) | ||
if state.is_a? Symbol | ||
name <=> state | ||
else | ||
name <=> state.name | ||
end | ||
end | ||
|
||
def to_s | ||
name.to_s | ||
end | ||
|
||
def fire_callbacks(action, record) | ||
action = @options[action] | ||
catch :halt_aasm_chain do | ||
action.is_a?(Array) ? | ||
action.each {|a| _fire_callbacks(a, record)} : | ||
_fire_callbacks(action, record) | ||
end | ||
end | ||
|
||
def display_name | ||
@display_name ||= begin | ||
if Module.const_defined?(:I18n) | ||
localized_name | ||
else | ||
name.to_s.gsub(/_/, ' ').capitalize | ||
end | ||
end | ||
end | ||
|
||
def localized_name | ||
AASM::Localizer.new.human_state_name(@clazz, self) | ||
end | ||
|
||
def for_select | ||
[display_name, name.to_s] | ||
end | ||
|
||
private | ||
|
||
def update(options = {}) | ||
if options.key?(:display) then | ||
@display_name = options.delete(:display) | ||
end | ||
@options = options | ||
self | ||
end | ||
|
||
def _fire_callbacks(action, record) | ||
case action | ||
when Symbol, String | ||
record.send(action) | ||
when Proc | ||
action.call(record) | ||
end | ||
end | ||
|
||
end | ||
end # AASM |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.