aasm 2.1.1 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.document +6 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
- data/.gitignore +20 -0
- data/.travis.yml +82 -0
- data/API +34 -0
- data/Appraisals +67 -0
- data/CHANGELOG.md +453 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +24 -0
- data/Dockerfile +44 -0
- data/Gemfile +6 -0
- data/Gemfile.lock_old +151 -0
- data/HOWTO +12 -0
- data/{MIT-LICENSE → LICENSE} +1 -1
- data/PLANNED_CHANGES.md +11 -0
- data/README.md +1524 -0
- data/README_FROM_VERSION_3_TO_4.md +240 -0
- data/Rakefile +20 -84
- data/TESTING.md +25 -0
- data/aasm.gemspec +37 -0
- data/docker-compose.yml +40 -0
- data/gemfiles/norails.gemfile +10 -0
- data/gemfiles/rails_4.2.gemfile +17 -0
- data/gemfiles/rails_4.2_mongoid_5.gemfile +12 -0
- data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.0.gemfile +14 -0
- data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
- data/gemfiles/rails_5.1.gemfile +14 -0
- data/gemfiles/rails_5.2.gemfile +14 -0
- data/lib/aasm/aasm.rb +160 -137
- data/lib/aasm/base.rb +290 -0
- data/lib/aasm/configuration.rb +48 -0
- data/lib/aasm/core/event.rb +177 -0
- data/lib/aasm/core/invoker.rb +129 -0
- data/lib/aasm/core/invokers/base_invoker.rb +75 -0
- data/lib/aasm/core/invokers/class_invoker.rb +52 -0
- data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
- data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
- data/lib/aasm/core/state.rb +91 -0
- data/lib/aasm/core/transition.rb +83 -0
- data/lib/aasm/dsl_helper.rb +32 -0
- data/lib/aasm/errors.rb +21 -0
- data/lib/aasm/instance_base.rb +133 -0
- data/lib/aasm/localizer.rb +64 -0
- data/lib/aasm/minitest/allow_event.rb +13 -0
- data/lib/aasm/minitest/allow_transition_to.rb +13 -0
- data/lib/aasm/minitest/have_state.rb +13 -0
- data/lib/aasm/minitest/transition_from.rb +21 -0
- data/lib/aasm/minitest.rb +5 -0
- data/lib/aasm/minitest_spec.rb +15 -0
- data/lib/aasm/persistence/active_record_persistence.rb +108 -173
- data/lib/aasm/persistence/base.rb +89 -0
- data/lib/aasm/persistence/core_data_query_persistence.rb +94 -0
- data/lib/aasm/persistence/dynamoid_persistence.rb +92 -0
- data/lib/aasm/persistence/mongoid_persistence.rb +126 -0
- data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
- data/lib/aasm/persistence/orm.rb +154 -0
- data/lib/aasm/persistence/plain_persistence.rb +26 -0
- data/lib/aasm/persistence/redis_persistence.rb +112 -0
- data/lib/aasm/persistence/sequel_persistence.rb +83 -0
- data/lib/aasm/persistence.rb +48 -10
- data/lib/aasm/rspec/allow_event.rb +26 -0
- data/lib/aasm/rspec/allow_transition_to.rb +26 -0
- data/lib/aasm/rspec/have_state.rb +22 -0
- data/lib/aasm/rspec/transition_from.rb +36 -0
- data/lib/aasm/rspec.rb +5 -0
- data/lib/aasm/state_machine.rb +40 -22
- data/lib/aasm/state_machine_store.rb +76 -0
- data/lib/aasm/version.rb +3 -0
- data/lib/aasm.rb +21 -1
- data/lib/generators/aasm/aasm_generator.rb +16 -0
- data/lib/generators/aasm/orm_helpers.rb +41 -0
- data/lib/generators/active_record/aasm_generator.rb +40 -0
- data/lib/generators/active_record/templates/migration.rb +8 -0
- data/lib/generators/active_record/templates/migration_existing.rb +5 -0
- data/lib/generators/mongoid/aasm_generator.rb +28 -0
- data/lib/generators/nobrainer/aasm_generator.rb +28 -0
- data/lib/motion-aasm.rb +37 -0
- data/spec/database.rb +57 -0
- data/spec/database.yml +3 -0
- data/spec/en.yml +9 -0
- data/spec/generators/active_record_generator_spec.rb +53 -0
- data/spec/generators/mongoid_generator_spec.rb +31 -0
- data/spec/generators/no_brainer_generator_spec.rb +29 -0
- data/spec/localizer_test_model_deprecated_style.yml +13 -0
- data/spec/localizer_test_model_new_style.yml +11 -0
- data/spec/models/active_record/active_record_callback.rb +93 -0
- data/spec/models/active_record/basic_active_record_two_state_machines_example.rb +25 -0
- data/spec/models/active_record/complex_active_record_example.rb +37 -0
- data/spec/models/active_record/derivate_new_dsl.rb +7 -0
- data/spec/models/active_record/false_state.rb +35 -0
- data/spec/models/active_record/gate.rb +39 -0
- data/spec/models/active_record/instance_level_skip_validation_example.rb +19 -0
- data/spec/models/active_record/invalid_persistor.rb +29 -0
- data/spec/models/active_record/localizer_test_model.rb +42 -0
- data/spec/models/active_record/namespaced.rb +16 -0
- data/spec/models/active_record/no_direct_assignment.rb +21 -0
- data/spec/models/active_record/no_scope.rb +21 -0
- data/spec/models/active_record/persisted_state.rb +12 -0
- data/spec/models/active_record/person.rb +23 -0
- data/spec/models/active_record/provided_and_persisted_state.rb +24 -0
- data/spec/models/active_record/reader.rb +7 -0
- data/spec/models/active_record/readme_job.rb +21 -0
- data/spec/models/active_record/silent_persistor.rb +29 -0
- data/spec/models/active_record/simple_new_dsl.rb +32 -0
- data/spec/models/active_record/thief.rb +29 -0
- data/spec/models/active_record/timestamp_example.rb +16 -0
- data/spec/models/active_record/transactor.rb +124 -0
- data/spec/models/active_record/transient.rb +6 -0
- data/spec/models/active_record/validator.rb +118 -0
- data/spec/models/active_record/with_enum.rb +39 -0
- data/spec/models/active_record/with_enum_without_column.rb +38 -0
- data/spec/models/active_record/with_false_enum.rb +31 -0
- data/spec/models/active_record/with_true_enum.rb +39 -0
- data/spec/models/active_record/work.rb +3 -0
- data/spec/models/active_record/worker.rb +2 -0
- data/spec/models/active_record/writer.rb +6 -0
- data/spec/models/basic_two_state_machines_example.rb +25 -0
- data/spec/models/callbacks/basic.rb +98 -0
- data/spec/models/callbacks/basic_multiple.rb +75 -0
- data/spec/models/callbacks/guard_within_block.rb +67 -0
- data/spec/models/callbacks/guard_within_block_multiple.rb +66 -0
- data/spec/models/callbacks/multiple_transitions_transition_guard.rb +66 -0
- data/spec/models/callbacks/multiple_transitions_transition_guard_multiple.rb +65 -0
- data/spec/models/callbacks/private_method.rb +44 -0
- data/spec/models/callbacks/private_method_multiple.rb +44 -0
- data/spec/models/callbacks/with_args.rb +62 -0
- data/spec/models/callbacks/with_args_multiple.rb +61 -0
- data/spec/models/callbacks/with_state_arg.rb +34 -0
- data/spec/models/callbacks/with_state_arg_multiple.rb +29 -0
- data/spec/models/complex_example.rb +222 -0
- data/spec/models/conversation.rb +93 -0
- data/spec/models/default_state.rb +12 -0
- data/spec/models/double_definer.rb +21 -0
- data/spec/models/dynamoid/complex_dynamoid_example.rb +37 -0
- data/spec/models/dynamoid/dynamoid_multiple.rb +18 -0
- data/spec/models/dynamoid/dynamoid_simple.rb +18 -0
- data/spec/models/foo.rb +106 -0
- data/spec/models/foo_callback_multiple.rb +45 -0
- data/spec/models/guard_arguments_check.rb +17 -0
- data/spec/models/guard_with_params.rb +24 -0
- data/spec/models/guard_with_params_multiple.rb +18 -0
- data/spec/models/guardian.rb +58 -0
- data/spec/models/guardian_multiple.rb +48 -0
- data/spec/models/guardian_without_from_specified.rb +18 -0
- data/spec/models/initial_state_proc.rb +31 -0
- data/spec/models/mongoid/complex_mongoid_example.rb +37 -0
- data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
- data/spec/models/mongoid/mongoid_relationships.rb +26 -0
- data/spec/models/mongoid/no_scope_mongoid.rb +21 -0
- data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
- data/spec/models/mongoid/simple_mongoid.rb +23 -0
- data/spec/models/mongoid/simple_new_dsl_mongoid.rb +25 -0
- data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
- data/spec/models/mongoid/validator_mongoid.rb +100 -0
- data/spec/models/multi_transitioner.rb +34 -0
- data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
- data/spec/models/namespaced_multiple_example.rb +42 -0
- data/spec/models/no_initial_state.rb +25 -0
- data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
- data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
- data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
- data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
- data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
- data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
- data/spec/models/nobrainer/simple_no_brainer.rb +23 -0
- data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
- data/spec/models/not_auto_loaded/process.rb +21 -0
- data/spec/models/parametrised_event.rb +42 -0
- data/spec/models/parametrised_event_multiple.rb +29 -0
- data/spec/models/process_with_new_dsl.rb +31 -0
- data/spec/models/provided_state.rb +24 -0
- data/spec/models/redis/complex_redis_example.rb +40 -0
- data/spec/models/redis/redis_multiple.rb +20 -0
- data/spec/models/redis/redis_simple.rb +20 -0
- data/spec/models/sequel/complex_sequel_example.rb +46 -0
- data/spec/models/sequel/invalid_persistor.rb +52 -0
- data/spec/models/sequel/sequel_multiple.rb +25 -0
- data/spec/models/sequel/sequel_simple.rb +26 -0
- data/spec/models/sequel/silent_persistor.rb +50 -0
- data/spec/models/sequel/transactor.rb +112 -0
- data/spec/models/sequel/validator.rb +93 -0
- data/spec/models/sequel/worker.rb +12 -0
- data/spec/models/silencer.rb +27 -0
- data/spec/models/simple_custom_example.rb +53 -0
- data/spec/models/simple_example.rb +23 -0
- data/spec/models/simple_example_with_guard_args.rb +17 -0
- data/spec/models/simple_multiple_example.rb +42 -0
- data/spec/models/state_machine_with_failed_event.rb +20 -0
- data/spec/models/states_on_one_line_example.rb +8 -0
- data/spec/models/sub_class.rb +41 -0
- data/spec/models/sub_class_with_more_states.rb +18 -0
- data/spec/models/sub_classing.rb +3 -0
- data/spec/models/super_class.rb +46 -0
- data/spec/models/this_name_better_not_be_in_use.rb +11 -0
- data/spec/models/timestamps_example.rb +19 -0
- data/spec/models/timestamps_with_named_machine_example.rb +13 -0
- data/spec/models/valid_state_name.rb +23 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/spec_helpers/active_record.rb +8 -0
- data/spec/spec_helpers/dynamoid.rb +35 -0
- data/spec/spec_helpers/mongoid.rb +26 -0
- data/spec/spec_helpers/nobrainer.rb +15 -0
- data/spec/spec_helpers/redis.rb +18 -0
- data/spec/spec_helpers/remove_warnings.rb +1 -0
- data/spec/spec_helpers/sequel.rb +7 -0
- data/spec/unit/abstract_class_spec.rb +27 -0
- data/spec/unit/api_spec.rb +104 -0
- data/spec/unit/basic_two_state_machines_example_spec.rb +10 -0
- data/spec/unit/callback_multiple_spec.rb +304 -0
- data/spec/unit/callbacks_spec.rb +521 -0
- data/spec/unit/complex_example_spec.rb +93 -0
- data/spec/unit/complex_multiple_example_spec.rb +115 -0
- data/spec/unit/edge_cases_spec.rb +16 -0
- data/spec/unit/event_multiple_spec.rb +73 -0
- data/spec/unit/event_naming_spec.rb +16 -0
- data/spec/unit/event_spec.rb +394 -0
- data/spec/unit/exception_spec.rb +11 -0
- data/spec/unit/guard_arguments_check_spec.rb +9 -0
- data/spec/unit/guard_multiple_spec.rb +60 -0
- data/spec/unit/guard_spec.rb +89 -0
- data/spec/unit/guard_with_params_multiple_spec.rb +10 -0
- data/spec/unit/guard_with_params_spec.rb +14 -0
- data/spec/unit/guard_without_from_specified_spec.rb +10 -0
- data/spec/unit/initial_state_multiple_spec.rb +15 -0
- data/spec/unit/initial_state_spec.rb +12 -0
- data/spec/unit/inspection_multiple_spec.rb +205 -0
- data/spec/unit/inspection_spec.rb +153 -0
- data/spec/unit/invoker_spec.rb +189 -0
- data/spec/unit/invokers/base_invoker_spec.rb +72 -0
- data/spec/unit/invokers/class_invoker_spec.rb +95 -0
- data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
- data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
- data/spec/unit/localizer_spec.rb +109 -0
- data/spec/unit/memory_leak_spec.rb +38 -0
- data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
- data/spec/unit/namespaced_multiple_example_spec.rb +75 -0
- data/spec/unit/new_dsl_spec.rb +12 -0
- data/spec/unit/override_warning_spec.rb +94 -0
- data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +635 -0
- data/spec/unit/persistence/active_record_persistence_spec.rb +852 -0
- data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +135 -0
- data/spec/unit/persistence/dynamoid_persistence_spec.rb +84 -0
- data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +200 -0
- data/spec/unit/persistence/mongoid_persistence_spec.rb +177 -0
- data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
- data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
- data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
- data/spec/unit/persistence/redis_persistence_spec.rb +53 -0
- data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +148 -0
- data/spec/unit/persistence/sequel_persistence_spec.rb +368 -0
- data/spec/unit/readme_spec.rb +41 -0
- data/spec/unit/reloading_spec.rb +15 -0
- data/spec/unit/rspec_matcher_spec.rb +88 -0
- data/spec/unit/simple_custom_example_spec.rb +39 -0
- data/spec/unit/simple_example_spec.rb +57 -0
- data/spec/unit/simple_multiple_example_spec.rb +91 -0
- data/spec/unit/state_spec.rb +105 -0
- data/spec/unit/states_on_one_line_example_spec.rb +16 -0
- data/spec/unit/subclassing_multiple_spec.rb +74 -0
- data/spec/unit/subclassing_spec.rb +46 -0
- data/spec/unit/timestamps_spec.rb +32 -0
- data/spec/unit/transition_spec.rb +436 -0
- data/test/minitest_helper.rb +57 -0
- data/test/unit/minitest_matcher_test.rb +80 -0
- metadata +607 -60
- data/CHANGELOG +0 -33
- data/README.rdoc +0 -122
- data/TODO +0 -9
- data/doc/jamis.rb +0 -591
- data/lib/aasm/event.rb +0 -76
- data/lib/aasm/state.rb +0 -35
- data/lib/aasm/state_transition.rb +0 -36
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
RSpec::Matchers.define :allow_transition_to do |state|
|
|
2
|
+
match do |obj|
|
|
3
|
+
@state_machine_name ||= :default
|
|
4
|
+
obj.aasm(@state_machine_name).states({:permitted => true}, *@args).include?(state)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
chain :on do |state_machine_name|
|
|
8
|
+
@state_machine_name = state_machine_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
chain :with do |*args|
|
|
12
|
+
@args = args
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
description do
|
|
16
|
+
"allow transition to #{expected} (on :#{@state_machine_name})"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
failure_message do |obj|
|
|
20
|
+
"expected that the state :#{expected} would be reachable (on :#{@state_machine_name})"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
failure_message_when_negated do |obj|
|
|
24
|
+
"expected that the state :#{expected} would not be reachable (on :#{@state_machine_name})"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
RSpec::Matchers.define :have_state do |state|
|
|
2
|
+
match do |obj|
|
|
3
|
+
@state_machine_name ||= :default
|
|
4
|
+
obj.aasm(@state_machine_name).current_state == state.to_sym
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
chain :on do |state_machine_name|
|
|
8
|
+
@state_machine_name = state_machine_name
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
description do
|
|
12
|
+
"have state #{expected} (on :#{@state_machine_name})"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
failure_message do |obj|
|
|
16
|
+
"expected that :#{obj.aasm(@state_machine_name).current_state} would be :#{expected} (on :#{@state_machine_name})"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
failure_message_when_negated do |obj|
|
|
20
|
+
"expected that :#{obj.aasm(@state_machine_name).current_state} would not be :#{expected} (on :#{@state_machine_name})"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
RSpec::Matchers.define :transition_from do |from_state|
|
|
2
|
+
match do |obj|
|
|
3
|
+
@state_machine_name ||= :default
|
|
4
|
+
obj.aasm(@state_machine_name).current_state = from_state.to_sym
|
|
5
|
+
begin
|
|
6
|
+
obj.send(@event, *@args) && obj.aasm(@state_machine_name).current_state == @to_state.to_sym
|
|
7
|
+
rescue AASM::InvalidTransition
|
|
8
|
+
false
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
chain :on do |state_machine_name|
|
|
13
|
+
@state_machine_name = state_machine_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
chain :to do |state|
|
|
17
|
+
@to_state = state
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
chain :on_event do |event, *args|
|
|
21
|
+
@event = event
|
|
22
|
+
@args = args
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
description do
|
|
26
|
+
"transition state to :#{@to_state} from :#{expected} on event :#{@event}, with params: #{@args} (on :#{@state_machine_name})"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
failure_message do |obj|
|
|
30
|
+
"expected that :#{obj.aasm(@state_machine_name).current_state} would be :#{@to_state} (on :#{@state_machine_name})"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
failure_message_when_negated do |obj|
|
|
34
|
+
"expected that :#{obj.aasm(@state_machine_name).current_state} would not be :#{@to_state} (on :#{@state_machine_name})"
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/aasm/rspec.rb
ADDED
data/lib/aasm/state_machine.rb
CHANGED
|
@@ -1,35 +1,53 @@
|
|
|
1
|
-
require 'ostruct'
|
|
2
|
-
|
|
3
1
|
module AASM
|
|
4
2
|
class StateMachine
|
|
5
|
-
|
|
6
|
-
(@machines ||= {})[args]
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
def self.[]=(*args)
|
|
10
|
-
val = args.pop
|
|
11
|
-
(@machines ||= {})[args] = val
|
|
12
|
-
end
|
|
3
|
+
# the following four methods provide the storage of all state machines
|
|
13
4
|
|
|
14
|
-
attr_accessor :states, :events, :initial_state, :config
|
|
15
|
-
attr_reader :name
|
|
5
|
+
attr_accessor :states, :events, :initial_state, :config, :name, :global_callbacks
|
|
16
6
|
|
|
17
7
|
def initialize(name)
|
|
18
|
-
@name = name
|
|
19
8
|
@initial_state = nil
|
|
20
9
|
@states = []
|
|
21
10
|
@events = {}
|
|
22
|
-
@
|
|
11
|
+
@global_callbacks = {}
|
|
12
|
+
@config = AASM::Configuration.new
|
|
13
|
+
@name = name
|
|
23
14
|
end
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
16
|
+
# called internally by Ruby 1.9 after clone()
|
|
17
|
+
def initialize_copy(orig)
|
|
18
|
+
super
|
|
19
|
+
@states = orig.states.collect { |state| state.clone }
|
|
20
|
+
@events = {}
|
|
21
|
+
orig.events.each_pair { |name, event| @events[name] = event.clone }
|
|
22
|
+
@global_callbacks = @global_callbacks.dup
|
|
29
23
|
end
|
|
30
24
|
|
|
31
|
-
def
|
|
32
|
-
|
|
25
|
+
def add_state(state_name, klass, options)
|
|
26
|
+
set_initial_state(state_name, options)
|
|
27
|
+
|
|
28
|
+
# allow reloading, extending or redefining a state
|
|
29
|
+
@states.delete(state_name) if @states.include?(state_name)
|
|
30
|
+
|
|
31
|
+
@states << AASM::Core::State.new(state_name, klass, self, options)
|
|
33
32
|
end
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
|
|
34
|
+
def add_event(name, options, &block)
|
|
35
|
+
@events[name] = AASM::Core::Event.new(name, self, options, &block)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add_global_callbacks(name, *callbacks, &block)
|
|
39
|
+
@global_callbacks[name] ||= []
|
|
40
|
+
callbacks.each do |callback|
|
|
41
|
+
@global_callbacks[name] << callback unless @global_callbacks[name].include? callback
|
|
42
|
+
end
|
|
43
|
+
@global_callbacks[name] << block if block
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def set_initial_state(name, options)
|
|
49
|
+
@initial_state = name if options[:initial] || !initial_state
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end # StateMachine
|
|
53
|
+
end # AASM
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
require 'concurrent'
|
|
2
|
+
module AASM
|
|
3
|
+
class StateMachineStore
|
|
4
|
+
@stores = Concurrent::Map.new
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def stores
|
|
8
|
+
@stores
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
# do not overwrite existing state machines, which could have been created by
|
|
12
|
+
# inheritance, see AASM::ClassMethods method inherited
|
|
13
|
+
def register(klass, overwrite = false, state_machine = nil)
|
|
14
|
+
raise "Cannot register #{klass}" unless klass.is_a?(Class)
|
|
15
|
+
|
|
16
|
+
case name = template = overwrite
|
|
17
|
+
when FalseClass then stores[klass.to_s] ||= new
|
|
18
|
+
when TrueClass then stores[klass.to_s] = new
|
|
19
|
+
when Class then stores[klass.to_s] = stores[template.to_s].clone
|
|
20
|
+
when Symbol then stores[klass.to_s].register(name, state_machine)
|
|
21
|
+
when String then stores[klass.to_s].register(name, state_machine)
|
|
22
|
+
else raise "Don't know what to do with #{overwrite}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
alias_method :[]=, :register
|
|
26
|
+
|
|
27
|
+
def fetch(klass, fallback = nil)
|
|
28
|
+
stores[klass.to_s] || fallback && begin
|
|
29
|
+
match = klass.ancestors.find do |ancestor|
|
|
30
|
+
ancestor.include? AASM and stores[ancestor.to_s]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
stores[match.to_s]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
alias_method :[], :fetch
|
|
37
|
+
|
|
38
|
+
def unregister(klass)
|
|
39
|
+
stores.delete(klass.to_s)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def initialize
|
|
44
|
+
@machines = Concurrent::Map.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def clone
|
|
48
|
+
StateMachineStore.new.tap do |store|
|
|
49
|
+
@machines.each_pair do |name, machine|
|
|
50
|
+
store.register(name, machine.clone)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def machine(name)
|
|
56
|
+
@machines[name.to_s]
|
|
57
|
+
end
|
|
58
|
+
alias_method :[], :machine
|
|
59
|
+
|
|
60
|
+
def machine_names
|
|
61
|
+
@machines.keys
|
|
62
|
+
end
|
|
63
|
+
alias_method :keys, :machine_names
|
|
64
|
+
|
|
65
|
+
def register(name, machine, force = false)
|
|
66
|
+
raise "Cannot use #{name.inspect} for machine name" unless name.is_a?(Symbol) or name.is_a?(String)
|
|
67
|
+
raise "Cannot use #{machine.inspect} as a machine" unless machine.is_a?(AASM::StateMachine)
|
|
68
|
+
|
|
69
|
+
if force
|
|
70
|
+
@machines[name.to_s] = machine
|
|
71
|
+
else
|
|
72
|
+
@machines[name.to_s] ||= machine
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/aasm/version.rb
ADDED
data/lib/aasm.rb
CHANGED
|
@@ -1 +1,21 @@
|
|
|
1
|
-
require
|
|
1
|
+
require 'aasm/version'
|
|
2
|
+
require 'aasm/errors'
|
|
3
|
+
require 'aasm/configuration'
|
|
4
|
+
require 'aasm/base'
|
|
5
|
+
require 'aasm/dsl_helper'
|
|
6
|
+
require 'aasm/instance_base'
|
|
7
|
+
require 'aasm/core/transition'
|
|
8
|
+
require 'aasm/core/event'
|
|
9
|
+
require 'aasm/core/state'
|
|
10
|
+
require 'aasm/core/invoker'
|
|
11
|
+
require 'aasm/core/invokers/base_invoker'
|
|
12
|
+
require 'aasm/core/invokers/class_invoker'
|
|
13
|
+
require 'aasm/core/invokers/literal_invoker'
|
|
14
|
+
require 'aasm/core/invokers/proc_invoker'
|
|
15
|
+
require 'aasm/localizer'
|
|
16
|
+
require 'aasm/state_machine_store'
|
|
17
|
+
require 'aasm/state_machine'
|
|
18
|
+
require 'aasm/persistence'
|
|
19
|
+
require 'aasm/persistence/base'
|
|
20
|
+
require 'aasm/persistence/plain_persistence'
|
|
21
|
+
require 'aasm/aasm'
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'rails/generators/named_base'
|
|
2
|
+
|
|
3
|
+
module AASM
|
|
4
|
+
module Generators
|
|
5
|
+
class AASMGenerator < Rails::Generators::NamedBase
|
|
6
|
+
namespace "aasm"
|
|
7
|
+
argument :column_name, type: :string, default: 'aasm_state'
|
|
8
|
+
|
|
9
|
+
desc "Generates a model with the given NAME (if one does not exist) with aasm " <<
|
|
10
|
+
"block and migration to add aasm_state column."
|
|
11
|
+
|
|
12
|
+
hook_for :orm
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module AASM
|
|
2
|
+
module Generators
|
|
3
|
+
module OrmHelpers
|
|
4
|
+
|
|
5
|
+
def model_contents
|
|
6
|
+
if column_name == 'aasm_state'
|
|
7
|
+
<<RUBY
|
|
8
|
+
include AASM
|
|
9
|
+
|
|
10
|
+
aasm do
|
|
11
|
+
end
|
|
12
|
+
RUBY
|
|
13
|
+
else
|
|
14
|
+
<<RUBY
|
|
15
|
+
include AASM
|
|
16
|
+
|
|
17
|
+
aasm :column => '#{column_name}' do
|
|
18
|
+
end
|
|
19
|
+
RUBY
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def column_exists?
|
|
26
|
+
table_name.singularize.humanize.constantize.column_names.include?(column_name.to_s)
|
|
27
|
+
rescue NameError
|
|
28
|
+
false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def model_exists?
|
|
32
|
+
File.exists?(File.join(destination_root, model_path))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def model_path
|
|
36
|
+
@model_path ||= File.join("app", "models", "#{file_path}.rb")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require 'rails/generators/active_record'
|
|
2
|
+
require 'generators/aasm/orm_helpers'
|
|
3
|
+
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
module Generators
|
|
6
|
+
class AASMGenerator < ActiveRecord::Generators::Base
|
|
7
|
+
include AASM::Generators::OrmHelpers
|
|
8
|
+
namespace "active_record:aasm"
|
|
9
|
+
argument :column_name, type: :string, default: 'aasm_state'
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("../templates", __FILE__)
|
|
12
|
+
|
|
13
|
+
def copy_aasm_migration
|
|
14
|
+
if column_exists?
|
|
15
|
+
puts "Both model and column exists"
|
|
16
|
+
elsif model_exists?
|
|
17
|
+
migration_template "migration_existing.rb", "db/migrate/add_#{column_name}_to_#{table_name}.rb"
|
|
18
|
+
else
|
|
19
|
+
migration_template "migration.rb", "db/migrate/aasm_create_#{table_name}.rb"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def generate_model
|
|
24
|
+
invoke "active_record:model", [name], migration: false unless model_exists?
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def inject_aasm_content
|
|
28
|
+
content = model_contents
|
|
29
|
+
|
|
30
|
+
class_path = if namespaced?
|
|
31
|
+
class_name.to_s.split("::")
|
|
32
|
+
else
|
|
33
|
+
[class_name]
|
|
34
|
+
end
|
|
35
|
+
inject_into_class(model_path, class_path.last, content) if model_exists?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'rails/generators/named_base'
|
|
2
|
+
require 'generators/aasm/orm_helpers'
|
|
3
|
+
|
|
4
|
+
module Mongoid
|
|
5
|
+
module Generators
|
|
6
|
+
class AASMGenerator < Rails::Generators::NamedBase
|
|
7
|
+
include AASM::Generators::OrmHelpers
|
|
8
|
+
namespace "mongoid:aasm"
|
|
9
|
+
argument :column_name, type: :string, default: 'aasm_state'
|
|
10
|
+
|
|
11
|
+
def generate_model
|
|
12
|
+
invoke "mongoid:model", [name] unless model_exists?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def inject_aasm_content
|
|
16
|
+
inject_into_file model_path, model_contents, after: "include Mongoid::Document\n" if model_exists?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def inject_field_types
|
|
20
|
+
inject_into_file model_path, migration_data, after: "include Mongoid::Document\n" if model_exists?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def migration_data
|
|
24
|
+
" field :#{column_name}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require 'rails/generators/named_base'
|
|
2
|
+
require 'generators/aasm/orm_helpers'
|
|
3
|
+
|
|
4
|
+
module NoBrainer
|
|
5
|
+
module Generators
|
|
6
|
+
class AASMGenerator < Rails::Generators::NamedBase
|
|
7
|
+
include AASM::Generators::OrmHelpers
|
|
8
|
+
namespace 'nobrainer:aasm'
|
|
9
|
+
argument :column_name, type: :string, default: 'aasm_state'
|
|
10
|
+
|
|
11
|
+
def generate_model
|
|
12
|
+
invoke 'nobrainer:model', [name] unless model_exists?
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def inject_aasm_content
|
|
16
|
+
inject_into_file model_path, model_contents, after: "include NoBrainer::Document::Timestamps\n" if model_exists?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def inject_field_types
|
|
20
|
+
inject_into_file model_path, migration_data, after: "include NoBrainer::Document::Timestamps\n" if model_exists?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def migration_data
|
|
24
|
+
" field :#{column_name}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/motion-aasm.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
unless defined?(Motion::Project::App)
|
|
2
|
+
raise "This must be required from within a RubyMotion Rakefile"
|
|
3
|
+
end
|
|
4
|
+
|
|
5
|
+
file_dependencies = {
|
|
6
|
+
'aasm/aasm.rb' => ['aasm/persistence.rb'],
|
|
7
|
+
'aasm/persistence.rb' => ['aasm/persistence/plain_persistence.rb', 'aasm/persistence/core_data_query_persistence.rb'],
|
|
8
|
+
'aasm/persistence/base.rb' => ['aasm/base.rb'],
|
|
9
|
+
'aasm/persistence/core_data_query_persistence.rb' => ['aasm/persistence/base.rb']
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
exclude_files = [
|
|
13
|
+
'aasm/rspec.*',
|
|
14
|
+
'aasm/minitest.*',
|
|
15
|
+
'aasm/minitest_spec.*',
|
|
16
|
+
'aasm/persistence/active_record_persistence.rb',
|
|
17
|
+
'aasm/persistence/dynamoid_persistence.rb',
|
|
18
|
+
'aasm/persistence/mongoid_persistence.rb',
|
|
19
|
+
'aasm/persistence/no_brainer_persistence.rb',
|
|
20
|
+
'aasm/persistence/sequel_persistence.rb',
|
|
21
|
+
'aasm/persistence/redis_persistence.rb'
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
Motion::Project::App.setup do |app|
|
|
25
|
+
parent = File.expand_path File.dirname(__FILE__)
|
|
26
|
+
|
|
27
|
+
app.files.unshift Dir.glob(File.join(parent, "aasm/**/*.rb")).reject { |file| exclude_files.any? { |exclude| file.match(exclude) } }
|
|
28
|
+
|
|
29
|
+
app.files_dependencies file_dependencies.inject({}, &->(file_dependencies, (file, *dependencies)) do
|
|
30
|
+
file = File.join(parent, file)
|
|
31
|
+
dependencies = dependencies.flatten(1).map do |dependency|
|
|
32
|
+
File.join(parent, dependency)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
file_dependencies.merge({ file => dependencies })
|
|
36
|
+
end)
|
|
37
|
+
end
|
data/spec/database.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
ActiveRecord::Migration.suppress_messages do
|
|
2
|
+
%w{gates multiple_gates readers writers transients simples no_scopes multiple_no_scopes no_direct_assignments multiple_no_direct_assignments thieves multiple_thieves localizer_test_models persisted_states provided_and_persisted_states with_enums with_enum_without_columns multiple_with_enum_without_columns with_true_enums with_false_enums false_states multiple_with_enums multiple_with_true_enums multiple_with_false_enums multiple_false_states readme_jobs}.each do |table_name|
|
|
3
|
+
ActiveRecord::Migration.create_table table_name, :force => true do |t|
|
|
4
|
+
t.string "aasm_state"
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
%w(simple_new_dsls multiple_simple_new_dsls implemented_abstract_class_dsls users multiple_namespaceds).each do |table_name|
|
|
9
|
+
ActiveRecord::Migration.create_table table_name, :force => true do |t|
|
|
10
|
+
t.string "status"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
ActiveRecord::Migration.create_table "complex_active_record_examples", :force => true do |t|
|
|
15
|
+
t.string "left"
|
|
16
|
+
t.string "right"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
ActiveRecord::Migration.create_table "works", :force => true do |t|
|
|
20
|
+
t.string "status"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
%w(validators multiple_validators workers invalid_persistors multiple_invalid_persistors silent_persistors multiple_silent_persistors active_record_callbacks).each do |table_name|
|
|
24
|
+
ActiveRecord::Migration.create_table table_name, :force => true do |t|
|
|
25
|
+
t.string "name"
|
|
26
|
+
t.string "status"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
%w(transactors no_lock_transactors lock_transactors lock_no_wait_transactors no_transactors multiple_transactors).each do |table_name|
|
|
31
|
+
ActiveRecord::Migration.create_table table_name, :force => true do |t|
|
|
32
|
+
t.string "name"
|
|
33
|
+
t.string "status"
|
|
34
|
+
t.integer "worker_id"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
ActiveRecord::Migration.create_table "fathers", :force => true do |t|
|
|
39
|
+
t.string "aasm_state"
|
|
40
|
+
t.string "type"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
ActiveRecord::Migration.create_table "basic_active_record_two_state_machines_examples", :force => true do |t|
|
|
44
|
+
t.string "search"
|
|
45
|
+
t.string "sync"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
ActiveRecord::Migration.create_table "instance_level_skip_validation_examples", :force => true do |t|
|
|
49
|
+
t.string "state"
|
|
50
|
+
t.string "some_string"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
ActiveRecord::Migration.create_table "timestamp_examples", :force => true do |t|
|
|
54
|
+
t.string "aasm_state"
|
|
55
|
+
t.datetime "opened_at"
|
|
56
|
+
end
|
|
57
|
+
end
|
data/spec/database.yml
ADDED
data/spec/en.yml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if defined?(ActiveRecord)
|
|
4
|
+
require 'generator_spec'
|
|
5
|
+
require 'generators/active_record/aasm_generator'
|
|
6
|
+
|
|
7
|
+
describe ActiveRecord::Generators::AASMGenerator, type: :generator do
|
|
8
|
+
destination File.expand_path("../../../tmp", __FILE__)
|
|
9
|
+
|
|
10
|
+
before(:all) do
|
|
11
|
+
prepare_destination
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "creates model with aasm block for default column_name" do
|
|
15
|
+
run_generator %w(user)
|
|
16
|
+
assert_file "app/models/user.rb", /include AASM\n\n aasm do\n end\n/
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "creates model with aasm block for custom column_name" do
|
|
20
|
+
run_generator %w(user state)
|
|
21
|
+
assert_file "app/models/user.rb", /aasm :column => 'state' do\n end\n/
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "creates model with aasm block for namespaced model" do
|
|
25
|
+
run_generator %w(Admin::User state)
|
|
26
|
+
assert_file "app/models/admin/user.rb", /aasm :column => 'state' do\n end\n/
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "creates migration for model with aasm_column" do
|
|
30
|
+
run_generator %w(post)
|
|
31
|
+
assert_migration "db/migrate/aasm_create_posts.rb", /create_table(:posts) do |t|\n t.string :aasm_state\n/
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "add aasm_column in existing model" do
|
|
35
|
+
run_generator %w(job)
|
|
36
|
+
assert_file "app/models/job.rb"
|
|
37
|
+
run_generator %w(job)
|
|
38
|
+
assert_migration "db/migrate/add_aasm_state_to_jobs.rb"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "add custom aasm_column in existing model" do
|
|
42
|
+
run_generator %w(job state)
|
|
43
|
+
assert_migration "db/migrate/add_state_to_jobs.rb"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "dont add column if column is already exists" do
|
|
47
|
+
require 'models/active_record/work.rb'
|
|
48
|
+
load_schema
|
|
49
|
+
run_generator %w(work status)
|
|
50
|
+
assert_no_migration "db/migrate/add_status_to_jobs.rb"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if defined?(Mongoid::Document)
|
|
4
|
+
require 'generator_spec'
|
|
5
|
+
require 'generators/mongoid/aasm_generator'
|
|
6
|
+
|
|
7
|
+
describe Mongoid::Generators::AASMGenerator, type: :generator do
|
|
8
|
+
destination File.expand_path("../../../tmp", __FILE__)
|
|
9
|
+
|
|
10
|
+
before(:all) do
|
|
11
|
+
prepare_destination
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it "creates model with aasm block for default column_name" do
|
|
15
|
+
run_generator %w(user)
|
|
16
|
+
assert_file "app/models/user.rb", /include AASM\n\n aasm do\n end\n/
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it "creates model with aasm block for custom column_name" do
|
|
20
|
+
run_generator %w(user state)
|
|
21
|
+
assert_file "app/models/user.rb", /aasm :column => 'state' do\n end\n/
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "creates model with aasm block for namespaced model" do
|
|
25
|
+
run_generator %w(Admin::User state)
|
|
26
|
+
assert_file "app/models/admin/user.rb", /aasm :column => 'state' do\n end\n/
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
if defined?(NoBrainer::Document)
|
|
4
|
+
require 'generator_spec'
|
|
5
|
+
require 'generators/nobrainer/aasm_generator'
|
|
6
|
+
|
|
7
|
+
describe NoBrainer::Generators::AASMGenerator, type: :generator do
|
|
8
|
+
destination File.expand_path('../../../tmp', __FILE__)
|
|
9
|
+
|
|
10
|
+
before(:all) do
|
|
11
|
+
prepare_destination
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'creates model with aasm block for default column_name' do
|
|
15
|
+
run_generator %w[user]
|
|
16
|
+
assert_file 'app/models/user.rb', /include AASM\n\n aasm do\n end\n/
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'creates model with aasm block for custom column_name' do
|
|
20
|
+
run_generator %w[user state]
|
|
21
|
+
assert_file 'app/models/user.rb', /aasm :column => 'state' do\n end\n/
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'creates model with aasm block for namespaced model' do
|
|
25
|
+
run_generator %w[Admin::User state]
|
|
26
|
+
assert_file 'app/models/admin/user.rb', /aasm :column => 'state' do\n end\n/
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|