aasm 4.11.1 → 5.2.0

Sign up to get free protection for your applications and to get access to all the features.
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