aasm 4.11.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.
Files changed (194) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  4. data/.travis.yml +56 -23
  5. data/Appraisals +67 -0
  6. data/CHANGELOG.md +112 -0
  7. data/CONTRIBUTING.md +24 -0
  8. data/Dockerfile +44 -0
  9. data/Gemfile +3 -21
  10. data/Gemfile.lock_old +151 -0
  11. data/LICENSE +1 -1
  12. data/README.md +540 -139
  13. data/Rakefile +6 -1
  14. data/TESTING.md +25 -0
  15. data/aasm.gemspec +5 -0
  16. data/docker-compose.yml +40 -0
  17. data/gemfiles/norails.gemfile +10 -0
  18. data/gemfiles/rails_4.2.gemfile +13 -11
  19. data/gemfiles/rails_4.2_mongoid_5.gemfile +8 -11
  20. data/gemfiles/rails_4.2_nobrainer.gemfile +9 -0
  21. data/gemfiles/rails_5.0.gemfile +11 -18
  22. data/gemfiles/rails_5.0_nobrainer.gemfile +9 -0
  23. data/gemfiles/rails_5.1.gemfile +14 -0
  24. data/gemfiles/rails_5.2.gemfile +14 -0
  25. data/lib/aasm/aasm.rb +40 -29
  26. data/lib/aasm/base.rb +61 -11
  27. data/lib/aasm/configuration.rb +10 -0
  28. data/lib/aasm/core/event.rb +45 -37
  29. data/lib/aasm/core/invoker.rb +129 -0
  30. data/lib/aasm/core/invokers/base_invoker.rb +75 -0
  31. data/lib/aasm/core/invokers/class_invoker.rb +52 -0
  32. data/lib/aasm/core/invokers/literal_invoker.rb +47 -0
  33. data/lib/aasm/core/invokers/proc_invoker.rb +59 -0
  34. data/lib/aasm/core/state.rb +22 -13
  35. data/lib/aasm/core/transition.rb +17 -69
  36. data/lib/aasm/dsl_helper.rb +24 -22
  37. data/lib/aasm/errors.rb +4 -6
  38. data/lib/aasm/instance_base.rb +22 -4
  39. data/lib/aasm/localizer.rb +13 -3
  40. data/lib/aasm/minitest/allow_event.rb +13 -0
  41. data/lib/aasm/minitest/allow_transition_to.rb +13 -0
  42. data/lib/aasm/minitest/have_state.rb +13 -0
  43. data/lib/aasm/minitest/transition_from.rb +21 -0
  44. data/lib/aasm/minitest.rb +5 -0
  45. data/lib/aasm/minitest_spec.rb +15 -0
  46. data/lib/aasm/persistence/active_record_persistence.rb +49 -105
  47. data/lib/aasm/persistence/base.rb +20 -5
  48. data/lib/aasm/persistence/core_data_query_persistence.rb +2 -1
  49. data/lib/aasm/persistence/dynamoid_persistence.rb +1 -1
  50. data/lib/aasm/persistence/mongoid_persistence.rb +26 -32
  51. data/lib/aasm/persistence/no_brainer_persistence.rb +105 -0
  52. data/lib/aasm/persistence/orm.rb +154 -0
  53. data/lib/aasm/persistence/plain_persistence.rb +2 -1
  54. data/lib/aasm/persistence/redis_persistence.rb +16 -11
  55. data/lib/aasm/persistence/sequel_persistence.rb +36 -64
  56. data/lib/aasm/persistence.rb +3 -3
  57. data/lib/aasm/rspec/allow_event.rb +5 -1
  58. data/lib/aasm/rspec/allow_transition_to.rb +5 -1
  59. data/lib/aasm/rspec/transition_from.rb +5 -1
  60. data/lib/aasm/state_machine.rb +4 -2
  61. data/lib/aasm/state_machine_store.rb +5 -2
  62. data/lib/aasm/version.rb +1 -1
  63. data/lib/aasm.rb +5 -2
  64. data/lib/generators/aasm/orm_helpers.rb +6 -0
  65. data/lib/generators/active_record/aasm_generator.rb +3 -1
  66. data/lib/generators/active_record/templates/migration.rb +1 -1
  67. data/lib/generators/active_record/templates/migration_existing.rb +1 -1
  68. data/lib/generators/nobrainer/aasm_generator.rb +28 -0
  69. data/lib/motion-aasm.rb +3 -1
  70. data/spec/database.rb +20 -7
  71. data/spec/en.yml +0 -3
  72. data/spec/generators/active_record_generator_spec.rb +49 -40
  73. data/spec/generators/mongoid_generator_spec.rb +4 -6
  74. data/spec/generators/no_brainer_generator_spec.rb +29 -0
  75. data/spec/{en_deprecated_style.yml → localizer_test_model_deprecated_style.yml} +6 -3
  76. data/spec/localizer_test_model_new_style.yml +11 -0
  77. data/spec/models/active_record/active_record_callback.rb +93 -0
  78. data/spec/models/active_record/complex_active_record_example.rb +5 -1
  79. data/spec/models/active_record/instance_level_skip_validation_example.rb +19 -0
  80. data/spec/models/{invalid_persistor.rb → active_record/invalid_persistor.rb} +0 -2
  81. data/spec/models/active_record/localizer_test_model.rb +11 -3
  82. data/spec/models/active_record/namespaced.rb +16 -0
  83. data/spec/models/active_record/person.rb +23 -0
  84. data/spec/models/{silent_persistor.rb → active_record/silent_persistor.rb} +0 -2
  85. data/spec/models/active_record/simple_new_dsl.rb +15 -0
  86. data/spec/models/active_record/timestamp_example.rb +16 -0
  87. data/spec/models/{transactor.rb → active_record/transactor.rb} +25 -2
  88. data/spec/models/{validator.rb → active_record/validator.rb} +0 -2
  89. data/spec/models/active_record/work.rb +3 -0
  90. data/spec/models/{worker.rb → active_record/worker.rb} +0 -0
  91. data/spec/models/callbacks/basic.rb +5 -2
  92. data/spec/models/callbacks/with_state_arg.rb +5 -1
  93. data/spec/models/callbacks/with_state_arg_multiple.rb +4 -1
  94. data/spec/models/default_state.rb +1 -1
  95. data/spec/models/guard_arguments_check.rb +17 -0
  96. data/spec/models/guard_with_params.rb +1 -1
  97. data/spec/models/guardian_without_from_specified.rb +18 -0
  98. data/spec/models/mongoid/invalid_persistor_mongoid.rb +39 -0
  99. data/spec/models/mongoid/silent_persistor_mongoid.rb +39 -0
  100. data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
  101. data/spec/models/mongoid/validator_mongoid.rb +100 -0
  102. data/spec/models/multiple_transitions_that_differ_only_by_guard.rb +31 -0
  103. data/spec/models/namespaced_multiple_example.rb +14 -0
  104. data/spec/models/nobrainer/complex_no_brainer_example.rb +36 -0
  105. data/spec/models/nobrainer/invalid_persistor_no_brainer.rb +39 -0
  106. data/spec/models/nobrainer/no_scope_no_brainer.rb +21 -0
  107. data/spec/models/nobrainer/nobrainer_relationships.rb +25 -0
  108. data/spec/models/nobrainer/silent_persistor_no_brainer.rb +39 -0
  109. data/spec/models/nobrainer/simple_new_dsl_nobrainer.rb +25 -0
  110. data/spec/models/{mongo_mapper/simple_mongo_mapper.rb → nobrainer/simple_no_brainer.rb} +8 -8
  111. data/spec/models/nobrainer/validator_no_brainer.rb +98 -0
  112. data/spec/models/parametrised_event.rb +7 -0
  113. data/spec/models/{mongo_mapper/complex_mongo_mapper_example.rb → redis/complex_redis_example.rb} +8 -5
  114. data/spec/models/redis/redis_multiple.rb +20 -0
  115. data/spec/models/redis/redis_simple.rb +20 -0
  116. data/spec/models/sequel/complex_sequel_example.rb +4 -3
  117. data/spec/models/sequel/invalid_persistor.rb +52 -0
  118. data/spec/models/sequel/sequel_multiple.rb +13 -13
  119. data/spec/models/sequel/sequel_simple.rb +13 -12
  120. data/spec/models/sequel/silent_persistor.rb +50 -0
  121. data/spec/models/sequel/transactor.rb +112 -0
  122. data/spec/models/sequel/validator.rb +93 -0
  123. data/spec/models/sequel/worker.rb +12 -0
  124. data/spec/models/simple_example.rb +8 -0
  125. data/spec/models/simple_example_with_guard_args.rb +17 -0
  126. data/spec/models/simple_multiple_example.rb +12 -0
  127. data/spec/models/sub_class.rb +34 -0
  128. data/spec/models/timestamps_example.rb +19 -0
  129. data/spec/models/timestamps_with_named_machine_example.rb +13 -0
  130. data/spec/spec_helper.rb +15 -33
  131. data/spec/spec_helpers/active_record.rb +8 -0
  132. data/spec/spec_helpers/dynamoid.rb +35 -0
  133. data/spec/spec_helpers/mongoid.rb +26 -0
  134. data/spec/spec_helpers/nobrainer.rb +15 -0
  135. data/spec/spec_helpers/redis.rb +18 -0
  136. data/spec/spec_helpers/remove_warnings.rb +1 -0
  137. data/spec/spec_helpers/sequel.rb +7 -0
  138. data/spec/unit/abstract_class_spec.rb +27 -0
  139. data/spec/unit/api_spec.rb +79 -72
  140. data/spec/unit/callback_multiple_spec.rb +7 -3
  141. data/spec/unit/callbacks_spec.rb +37 -2
  142. data/spec/unit/complex_example_spec.rb +12 -3
  143. data/spec/unit/complex_multiple_example_spec.rb +20 -4
  144. data/spec/unit/event_multiple_spec.rb +1 -1
  145. data/spec/unit/event_spec.rb +29 -4
  146. data/spec/unit/exception_spec.rb +1 -1
  147. data/spec/unit/guard_arguments_check_spec.rb +9 -0
  148. data/spec/unit/guard_spec.rb +17 -0
  149. data/spec/unit/guard_with_params_spec.rb +4 -0
  150. data/spec/unit/guard_without_from_specified_spec.rb +10 -0
  151. data/spec/unit/inspection_multiple_spec.rb +9 -5
  152. data/spec/unit/inspection_spec.rb +7 -3
  153. data/spec/unit/invoker_spec.rb +189 -0
  154. data/spec/unit/invokers/base_invoker_spec.rb +72 -0
  155. data/spec/unit/invokers/class_invoker_spec.rb +95 -0
  156. data/spec/unit/invokers/literal_invoker_spec.rb +86 -0
  157. data/spec/unit/invokers/proc_invoker_spec.rb +86 -0
  158. data/spec/unit/localizer_spec.rb +85 -52
  159. data/spec/unit/multiple_transitions_that_differ_only_by_guard_spec.rb +14 -0
  160. data/spec/unit/namespaced_multiple_example_spec.rb +22 -0
  161. data/spec/unit/override_warning_spec.rb +8 -0
  162. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +468 -447
  163. data/spec/unit/persistence/active_record_persistence_spec.rb +639 -486
  164. data/spec/unit/persistence/dynamoid_persistence_multiple_spec.rb +4 -9
  165. data/spec/unit/persistence/dynamoid_persistence_spec.rb +4 -9
  166. data/spec/unit/persistence/mongoid_persistence_multiple_spec.rb +83 -13
  167. data/spec/unit/persistence/mongoid_persistence_spec.rb +97 -13
  168. data/spec/unit/persistence/no_brainer_persistence_multiple_spec.rb +198 -0
  169. data/spec/unit/persistence/no_brainer_persistence_spec.rb +158 -0
  170. data/spec/unit/persistence/redis_persistence_multiple_spec.rb +88 -0
  171. data/spec/unit/persistence/redis_persistence_spec.rb +8 -32
  172. data/spec/unit/persistence/sequel_persistence_multiple_spec.rb +6 -11
  173. data/spec/unit/persistence/sequel_persistence_spec.rb +278 -10
  174. data/spec/unit/rspec_matcher_spec.rb +9 -0
  175. data/spec/unit/simple_example_spec.rb +15 -0
  176. data/spec/unit/simple_multiple_example_spec.rb +28 -0
  177. data/spec/unit/state_spec.rb +23 -7
  178. data/spec/unit/subclassing_multiple_spec.rb +37 -2
  179. data/spec/unit/subclassing_spec.rb +17 -2
  180. data/spec/unit/timestamps_spec.rb +32 -0
  181. data/spec/unit/transition_spec.rb +1 -1
  182. data/test/minitest_helper.rb +57 -0
  183. data/test/unit/minitest_matcher_test.rb +80 -0
  184. metadata +213 -37
  185. data/callbacks.txt +0 -51
  186. data/gemfiles/rails_3.2_stable.gemfile +0 -15
  187. data/gemfiles/rails_4.0.gemfile +0 -16
  188. data/gemfiles/rails_4.0_mongo_mapper.gemfile +0 -16
  189. data/gemfiles/rails_4.2_mongo_mapper.gemfile +0 -17
  190. data/lib/aasm/persistence/mongo_mapper_persistence.rb +0 -163
  191. data/spec/models/mongo_mapper/no_scope_mongo_mapper.rb +0 -21
  192. data/spec/models/mongo_mapper/simple_new_dsl_mongo_mapper.rb +0 -25
  193. data/spec/unit/persistence/mongo_mapper_persistence_multiple_spec.rb +0 -149
  194. data/spec/unit/persistence/mongo_mapper_persistence_spec.rb +0 -96
data/Rakefile CHANGED
@@ -23,4 +23,9 @@ Rake::RDocTask.new do |rdoc|
23
23
  rdoc.rdoc_files.include('lib/**/*.rb')
24
24
  end
25
25
 
26
- task :default => :spec
26
+ if ENV["APPRAISAL_INITIALIZED"] || ENV["TRAVIS"]
27
+ task :default => :spec
28
+ else
29
+ require 'appraisal'
30
+ task :default => :appraisal
31
+ end
data/TESTING.md ADDED
@@ -0,0 +1,25 @@
1
+ ## Install dependency matrix
2
+
3
+ appraisal install
4
+
5
+ This will re-generate Gemfiles in `gemfile` folder
6
+
7
+ Use rvm gemsets or similar to avoid global gem pollution
8
+
9
+ ## Run specs
10
+
11
+ For all supported Rails/ORM combinations:
12
+
13
+ appraisal rspec
14
+
15
+ Or for specific one:
16
+
17
+ appraisal rails_4.2 rspec
18
+
19
+ Or for one particular test file
20
+
21
+ appraisal rails_4.2_mongoid_5 rspec spec/unit/persistence/mongoid_persistence_multiple_spec.rb
22
+
23
+ Or down to one test case
24
+
25
+ appraisal rails_4.2_mongoid_5 rspec spec/unit/persistence/mongoid_persistence_multiple_spec.rb:92
data/aasm.gemspec CHANGED
@@ -16,10 +16,15 @@ Gem::Specification.new do |s|
16
16
  s.platform = Gem::Platform::RUBY
17
17
  s.required_ruby_version = '>= 1.9.3'
18
18
 
19
+ s.add_dependency 'concurrent-ruby', '~> 1.0'
20
+
19
21
  s.add_development_dependency 'rake'
20
22
  s.add_development_dependency 'sdoc'
21
23
  s.add_development_dependency 'rspec', ">= 3"
22
24
  s.add_development_dependency 'generator_spec'
25
+ s.add_development_dependency 'appraisal'
26
+ s.add_development_dependency "simplecov"
27
+ s.add_development_dependency "codecov", ">= 0.1.21"
23
28
 
24
29
  # debugging
25
30
  # s.add_development_dependency 'debugger'
@@ -0,0 +1,40 @@
1
+ version: "2"
2
+
3
+ services:
4
+ aasm:
5
+ image: aasm/aasm
6
+ build: .
7
+ command: bash -c 'bundle exec appraisal install && bundle exec appraisal rspec'
8
+ environment:
9
+ - DYNAMODB_HOST=dynamodb
10
+ - DYNAMODB_PORT=8000
11
+ - MONGODB_HOST=mongo
12
+ - MONGODB_PORT=27017
13
+ - RAILS_ENV=development
14
+ - REDIS_HOST=redis
15
+ - REDIS_PORT=6379
16
+ - RETHINKDB_DB=rethinkdb_test
17
+ - RETHINKDB_HOST=rethinkdb
18
+ - RETHINKDB_PORT=28015
19
+ depends_on:
20
+ - dynamodb
21
+ - mongo
22
+ - redis
23
+ - rethinkdb
24
+ volumes:
25
+ - .:/application
26
+ volumes_from:
27
+ - bundle
28
+ bundle:
29
+ image: aasm/aasm
30
+ command: echo Bundler data container
31
+ volumes:
32
+ - /bundle
33
+ dynamodb:
34
+ image: cnadiminti/dynamodb-local:2017-02-16
35
+ mongo:
36
+ image: mongo:3.6.1
37
+ redis:
38
+ image: redis:4.0.6-alpine
39
+ rethinkdb:
40
+ image: rethinkdb:2.3.6
@@ -0,0 +1,10 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "~> 1.3", ">= 1.3.5", platforms: :ruby
6
+ gem "rails", install_if: false
7
+ gem "sequel"
8
+ gem "redis-objects"
9
+
10
+ gemspec path: "../"
@@ -1,15 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
- gem "sqlite3", :platforms => :ruby
4
- gem 'rubysl', :platforms => :rbx
5
- gem 'rubinius-developer_tools', :platforms => :rbx
6
- gem "jruby-openssl", :platforms => :jruby
7
- gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
8
- gem "mime-types", "~> 2" if Gem::Version.create(RUBY_VERSION.dup) <= Gem::Version.create('1.9.3')
5
+ gem "sqlite3", "~> 1.3.5", platforms: :ruby
9
6
  gem "rails", "4.2.5"
10
- gem 'mongoid', '~>4.0' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.create('1.9.3')
11
- gem 'sequel'
12
- gem 'dynamoid', '~> 1', :platforms => :ruby
13
- gem 'aws-sdk', '~>2', :platforms => :ruby
7
+ gem "nokogiri", "1.6.8.1", platforms: [:ruby_19]
8
+ gem "mime-types", "~> 2", platforms: [:ruby_19, :jruby]
9
+ gem "mongoid", "~> 4.0"
10
+ gem "sequel"
11
+ gem "dynamoid", "~> 1", platforms: :ruby
12
+ gem "aws-sdk", "~> 2", platforms: :ruby
13
+ gem "redis-objects"
14
+ gem "activerecord-jdbcsqlite3-adapter", "1.3.24", platforms: :jruby
15
+ gem "after_commit_everywhere", "~> 1.0"
14
16
 
15
- gemspec :path => "../"
17
+ gemspec path: "../"
@@ -1,15 +1,12 @@
1
+ # This file was generated by Appraisal
2
+
1
3
  source "https://rubygems.org"
2
4
 
3
- gem "sqlite3", :platforms => :ruby
4
- gem 'rubysl', :platforms => :rbx
5
- gem 'rubinius-developer_tools', :platforms => :rbx
6
- gem "jruby-openssl", :platforms => :jruby
7
- gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
8
- gem "mime-types", "~> 2" if Gem::Version.create(RUBY_VERSION.dup) <= Gem::Version.create('1.9.3')
5
+ gem "sqlite3", "~> 1.3.5", platforms: :ruby
9
6
  gem "rails", "4.2.5"
10
- gem 'mongoid', '~>5.0' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.create('1.9.3')
11
- gem 'sequel'
12
- gem 'dynamoid', '~> 1', :platforms => :ruby
13
- gem 'aws-sdk', '~>2', :platforms => :ruby
7
+ gem "mime-types", "~> 2", platforms: [:ruby_19, :jruby]
8
+ gem "mongoid", "~> 5.0"
9
+ gem "activerecord-jdbcsqlite3-adapter", "1.3.24", platforms: :jruby
10
+ gem "after_commit_everywhere", "~> 1.0"
14
11
 
15
- gemspec :path => "../"
12
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "~> 1.3.5", platforms: :ruby
6
+ gem "rails", "4.2.5"
7
+ gem "nobrainer", "~> 0.33.0"
8
+
9
+ gemspec path: "../"
@@ -1,21 +1,14 @@
1
- source "https://rubygems.org"
2
-
3
- gem "sqlite3", :platforms => :ruby
4
- gem 'rubysl', :platforms => :rbx
5
- gem 'rubinius-developer_tools', :platforms => :rbx
6
- gem "jruby-openssl", :platforms => :jruby
7
- gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
8
-
9
- gem "rails", "5.0.0.beta4"
1
+ # This file was generated by Appraisal
10
2
 
11
- # mongoid is not yet Rails 5 compatible
12
- # gem 'mongoid', '~>4.0' if Gem::Version.create(RUBY_VERSION.dup) >= Gem::Version.create('1.9.3')
13
-
14
- gem 'sequel'
15
-
16
- # dynamoid is not yet Rails 5 compatible
17
- # gem 'dynamoid', '~> 1', :platforms => :ruby
3
+ source "https://rubygems.org"
18
4
 
19
- gem 'aws-sdk', '~>2', :platforms => :ruby
5
+ gem "sqlite3", "~> 1.3.5", platforms: :ruby
6
+ gem "rails", "5.0.0"
7
+ gem "mongoid", "~> 6.0"
8
+ gem "sequel"
9
+ gem "dynamoid", "~> 1.3", platforms: :ruby
10
+ gem "aws-sdk", "~> 2", platforms: :ruby
11
+ gem "redis-objects"
12
+ gem "after_commit_everywhere", "~> 1.0"
20
13
 
21
- gemspec :path => "../"
14
+ gemspec path: "../"
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "~> 1.3.5", platforms: :ruby
6
+ gem "rails", "5.0.0"
7
+ gem "nobrainer", "~> 0.33.0"
8
+
9
+ gemspec path: "../"
@@ -0,0 +1,14 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "~> 1.3.5", platforms: :ruby
6
+ gem "rails", "5.1"
7
+ gem "mongoid", "~>6.0"
8
+ gem "sequel"
9
+ gem "dynamoid", "~> 1.3", platforms: :ruby
10
+ gem "aws-sdk", "~>2", platforms: :ruby
11
+ gem "redis-objects"
12
+ gem "after_commit_everywhere", "~> 1.0"
13
+
14
+ gemspec path: "../"
@@ -0,0 +1,14 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "~> 1.3.5", platforms: :ruby
6
+ gem "rails", "5.2"
7
+ gem "mongoid", "~>6.0"
8
+ gem "sequel"
9
+ gem "dynamoid", "~>2.2", platforms: :ruby
10
+ gem "aws-sdk", "~>2", platforms: :ruby
11
+ gem "redis-objects"
12
+ gem "after_commit_everywhere", "~> 1.0"
13
+
14
+ gemspec path: "../"
data/lib/aasm/aasm.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  module AASM
2
+ # this is used internally as an argument default value to represent no value
3
+ NO_VALUE = :_aasm_no_value
2
4
 
3
5
  # provide a state machine for the including class
4
6
  # make sure to load class methods as well
@@ -42,7 +44,7 @@ module AASM
42
44
 
43
45
  raise ArgumentError, "The class #{aasm_klass} must inherit from AASM::Base!" unless aasm_klass.ancestors.include?(AASM::Base)
44
46
 
45
- @aasm ||= {}
47
+ @aasm ||= Concurrent::Map.new
46
48
  if @aasm[state_machine_name]
47
49
  # make sure to use provided options
48
50
  options.each do |key, value|
@@ -67,12 +69,12 @@ module AASM
67
69
  unless AASM::StateMachineStore.fetch(self.class, true).machine(name)
68
70
  raise AASM::UnknownStateMachineError.new("There is no state machine with the name '#{name}' defined in #{self.class.name}!")
69
71
  end
70
- @aasm ||= {}
72
+ @aasm ||= Concurrent::Map.new
71
73
  @aasm[name.to_sym] ||= AASM::InstanceBase.new(self, name.to_sym)
72
74
  end
73
75
 
74
76
  def initialize_dup(other)
75
- @aasm = {}
77
+ @aasm = Concurrent::Map.new
76
78
  super
77
79
  end
78
80
 
@@ -97,25 +99,10 @@ private
97
99
  begin
98
100
  old_state = aasm(state_machine_name).state_object_for_name(aasm(state_machine_name).current_state)
99
101
 
100
- event.fire_global_callbacks(
101
- :before_all_events,
102
- self,
103
- *process_args(event, aasm(state_machine_name).current_state, *args)
104
- )
105
-
106
- # new event before callback
107
- event.fire_callbacks(
108
- :before,
109
- self,
110
- *process_args(event, aasm(state_machine_name).current_state, *args)
111
- )
102
+ fire_default_callbacks(event, *process_args(event, aasm(state_machine_name).current_state, *args))
112
103
 
113
104
  if may_fire_to = event.may_fire?(self, *args)
114
- old_state.fire_callbacks(:before_exit, self,
115
- *process_args(event, aasm(state_machine_name).current_state, *args))
116
- old_state.fire_callbacks(:exit, self,
117
- *process_args(event, aasm(state_machine_name).current_state, *args))
118
-
105
+ fire_exit_callbacks(old_state, *process_args(event, aasm(state_machine_name).current_state, *args))
119
106
  if new_state_name = event.fire(self, {:may_fire => may_fire_to}, *args)
120
107
  aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args, &block)
121
108
  else
@@ -128,41 +115,65 @@ private
128
115
  event.fire_callbacks(:error, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
129
116
  event.fire_global_callbacks(:error_on_all_events, self, e, *process_args(event, aasm(state_machine_name).current_state, *args)) ||
130
117
  raise(e)
118
+ false
131
119
  ensure
132
120
  event.fire_callbacks(:ensure, self, *process_args(event, aasm(state_machine_name).current_state, *args))
133
121
  event.fire_global_callbacks(:ensure_on_all_events, self, *process_args(event, aasm(state_machine_name).current_state, *args))
134
122
  end
135
123
  end
136
124
 
125
+ def fire_default_callbacks(event, *processed_args)
126
+ event.fire_global_callbacks(
127
+ :before_all_events,
128
+ self,
129
+ *processed_args
130
+ )
131
+
132
+ # new event before callback
133
+ event.fire_callbacks(
134
+ :before,
135
+ self,
136
+ *processed_args
137
+ )
138
+ end
139
+
140
+ def fire_exit_callbacks(old_state, *processed_args)
141
+ old_state.fire_callbacks(:before_exit, self, *processed_args)
142
+ old_state.fire_callbacks(:exit, self, *processed_args)
143
+ end
144
+
137
145
  def aasm_fired(state_machine_name, event, old_state, new_state_name, options, *args)
138
146
  persist = options[:persist]
139
147
 
140
148
  new_state = aasm(state_machine_name).state_object_for_name(new_state_name)
149
+ callback_args = process_args(event, aasm(state_machine_name).current_state, *args)
141
150
 
142
- new_state.fire_callbacks(:before_enter, self,
143
- *process_args(event, aasm(state_machine_name).current_state, *args))
151
+ new_state.fire_callbacks(:before_enter, self, *callback_args)
144
152
 
145
- new_state.fire_callbacks(:enter, self,
146
- *process_args(event, aasm(state_machine_name).current_state, *args)) # TODO: remove for AASM 4?
153
+ new_state.fire_callbacks(:enter, self, *callback_args) # TODO: remove for AASM 4?
147
154
 
148
155
  persist_successful = true
149
156
  if persist
150
157
  persist_successful = aasm(state_machine_name).set_current_state_with_persistence(new_state_name)
151
158
  if persist_successful
152
159
  yield if block_given?
160
+ event.fire_callbacks(:before_success, self, *callback_args)
153
161
  event.fire_transition_callbacks(self, *process_args(event, old_state.name, *args))
154
- event.fire_callbacks(:success, self)
162
+ event.fire_callbacks(:success, self, *callback_args)
155
163
  end
156
164
  else
157
165
  aasm(state_machine_name).current_state = new_state_name
158
166
  yield if block_given?
159
167
  end
160
168
 
169
+ binding_event = event.options[:binding_event]
170
+ if binding_event
171
+ __send__("#{binding_event}#{'!' if persist}")
172
+ end
173
+
161
174
  if persist_successful
162
- old_state.fire_callbacks(:after_exit, self,
163
- *process_args(event, aasm(state_machine_name).current_state, *args))
164
- new_state.fire_callbacks(:after_enter, self,
165
- *process_args(event, aasm(state_machine_name).current_state, *args))
175
+ old_state.fire_callbacks(:after_exit, self, *callback_args)
176
+ new_state.fire_callbacks(:after_enter, self, *callback_args)
166
177
  event.fire_callbacks(
167
178
  :after,
168
179
  self,
data/lib/aasm/base.rb CHANGED
@@ -26,6 +26,9 @@ module AASM
26
26
  # raise if the model is invalid (in ActiveRecord)
27
27
  configure :whiny_persistence, false
28
28
 
29
+ # Use transactions (in ActiveRecord)
30
+ configure :use_transactions, true
31
+
29
32
  # use requires_new for nested transactions (in ActiveRecord)
30
33
  configure :requires_new_transaction, true
31
34
 
@@ -34,6 +37,9 @@ module AASM
34
37
  # string for a specific lock type i.e. FOR UPDATE NOWAIT
35
38
  configure :requires_lock, false
36
39
 
40
+ # automatically set `"#{state_name}_at" = ::Time.now` on state changes
41
+ configure :timestamps, false
42
+
37
43
  # set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
38
44
  configure :no_direct_assignment, false
39
45
 
@@ -48,18 +54,12 @@ module AASM
48
54
  # Configure a logger, with default being a Logger to STDERR
49
55
  configure :logger, Logger.new(STDERR)
50
56
 
57
+ # setup timestamp-setting callback if enabled
58
+ setup_timestamps(@name)
59
+
51
60
  # make sure to raise an error if no_direct_assignment is enabled
52
61
  # and attribute is directly assigned though
53
- aasm_name = @name
54
- klass.send :define_method, "#{@state_machine.config.column}=", ->(state_name) do
55
- if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
56
- raise AASM::NoDirectAssignmentError.new(
57
- 'direct assignment of AASM column has been disabled (see AASM configuration for this class)'
58
- )
59
- else
60
- super(state_name)
61
- end
62
- end
62
+ setup_no_direct_assignment(@name)
63
63
  end
64
64
 
65
65
  # This method is both a getter and a setter
@@ -130,6 +130,16 @@ module AASM
130
130
  aasm(aasm_name).current_event = event
131
131
  aasm_fire_event(aasm_name, event, {:persist => false}, *args, &block)
132
132
  end
133
+
134
+ skip_instance_level_validation(event, name, aasm_name, klass)
135
+
136
+ # Create aliases for the event methods. Keep the old names to maintain backwards compatibility.
137
+ if namespace?
138
+ klass.send(:alias_method, "may_#{name}_#{namespace}?", "may_#{name}?")
139
+ klass.send(:alias_method, "#{name}_#{namespace}!", "#{name}!")
140
+ klass.send(:alias_method, "#{name}_#{namespace}", name)
141
+ end
142
+
133
143
  end
134
144
 
135
145
  def after_all_transitions(*callbacks, &block)
@@ -208,7 +218,9 @@ module AASM
208
218
  klass.defined_enums.values.any?{ |methods|
209
219
  methods.keys{| enum | enum + '?' == method_name }
210
220
  })
211
- @state_machine.config.logger.warn "#{klass.name}: overriding method '#{method_name}'!"
221
+ unless AASM::Configuration.hide_warnings
222
+ @state_machine.config.logger.warn "#{klass.name}: overriding method '#{method_name}'!"
223
+ end
212
224
  end
213
225
 
214
226
  klass.send(:define_method, method_name, method_definition)
@@ -236,5 +248,43 @@ module AASM
236
248
  end
237
249
  end
238
250
 
251
+ def skip_instance_level_validation(event, name, aasm_name, klass)
252
+ # Overrides the skip_validation config for an instance (If skip validation is set to false in original config) and
253
+ # restores it back to the original value after the event is fired.
254
+ safely_define_method klass, "#{name}_without_validation!", ->(*args, &block) do
255
+ original_config = AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save
256
+ begin
257
+ AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save = true unless original_config
258
+ aasm(aasm_name).current_event = :"#{name}!"
259
+ aasm_fire_event(aasm_name, event, {:persist => true}, *args, &block)
260
+ ensure
261
+ AASM::StateMachineStore.fetch(self.class, true).machine(aasm_name).config.skip_validation_on_save = original_config
262
+ end
263
+ end
264
+ end
265
+
266
+ def setup_timestamps(aasm_name)
267
+ return unless @state_machine.config.timestamps
268
+
269
+ after_all_transitions do
270
+ if self.class.aasm(:"#{aasm_name}").state_machine.config.timestamps
271
+ ts_setter = "#{aasm(aasm_name).to_state}_at="
272
+ respond_to?(ts_setter) && send(ts_setter, ::Time.now)
273
+ end
274
+ end
275
+ end
276
+
277
+ def setup_no_direct_assignment(aasm_name)
278
+ return unless @state_machine.config.no_direct_assignment
279
+
280
+ @klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
281
+ if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
282
+ raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
283
+ else
284
+ super(state_name)
285
+ end
286
+ end
287
+ end
288
+
239
289
  end
240
290
  end
@@ -15,12 +15,18 @@ module AASM
15
15
  # for ActiveRecord: store the new state even if the model is invalid and return true
16
16
  attr_accessor :skip_validation_on_save
17
17
 
18
+ # for ActiveRecord: use transactions
19
+ attr_accessor :use_transactions
20
+
18
21
  # for ActiveRecord: use requires_new for nested transactions?
19
22
  attr_accessor :requires_new_transaction
20
23
 
21
24
  # for ActiveRecord: use pessimistic locking
22
25
  attr_accessor :requires_lock
23
26
 
27
+ # automatically set `"#{state_name}_at" = ::Time.now` on state changes
28
+ attr_accessor :timestamps
29
+
24
30
  # forbid direct assignment in aasm_state column (in ActiveRecord)
25
31
  attr_accessor :no_direct_assignment
26
32
 
@@ -34,5 +40,9 @@ module AASM
34
40
 
35
41
  # Configure a logger, with default being a Logger to STDERR
36
42
  attr_accessor :logger
43
+
44
+ class << self
45
+ attr_accessor :hide_warnings
46
+ end
37
47
  end
38
48
  end
@@ -1,8 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module AASM::Core
2
4
  class Event
3
- include DslHelper
5
+ include AASM::DslHelper
4
6
 
5
- attr_reader :name, :state_machine, :options
7
+ attr_reader :name, :state_machine, :options, :default_display_name
6
8
 
7
9
  def initialize(name, state_machine, options = {}, &block)
8
10
  @name = name
@@ -11,6 +13,7 @@ module AASM::Core
11
13
  @valid_transitions = {}
12
14
  @guards = Array(options[:guard] || options[:guards] || options[:if])
13
15
  @unless = Array(options[:unless]) #TODO: This could use a better name
16
+ @default_display_name = name.to_s.gsub(/_/, ' ').capitalize
14
17
 
15
18
  # from aasm4
16
19
  @options = options # QUESTION: .dup ?
@@ -22,18 +25,29 @@ module AASM::Core
22
25
  :before_transaction,
23
26
  :ensure,
24
27
  :error,
28
+ :before_success,
25
29
  :success,
26
30
  ], &block) if block
27
31
  end
28
32
 
33
+ # called internally by Ruby 1.9 after clone()
34
+ def initialize_copy(orig)
35
+ super
36
+ @transitions = @transitions.collect { |transition| transition.clone }
37
+ @guards = @guards.dup
38
+ @unless = @unless.dup
39
+ @options = {}
40
+ orig.options.each_pair { |name, setting| @options[name] = setting.is_a?(Hash) || setting.is_a?(Array) ? setting.dup : setting }
41
+ end
42
+
29
43
  # a neutered version of fire - it doesn't actually fire the event, it just
30
44
  # executes the transition guards to determine if a transition is even
31
45
  # an option given current conditions.
32
- def may_fire?(obj, to_state=nil, *args)
46
+ def may_fire?(obj, to_state=::AASM::NO_VALUE, *args)
33
47
  _fire(obj, {:test_only => true}, to_state, *args) # true indicates test firing
34
48
  end
35
49
 
36
- def fire(obj, options={}, to_state=nil, *args)
50
+ def fire(obj, options={}, to_state=::AASM::NO_VALUE, *args)
37
51
  _fire(obj, options, to_state, *args) # false indicates this is not a test (fire!)
38
52
  end
39
53
 
@@ -85,7 +99,7 @@ module AASM::Core
85
99
  @transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions.merge(:from => s.to_sym)), &block)
86
100
  end
87
101
  # Create a transition if :to is specified without :from (transitions from ANY state)
88
- if @transitions.empty? && definitions[:to]
102
+ if !definitions[:from] && definitions[:to]
89
103
  @transitions << AASM::Core::Transition.new(self, attach_event_guards(definitions), &block)
90
104
  end
91
105
  end
@@ -96,6 +110,10 @@ module AASM::Core
96
110
  transitions.flat_map(&:failures)
97
111
  end
98
112
 
113
+ def to_s
114
+ name.to_s
115
+ end
116
+
99
117
  private
100
118
 
101
119
  def attach_event_guards(definitions)
@@ -110,28 +128,31 @@ module AASM::Core
110
128
  definitions
111
129
  end
112
130
 
113
- def _fire(obj, options={}, to_state=nil, *args)
131
+ def _fire(obj, options={}, to_state=::AASM::NO_VALUE, *args)
114
132
  result = options[:test_only] ? false : nil
133
+ clear_failed_callbacks
115
134
  transitions = @transitions.select { |t| t.from == obj.aasm(state_machine.name).current_state || t.from == nil}
116
135
  return result if transitions.size == 0
117
136
 
118
- # If to_state is not nil it either contains a potential
119
- # to_state or an arg
120
- unless to_state == nil
121
- if !to_state.respond_to?(:to_sym) || !transitions.map(&:to).flatten.include?(to_state.to_sym)
122
- args.unshift(to_state)
123
- to_state = nil
124
- end
137
+ if to_state == ::AASM::NO_VALUE
138
+ to_state = nil
139
+ elsif !(to_state.respond_to?(:to_sym) && transitions.map(&:to).flatten.include?(to_state.to_sym))
140
+ # to_state is an argument
141
+ args.unshift(to_state)
142
+ to_state = nil
125
143
  end
144
+
145
+ # nop, to_state is a valid to-state
126
146
 
127
147
  transitions.each do |transition|
128
148
  next if to_state and !Array(transition.to).include?(to_state)
129
- if (options.key?(:may_fire) && Array(transition.to).include?(options[:may_fire])) ||
149
+ if (options.key?(:may_fire) && transition.eql?(options[:may_fire])) ||
130
150
  (!options.key?(:may_fire) && transition.allowed?(obj, *args))
131
- result = to_state || Array(transition.to).first
151
+
132
152
  if options[:test_only]
133
- # result = true
153
+ result = transition
134
154
  else
155
+ result = to_state || Array(transition.to).first
135
156
  Array(transition.to).each {|to| @valid_transitions[to] = transition }
136
157
  transition.execute(obj, *args)
137
158
  end
@@ -142,28 +163,15 @@ module AASM::Core
142
163
  result
143
164
  end
144
165
 
145
- def invoke_callbacks(code, record, args)
146
- case code
147
- when Symbol, String
148
- unless record.respond_to?(code, true)
149
- raise NoMethodError.new("NoMethodError: undefined method `#{code}' for #{record.inspect}:#{record.class}")
150
- end
151
- arity = record.__send__(:method, code.to_sym).arity
152
- record.__send__(code, *(arity < 0 ? args : args[0...arity]))
153
- true
154
-
155
- when Proc
156
- arity = code.arity
157
- record.instance_exec(*(arity < 0 ? args : args[0...arity]), &code)
158
- true
159
-
160
- when Array
161
- code.each {|a| invoke_callbacks(a, record, args)}
162
- true
166
+ def clear_failed_callbacks
167
+ # https://github.com/aasm/aasm/issues/383, https://github.com/aasm/aasm/issues/599
168
+ transitions.each { |transition| transition.failures.clear }
169
+ end
163
170
 
164
- else
165
- false
166
- end
171
+ def invoke_callbacks(code, record, args)
172
+ Invoker.new(code, record, args)
173
+ .with_default_return_value(false)
174
+ .invoke
167
175
  end
168
176
  end
169
177
  end # AASM