aasm 5.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +5 -5
  3. data/CHANGELOG.md +13 -2
  4. data/README.md +59 -6
  5. data/aasm.gemspec +1 -1
  6. data/gemfiles/rails_4.2.gemfile +1 -1
  7. data/gemfiles/rails_4.2_mongoid_5.gemfile +1 -1
  8. data/gemfiles/rails_5.0.gemfile +1 -1
  9. data/gemfiles/rails_5.1.gemfile +1 -1
  10. data/gemfiles/rails_5.2.gemfile +1 -1
  11. data/lib/aasm/base.rb +30 -11
  12. data/lib/aasm/configuration.rb +3 -0
  13. data/lib/aasm/core/event.rb +7 -2
  14. data/lib/aasm/core/state.rb +6 -5
  15. data/lib/aasm/core/transition.rb +1 -1
  16. data/lib/aasm/dsl_helper.rb +24 -22
  17. data/lib/aasm/localizer.rb +13 -3
  18. data/lib/aasm/persistence/active_record_persistence.rb +1 -1
  19. data/lib/aasm/persistence/base.rb +13 -2
  20. data/lib/aasm/version.rb +1 -1
  21. data/spec/database.rb +9 -11
  22. data/spec/localizer_test_model_deprecated_style.yml +7 -0
  23. data/spec/localizer_test_model_new_style.yml +6 -0
  24. data/spec/models/active_record/localizer_test_model.rb +8 -0
  25. data/spec/models/active_record/namespaced.rb +16 -0
  26. data/spec/models/active_record/timestamp_example.rb +16 -0
  27. data/spec/models/mongoid/timestamp_example_mongoid.rb +20 -0
  28. data/spec/models/timestamps_example.rb +19 -0
  29. data/spec/models/timestamps_with_named_machine_example.rb +13 -0
  30. data/spec/unit/localizer_spec.rb +40 -8
  31. data/spec/unit/persistence/active_record_persistence_multiple_spec.rb +17 -0
  32. data/spec/unit/persistence/active_record_persistence_spec.rb +12 -0
  33. data/spec/unit/persistence/mongoid_persistence_spec.rb +12 -0
  34. data/spec/unit/timestamps_spec.rb +32 -0
  35. metadata +16 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2f4099a66f8d753c2bd7c06b3c378dcd37b47acf9b2f7487784e7762742c1c4
4
- data.tar.gz: 8971b36f1b066c08d3a4d278a99d7c1e5386be72182da62b51a82db1e60e9e9f
3
+ metadata.gz: 6e3ec7032fd9c8368ddfb4bed4e931edc5f9d96c02a69f89f7ee26913f7d8698
4
+ data.tar.gz: ddb4ef39501440da1869426eda3b4359e37eeef67b464c9e0d660e9f353510f6
5
5
  SHA512:
6
- metadata.gz: c24e544b82cfc4616f3e3ff033b7a5ab68bff7bd834b115797e9127ca361effe241ffb1adaedcdb595711a6757f2e0a8e0a8d606a9bc812c28707a1ee845ef40
7
- data.tar.gz: 421636d1e59e52b8d6b4ddff7dd373fecd9a19ef67b90935e856b1ee980bae9a5a4a8526e62238de9ecc84a7783362e8d5b2005c53b9c7754368be79724c6255
6
+ metadata.gz: c294de071cad6569f855af1455c9d0281622ca5c9a3df0466baacaca569d8974718eb46d4c06e674f54629a77e2634d69829ee2910612ba146b4aaf786891544
7
+ data.tar.gz: a7b0c176cec262ad9d89538fe43e6df484f4d79328f85e853dcc9fe6b6ce9a9629f802e7de14659e5c91135a3a4df09a6066011983b1936eff06b17657c24cc5
data/Appraisals CHANGED
@@ -8,7 +8,7 @@ appraise 'rails_4.2' do
8
8
  gem 'aws-sdk', '~> 2', platforms: :ruby
9
9
  gem 'redis-objects'
10
10
  gem 'activerecord-jdbcsqlite3-adapter', '1.3.24', platforms: :jruby
11
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
11
+ gem "after_commit_everywhere", "~> 1.0"
12
12
  end
13
13
 
14
14
  appraise 'rails_4.2_nobrainer' do
@@ -21,7 +21,7 @@ appraise 'rails_4.2_mongoid_5' do
21
21
  gem 'rails', '4.2.5'
22
22
  gem 'mongoid', '~> 5.0'
23
23
  gem 'activerecord-jdbcsqlite3-adapter', '1.3.24', platforms: :jruby
24
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
24
+ gem "after_commit_everywhere", "~> 1.0"
25
25
  end
26
26
 
27
27
  appraise 'rails_5.0' do
@@ -31,7 +31,7 @@ appraise 'rails_5.0' do
31
31
  gem 'dynamoid', '~> 1.3', platforms: :ruby
32
32
  gem 'aws-sdk', '~> 2', platforms: :ruby
33
33
  gem 'redis-objects'
34
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
34
+ gem "after_commit_everywhere", "~> 1.0"
35
35
  end
36
36
 
37
37
  appraise 'rails_5.0_nobrainer' do
@@ -46,7 +46,7 @@ appraise 'rails_5.1' do
46
46
  gem 'dynamoid', '~> 1.3', platforms: :ruby
47
47
  gem 'aws-sdk', '~>2', platforms: :ruby
48
48
  gem 'redis-objects'
49
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
49
+ gem "after_commit_everywhere", "~> 1.0"
50
50
  end
51
51
 
52
52
  appraise 'rails_5.2' do
@@ -56,7 +56,7 @@ appraise 'rails_5.2' do
56
56
  gem 'dynamoid', '~>2.2', platforms: :ruby
57
57
  gem 'aws-sdk', '~>2', platforms: :ruby
58
58
  gem 'redis-objects'
59
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
59
+ gem "after_commit_everywhere", "~> 1.0"
60
60
  end
61
61
 
62
62
  appraise 'norails' do
data/CHANGELOG.md CHANGED
@@ -1,8 +1,19 @@
1
1
  # CHANGELOG
2
2
 
3
3
  ## unreleased
4
-
5
- ## 5.1.0
4
+ ## 5.2.0
5
+
6
+ * fix: timestamp will work with named machine [#739](https://github.com/aasm/aasm/pull/739), thanks to [RolandStuder](https://github.com/RolandStuder)
7
+ * Create namespaced scopes in PR [#735](https://github.com/aasm/aasm/pull/735), thanks to [caiohsramos](https://github.com/caiohsramos)
8
+ * Fix multiple state machines example per class on README in PR [#732](https://github.com/aasm/aasm/pull/732), thanks to [RodrigoVitiello](https://github.com/RodrigoVitiello)
9
+ * Update version in recommendation to add after_commit_everywhere in PR [#729](https://github.com/aasm/aasm/pull/729), thanks to [Envek](https://github.com/Envek)
10
+ * Fix i18n Event translations failing [#721](https://github.com/aasm/aasm/issues/721) in PR [#723](https://github.com/aasm/aasm/pull/723), thanks to [the-spectator](https://github.com/the-spectator)
11
+ * Add documentation to the Readme about how parameters are handled in AASM events in PR [#722](https://github.com/aasm/aasm/pull/722), thanks to [dstuebe](https://github.com/dstuebe)
12
+ * Fix human_state cached across locales [#709](https://github.com/aasm/aasm/issues/709) in PR [716](https://github.com/aasm/aasm/pull/716), thanks to [the-spectator](https://github.com/the-spectator)
13
+ * Relocate DslHelper from root namespace to under AASM namespace in PR [#711](https://github.com/aasm/aasm/pull/711) thank to [yujideveloper ](https://github.com/yujideveloper )
14
+ * Document how to define transitions from any state in in PR [#699](https://github.com/aasm/aasm/pull/699) thanks to [hedgesky](https://github.com/hedgesky)
15
+ * Add simple option for auto-generated timestamps in PR [#677](https://github.com/aasm/aasm/pull/677), thanks to [jaynetics](https://github.com/jaynetics)
16
+ ## 5.1.1
6
17
 
7
18
  * Fix Depreciation message for after_commit_everywhere [#695](https://github.com/aasm/aasm/issues/695) in PR [#696](https://github.com/aasm/aasm/pull/696)
8
19
  * Fix human_state to use display option [#684](https://github.com/aasm/aasm/issues/684) in PR [#697](https://github.com/aasm/aasm/pull/697)
data/README.md CHANGED
@@ -20,6 +20,7 @@
20
20
  - [Extending AASM](#extending-aasm)
21
21
  - [ActiveRecord](#activerecord)
22
22
  - [Bang events](#bang-events)
23
+ - [Timestamps](#timestamps)
23
24
  - [ActiveRecord enums](#activerecord-enums)
24
25
  - [Sequel](#sequel)
25
26
  - [Dynamoid](#dynamoid)
@@ -212,15 +213,28 @@ class LogRunTime
212
213
  end
213
214
  ```
214
215
 
215
- Also, you can pass parameters to events:
216
+ #### Parameters
217
+ You can pass parameters to events:
216
218
 
217
219
  ```ruby
218
220
  job = Job.new
219
221
  job.run(:defragmentation)
220
222
  ```
221
223
 
222
- In this case the `set_process` would be called with `:defragmentation` argument.
224
+ All guards and after callbacks will receive these parameters. In this case `set_process` would be called with
225
+ `:defragmentation` argument.
223
226
 
227
+ If the first argument to the event is a state (e.g. `:running` or `:finished`), the first argument is consumed and
228
+ the state machine will attempt to transition to that state. Add comma separated parameter for gaurds and callbacks
229
+
230
+ ```ruby
231
+ job = Job.new
232
+ job.run(:running, :defragmentation)
233
+ ```
234
+ In this case `set_process` won't be called, job will transition to running state and callback will receive
235
+ :defragmentation as parameter
236
+
237
+ #### Error Handling
224
238
  In case of an error during the event processing the error is rescued and passed to `:error`
225
239
  callback, which can handle it or re-raise it for further propagation.
226
240
 
@@ -434,6 +448,14 @@ job.stage1_completed
434
448
  job.aasm.current_state # stage3
435
449
  ```
436
450
 
451
+ You can define transition from any defined state by omitting `from`:
452
+
453
+ ```ruby
454
+ event :abort do
455
+ transitions to: :aborted
456
+ end
457
+ ```
458
+
437
459
  ### Display name for state
438
460
 
439
461
  You can define display name for state using :display option
@@ -495,13 +517,13 @@ simple = SimpleMultipleExample.new
495
517
 
496
518
  simple.aasm(:move).current_state
497
519
  # => :standing
498
- simple.aasm(:work).current
520
+ simple.aasm(:work).current_state
499
521
  # => :sleeping
500
522
 
501
523
  simple.start
502
524
  simple.aasm(:move).current_state
503
525
  # => :standing
504
- simple.aasm(:work).current
526
+ simple.aasm(:work).current_state
505
527
  # => :processing
506
528
 
507
529
  ```
@@ -698,7 +720,7 @@ end
698
720
  AASM comes with support for ActiveRecord and allows automatic persisting of the object's
699
721
  state in the database.
700
722
 
701
- Add `gem 'after_commit_everywhere', '~> 0.1', '>= 0.1.5'` to your Gemfile
723
+ Add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile.
702
724
 
703
725
  ```ruby
704
726
  class Job < ActiveRecord::Base
@@ -800,6 +822,37 @@ job.aasm_state = :running # => raises AASM::NoDirectAssignmentError
800
822
  job.aasm_state # => 'sleeping'
801
823
  ```
802
824
 
825
+ ### Timestamps
826
+
827
+ You can tell _AASM_ to try to write a timestamp whenever a new state is entered.
828
+ If `timestamps: true` is set, _AASM_ will look for a field named like the new state plus `_at` and try to fill it:
829
+
830
+ ```ruby
831
+ class Job < ActiveRecord::Base
832
+ include AASM
833
+
834
+ aasm timestamps: true do
835
+ state :sleeping, initial: true
836
+ state :running
837
+
838
+ event :run do
839
+ transitions from: :sleeping, to: :running
840
+ end
841
+ end
842
+ end
843
+ ```
844
+
845
+ resulting in this:
846
+
847
+ ```ruby
848
+ job = Job.create
849
+ job.running_at # => nil
850
+ job.run!
851
+ job.running_at # => 2020-02-20 20:00:00
852
+ ```
853
+
854
+ Missing timestamp fields are silently ignored, so it is not necessary to have setters (such as ActiveRecord columns) for *all* states when using this option.
855
+
803
856
  #### ActiveRecord enums
804
857
 
805
858
  You can use
@@ -1007,7 +1060,7 @@ job.save! #notify_about_running_job is not run
1007
1060
  Please note that `:after_commit` AASM callbacks behaves around custom implementation
1008
1061
  of transaction pattern rather than a real-life DB transaction. This fact still causes
1009
1062
  the race conditions and redundant callback calls within nested transaction. In order
1010
- to fix that it's highly recommended to add `gem 'after_commit_everywhere', '~> 0.1', '>= 0.1.5'`
1063
+ to fix that it's highly recommended to add `gem 'after_commit_everywhere', '~> 1.0'`
1011
1064
  to your `Gemfile`.
1012
1065
 
1013
1066
  If you want to encapsulate state changes within an own transaction, the behavior
data/aasm.gemspec CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
24
24
  s.add_development_dependency 'generator_spec'
25
25
  s.add_development_dependency 'appraisal'
26
26
  s.add_development_dependency "simplecov"
27
- s.add_development_dependency "codecov", ">= 0.1.17", '< 0.1.20'
27
+ s.add_development_dependency "codecov", ">= 0.1.21"
28
28
 
29
29
  # debugging
30
30
  # s.add_development_dependency 'debugger'
@@ -12,6 +12,6 @@ gem "dynamoid", "~> 1", platforms: :ruby
12
12
  gem "aws-sdk", "~> 2", platforms: :ruby
13
13
  gem "redis-objects"
14
14
  gem "activerecord-jdbcsqlite3-adapter", "1.3.24", platforms: :jruby
15
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
15
+ gem "after_commit_everywhere", "~> 1.0"
16
16
 
17
17
  gemspec path: "../"
@@ -7,6 +7,6 @@ gem "rails", "4.2.5"
7
7
  gem "mime-types", "~> 2", platforms: [:ruby_19, :jruby]
8
8
  gem "mongoid", "~> 5.0"
9
9
  gem "activerecord-jdbcsqlite3-adapter", "1.3.24", platforms: :jruby
10
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
10
+ gem "after_commit_everywhere", "~> 1.0"
11
11
 
12
12
  gemspec path: "../"
@@ -9,6 +9,6 @@ gem "sequel"
9
9
  gem "dynamoid", "~> 1.3", platforms: :ruby
10
10
  gem "aws-sdk", "~> 2", platforms: :ruby
11
11
  gem "redis-objects"
12
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
12
+ gem "after_commit_everywhere", "~> 1.0"
13
13
 
14
14
  gemspec path: "../"
@@ -9,6 +9,6 @@ gem "sequel"
9
9
  gem "dynamoid", "~> 1.3", platforms: :ruby
10
10
  gem "aws-sdk", "~>2", platforms: :ruby
11
11
  gem "redis-objects"
12
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
12
+ gem "after_commit_everywhere", "~> 1.0"
13
13
 
14
14
  gemspec path: "../"
@@ -9,6 +9,6 @@ gem "sequel"
9
9
  gem "dynamoid", "~>2.2", platforms: :ruby
10
10
  gem "aws-sdk", "~>2", platforms: :ruby
11
11
  gem "redis-objects"
12
- gem "after_commit_everywhere", "~> 0.1", ">= 0.1.5"
12
+ gem "after_commit_everywhere", "~> 1.0"
13
13
 
14
14
  gemspec path: "../"
data/lib/aasm/base.rb CHANGED
@@ -37,6 +37,9 @@ module AASM
37
37
  # string for a specific lock type i.e. FOR UPDATE NOWAIT
38
38
  configure :requires_lock, false
39
39
 
40
+ # automatically set `"#{state_name}_at" = ::Time.now` on state changes
41
+ configure :timestamps, false
42
+
40
43
  # set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
41
44
  configure :no_direct_assignment, false
42
45
 
@@ -51,19 +54,12 @@ module AASM
51
54
  # Configure a logger, with default being a Logger to STDERR
52
55
  configure :logger, Logger.new(STDERR)
53
56
 
57
+ # setup timestamp-setting callback if enabled
58
+ setup_timestamps(@name)
59
+
54
60
  # make sure to raise an error if no_direct_assignment is enabled
55
61
  # and attribute is directly assigned though
56
- aasm_name = @name
57
-
58
- if @state_machine.config.no_direct_assignment
59
- @klass.send(:define_method, "#{@state_machine.config.column}=") do |state_name|
60
- if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
61
- raise AASM::NoDirectAssignmentError.new('direct assignment of AASM column has been disabled (see AASM configuration for this class)')
62
- else
63
- super(state_name)
64
- end
65
- end
66
- end
62
+ setup_no_direct_assignment(@name)
67
63
  end
68
64
 
69
65
  # This method is both a getter and a setter
@@ -267,5 +263,28 @@ module AASM
267
263
  end
268
264
  end
269
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
+
270
289
  end
271
290
  end
@@ -24,6 +24,9 @@ module AASM
24
24
  # for ActiveRecord: use pessimistic locking
25
25
  attr_accessor :requires_lock
26
26
 
27
+ # automatically set `"#{state_name}_at" = ::Time.now` on state changes
28
+ attr_accessor :timestamps
29
+
27
30
  # forbid direct assignment in aasm_state column (in ActiveRecord)
28
31
  attr_accessor :no_direct_assignment
29
32
 
@@ -2,9 +2,9 @@
2
2
 
3
3
  module AASM::Core
4
4
  class Event
5
- include DslHelper
5
+ include AASM::DslHelper
6
6
 
7
- attr_reader :name, :state_machine, :options
7
+ attr_reader :name, :state_machine, :options, :default_display_name
8
8
 
9
9
  def initialize(name, state_machine, options = {}, &block)
10
10
  @name = name
@@ -13,6 +13,7 @@ module AASM::Core
13
13
  @valid_transitions = {}
14
14
  @guards = Array(options[:guard] || options[:guards] || options[:if])
15
15
  @unless = Array(options[:unless]) #TODO: This could use a better name
16
+ @default_display_name = name.to_s.gsub(/_/, ' ').capitalize
16
17
 
17
18
  # from aasm4
18
19
  @options = options # QUESTION: .dup ?
@@ -109,6 +110,10 @@ module AASM::Core
109
110
  transitions.flat_map(&:failures)
110
111
  end
111
112
 
113
+ def to_s
114
+ name.to_s
115
+ end
116
+
112
117
  private
113
118
 
114
119
  def attach_event_guards(definitions)
@@ -2,12 +2,13 @@
2
2
 
3
3
  module AASM::Core
4
4
  class State
5
- attr_reader :name, :state_machine, :options
5
+ attr_reader :name, :state_machine, :options, :default_display_name
6
6
 
7
7
  def initialize(name, klass, state_machine, options={})
8
8
  @name = name
9
9
  @klass = klass
10
10
  @state_machine = state_machine
11
+ @default_display_name = name.to_s.gsub(/_/, ' ').capitalize
11
12
  update(options)
12
13
  end
13
14
 
@@ -54,11 +55,11 @@ module AASM::Core
54
55
  end
55
56
 
56
57
  def display_name
57
- @display_name ||= begin
58
+ @display_name = begin
58
59
  if Module.const_defined?(:I18n)
59
60
  localized_name
60
61
  else
61
- name.to_s.gsub(/_/, ' ').capitalize
62
+ @default_display_name
62
63
  end
63
64
  end
64
65
  end
@@ -75,8 +76,8 @@ module AASM::Core
75
76
  private
76
77
 
77
78
  def update(options = {})
78
- if options.key?(:display) then
79
- @display_name = options.delete(:display)
79
+ if options.key?(:display)
80
+ @default_display_name = options.delete(:display)
80
81
  end
81
82
  @options = options
82
83
  self
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AASM::Core
4
4
  class Transition
5
- include DslHelper
5
+ include AASM::DslHelper
6
6
 
7
7
  attr_reader :from, :to, :event, :opts, :failures
8
8
  alias_method :options, :opts
@@ -1,30 +1,32 @@
1
- module DslHelper
1
+ module AASM
2
+ module DslHelper
2
3
 
3
- class Proxy
4
- attr_accessor :options
4
+ class Proxy
5
+ attr_accessor :options
5
6
 
6
- def initialize(options, valid_keys, source)
7
- @valid_keys = valid_keys
8
- @source = source
7
+ def initialize(options, valid_keys, source)
8
+ @valid_keys = valid_keys
9
+ @source = source
9
10
 
10
- @options = options
11
- end
11
+ @options = options
12
+ end
12
13
 
13
- def method_missing(name, *args, &block)
14
- if @valid_keys.include?(name)
15
- options[name] = Array(options[name])
16
- options[name] << block if block
17
- options[name] += Array(args)
18
- else
19
- @source.send name, *args, &block
14
+ def method_missing(name, *args, &block)
15
+ if @valid_keys.include?(name)
16
+ options[name] = Array(options[name])
17
+ options[name] << block if block
18
+ options[name] += Array(args)
19
+ else
20
+ @source.send name, *args, &block
21
+ end
20
22
  end
21
23
  end
22
- end
23
24
 
24
- def add_options_from_dsl(options, valid_keys, &block)
25
- proxy = Proxy.new(options, valid_keys, self)
26
- proxy.instance_eval(&block)
27
- proxy.options
28
- end
25
+ def add_options_from_dsl(options, valid_keys, &block)
26
+ proxy = Proxy.new(options, valid_keys, self)
27
+ proxy.instance_eval(&block)
28
+ proxy.options
29
+ end
29
30
 
30
- end
31
+ end
32
+ end
@@ -5,7 +5,7 @@ module AASM
5
5
  list << :"#{i18n_scope(klass)}.events.#{i18n_klass(ancestor)}.#{event}"
6
6
  list
7
7
  end
8
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => event.to_s.humanize)
8
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => default_display_name(event))
9
9
  end
10
10
 
11
11
  def human_state_name(klass, state)
@@ -14,7 +14,7 @@ module AASM
14
14
  list << item_for(klass, state, ancestor, :old_style => true)
15
15
  list
16
16
  end
17
- translate_queue(checklist) || I18n.translate(checklist.shift, :default => state.to_s.humanize)
17
+ translate_queue(checklist) || I18n.translate(checklist.shift, :default => default_display_name(state))
18
18
  end
19
19
 
20
20
  private
@@ -46,8 +46,18 @@ module AASM
46
46
  end
47
47
 
48
48
  def ancestors_list(klass)
49
+ has_active_record_base = defined?(::ActiveRecord::Base)
49
50
  klass.ancestors.select do |ancestor|
50
- ancestor.respond_to?(:model_name) unless ancestor.name == 'ActiveRecord::Base'
51
+ not_active_record_base = has_active_record_base ? (ancestor != ::ActiveRecord::Base) : true
52
+ ancestor.respond_to?(:model_name) && not_active_record_base
53
+ end
54
+ end
55
+
56
+ def default_display_name(object) # Can use better arguement name
57
+ if object.respond_to?(:default_display_name)
58
+ object.default_display_name
59
+ else
60
+ object.to_s.gsub(/_/, ' ').capitalize
51
61
  end
52
62
  end
53
63
  end
@@ -73,7 +73,7 @@ module AASM
73
73
  rescue LoadError
74
74
  warn <<-MSG
75
75
  [DEPRECATION] :after_commit AASM callback is not safe in terms of race conditions and redundant calls.
76
- Please add `gem 'after_commit_everywhere', '~> 0.1', '>= 0.1.5'` to your Gemfile in order to fix that.
76
+ Please add `gem 'after_commit_everywhere', '~> 1.0'` to your Gemfile in order to fix that.
77
77
  MSG
78
78
  yield
79
79
  end
@@ -59,7 +59,9 @@ module AASM
59
59
  # make sure to create a (named) scope for each state
60
60
  def state_with_scope(*args)
61
61
  names = state_without_scope(*args)
62
- names.each { |name| create_scope(name) if create_scope?(name) }
62
+ names.each do |name|
63
+ create_scopes(name)
64
+ end
63
65
  end
64
66
  alias_method :state_without_scope, :state
65
67
  alias_method :state, :state_with_scope
@@ -71,7 +73,16 @@ module AASM
71
73
  end
72
74
 
73
75
  def create_scope(name)
74
- @klass.aasm_create_scope(@name, name)
76
+ @klass.aasm_create_scope(@name, name) if create_scope?(name)
77
+ end
78
+
79
+ def create_scopes(name)
80
+ if namespace?
81
+ # Create default scopes even when namespace? for backward compatibility
82
+ namepaced_name = "#{namespace}_#{name}"
83
+ create_scope(namepaced_name)
84
+ end
85
+ create_scope(name)
75
86
  end
76
87
  end # Base
77
88
 
data/lib/aasm/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module AASM
2
- VERSION = "5.1.1"
2
+ VERSION = "5.2.0"
3
3
  end
data/spec/database.rb CHANGED
@@ -5,17 +5,10 @@ ActiveRecord::Migration.suppress_messages do
5
5
  end
6
6
  end
7
7
 
8
- ActiveRecord::Migration.create_table "simple_new_dsls", :force => true do |t|
9
- t.string "status"
10
- end
11
- ActiveRecord::Migration.create_table "multiple_simple_new_dsls", :force => true do |t|
12
- t.string "status"
13
- end
14
- ActiveRecord::Migration.create_table "implemented_abstract_class_dsls", :force => true do |t|
15
- t.string "status"
16
- end
17
- ActiveRecord::Migration.create_table "users", :force => true do |t|
18
- t.string "status"
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
19
12
  end
20
13
 
21
14
  ActiveRecord::Migration.create_table "complex_active_record_examples", :force => true do |t|
@@ -56,4 +49,9 @@ ActiveRecord::Migration.suppress_messages do
56
49
  t.string "state"
57
50
  t.string "some_string"
58
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
59
57
  end
@@ -4,3 +4,10 @@ en:
4
4
  localizer_test_model:
5
5
  aasm_state:
6
6
  opened: "It's open now!"
7
+
8
+ fr:
9
+ activerecord:
10
+ attributes:
11
+ localizer_test_model:
12
+ aasm_state:
13
+ opened: "C'est ouvert maintenant!"
@@ -3,3 +3,9 @@ en:
3
3
  attributes:
4
4
  localizer_test_model:
5
5
  aasm_state/opened: "It's open now!"
6
+
7
+ fr:
8
+ activerecord:
9
+ attributes:
10
+ localizer_test_model:
11
+ aasm_state/opened: "C'est ouvert maintenant!"
@@ -24,11 +24,19 @@ describe 'localized state names' do
24
24
  state = LocalizerTestModel.aasm.states.detect {|s| s == :opened}
25
25
  expect(state.localized_name).to eq("It's open now!")
26
26
  expect(state.human_name).to eq("It's open now!")
27
+ expect(state.display_name).to eq("It's open now!")
28
+
29
+ I18n.with_locale(:fr) do
30
+ expect(state.localized_name).to eq("C'est ouvert maintenant!")
31
+ expect(state.human_name).to eq("C'est ouvert maintenant!")
32
+ expect(state.display_name).to eq("C'est ouvert maintenant!")
33
+ end
27
34
  end
28
35
 
29
36
  it 'should use fallback' do
30
37
  state = LocalizerTestModel.aasm.states.detect {|s| s == :closed}
31
38
  expect(state.localized_name).to eq('Closed')
32
39
  expect(state.human_name).to eq('Closed')
40
+ expect(state.display_name).to eq('Closed')
33
41
  end
34
42
  end
@@ -0,0 +1,16 @@
1
+ class MultipleNamespaced < ActiveRecord::Base
2
+ include AASM
3
+
4
+ aasm(:status, namespace: :car) do
5
+ state :unsold, initial: true
6
+ state :sold
7
+
8
+ event :sell do
9
+ transitions from: :unsold, to: :sold
10
+ end
11
+
12
+ event :return do
13
+ transitions from: :sold, to: :unsold
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ class TimestampExample < ActiveRecord::Base
2
+ include AASM
3
+
4
+ aasm column: :aasm_state, timestamps: true do
5
+ state :opened
6
+ state :closed
7
+
8
+ event :open do
9
+ transitions to: :opened
10
+ end
11
+
12
+ event :close do
13
+ transitions to: :closed
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ class TimestampExampleMongoid
2
+ include Mongoid::Document
3
+ include AASM
4
+
5
+ field :status, type: String
6
+ field :opened_at, type: Time
7
+
8
+ aasm column: :status, timestamps: true do
9
+ state :opened
10
+ state :closed
11
+
12
+ event :open do
13
+ transitions to: :opened
14
+ end
15
+
16
+ event :close do
17
+ transitions to: :closed
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,19 @@
1
+ class TimestampsExample
2
+ include AASM
3
+
4
+ attr_accessor :opened_at
5
+ attr_reader :closed_at
6
+
7
+ aasm timestamps: true do
8
+ state :opened
9
+ state :closed
10
+
11
+ event :open do
12
+ transitions to: :opened
13
+ end
14
+
15
+ event :close do
16
+ transitions to: :closed
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ class TimestampsWithNamedMachineExample
2
+ include AASM
3
+
4
+ attr_accessor :opened_at
5
+
6
+ aasm :my_state, timestamps: true do
7
+ state :opened
8
+
9
+ event :open do
10
+ transitions to: :opened
11
+ end
12
+ end
13
+ end
@@ -29,12 +29,28 @@ if defined?(ActiveRecord)
29
29
  end
30
30
 
31
31
  context 'aasm.human_event_name' do
32
- it 'should return translated event name' do
33
- expect(LocalizerTestModel.aasm.human_event_name(:close)).to eq("Let's close it!")
32
+ context 'with event name' do
33
+ it 'should return translated event name' do
34
+ expect(LocalizerTestModel.aasm.human_event_name(:close)).to eq("Let's close it!")
35
+ end
36
+
37
+ it 'should return humanized event name' do
38
+ expect(LocalizerTestModel.aasm.human_event_name(:open)).to eq("Open")
39
+ end
34
40
  end
35
41
 
36
- it 'should return humanized event name' do
37
- expect(LocalizerTestModel.aasm.human_event_name(:open)).to eq("Open")
42
+ context 'with event object' do
43
+ it 'should return translated event name' do
44
+ event = LocalizerTestModel.aasm.events.detect { |e| e.name == :close }
45
+
46
+ expect(LocalizerTestModel.aasm.human_event_name(event)).to eq("Let's close it!")
47
+ end
48
+
49
+ it 'should return humanized event name' do
50
+ event = LocalizerTestModel.aasm.events.detect { |e| e.name == :open }
51
+
52
+ expect(LocalizerTestModel.aasm.human_event_name(event)).to eq("Open")
53
+ end
38
54
  end
39
55
  end
40
56
  end
@@ -65,12 +81,28 @@ if defined?(ActiveRecord)
65
81
  end
66
82
 
67
83
  context 'aasm.human_event_name' do
68
- it 'should return translated event name' do
69
- expect(LocalizerTestModel.aasm.human_event_name(:close)).to eq("Let's close it!")
84
+ context 'with event name' do
85
+ it 'should return translated event name' do
86
+ expect(LocalizerTestModel.aasm.human_event_name(:close)).to eq("Let's close it!")
87
+ end
88
+
89
+ it 'should return humanized event name' do
90
+ expect(LocalizerTestModel.aasm.human_event_name(:open)).to eq("Open")
91
+ end
70
92
  end
71
93
 
72
- it 'should return humanized event name' do
73
- expect(LocalizerTestModel.aasm.human_event_name(:open)).to eq("Open")
94
+ context 'with event object' do
95
+ it 'should return translated event name' do
96
+ event = LocalizerTestModel.aasm.events.detect { |e| e.name == :close }
97
+
98
+ expect(LocalizerTestModel.aasm.human_event_name(event)).to eq("Let's close it!")
99
+ end
100
+
101
+ it 'should return humanized event name' do
102
+ event = LocalizerTestModel.aasm.events.detect { |e| e.name == :open }
103
+
104
+ expect(LocalizerTestModel.aasm.human_event_name(event)).to eq("Open")
105
+ end
74
106
  end
75
107
  end
76
108
  end
@@ -331,6 +331,23 @@ if defined?(ActiveRecord)
331
331
  expect(MultipleSimpleNewDsl.unknown_scope).to contain_exactly(dsl2)
332
332
  end
333
333
  end
334
+
335
+ context "when namespeced" do
336
+ it "add namespaced scopes" do
337
+ expect(MultipleNamespaced).to respond_to(:car_unsold)
338
+ expect(MultipleNamespaced).to respond_to(:car_sold)
339
+
340
+ expect(MultipleNamespaced.car_unsold.is_a?(ActiveRecord::Relation)).to be_truthy
341
+ expect(MultipleNamespaced.car_sold.is_a?(ActiveRecord::Relation)).to be_truthy
342
+ end
343
+ it "add unnamespaced scopes" do
344
+ expect(MultipleNamespaced).to respond_to(:unsold)
345
+ expect(MultipleNamespaced).to respond_to(:sold)
346
+
347
+ expect(MultipleNamespaced.unsold.is_a?(ActiveRecord::Relation)).to be_truthy
348
+ expect(MultipleNamespaced.sold.is_a?(ActiveRecord::Relation)).to be_truthy
349
+ end
350
+ end
334
351
  end # scopes
335
352
 
336
353
  describe "direct assignment" do
@@ -837,4 +837,16 @@ if defined?(ActiveRecord)
837
837
  expect(example.complete!).to be_falsey
838
838
  end
839
839
  end
840
+
841
+ describe 'testing the timestamps option' do
842
+ let(:example) { TimestampExample.create! }
843
+
844
+ it 'should update existing timestamp columns' do
845
+ expect { example.open! }.to change { example.reload.opened_at }.from(nil).to(instance_of(::Time))
846
+ end
847
+
848
+ it 'should not fail if there is no corresponding timestamp column' do
849
+ expect { example.close! }.to change { example.reload.aasm_state }
850
+ end
851
+ end
840
852
  end
@@ -161,5 +161,17 @@ if defined?(Mongoid::Document)
161
161
  end
162
162
  end
163
163
 
164
+ describe 'testing the timestamps option' do
165
+ let(:example) { TimestampExampleMongoid.create }
166
+
167
+ it 'should update existing timestamp fields' do
168
+ expect { example.open! }.to change { example.reload.opened_at }.from(nil).to(instance_of(::Time))
169
+ end
170
+
171
+ it 'should not fail if there is no corresponding timestamp field' do
172
+ expect { example.close! }.to change { example.reload.status }
173
+ end
174
+ end
175
+
164
176
  end
165
177
  end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'timestamps option' do
4
+ it 'calls a timestamp setter based on the state name when entering a new state' do
5
+ object = TimestampsExample.new
6
+ expect { object.open }.to change { object.opened_at }.from(nil).to(instance_of(::Time))
7
+ end
8
+
9
+ it 'overwrites any previous timestamp if a state is entered repeatedly' do
10
+ object = TimestampsExample.new
11
+ object.opened_at = ::Time.new(2000, 1, 1)
12
+ expect { object.open }.to change { object.opened_at }
13
+ end
14
+
15
+ it 'does nothing if there is no setter matching the new state' do
16
+ object = TimestampsExample.new
17
+ expect { object.close }.not_to change { object.closed_at }
18
+ end
19
+
20
+ it 'can be turned off and on' do
21
+ object = TimestampsExample.new
22
+ object.class.aasm.state_machine.config.timestamps = false
23
+ expect { object.open }.not_to change { object.opened_at }
24
+ object.class.aasm.state_machine.config.timestamps = true
25
+ expect { object.open }.to change { object.opened_at }
26
+ end
27
+
28
+ it 'calls a timestamp setter when using a named state machine' do
29
+ object = TimestampsWithNamedMachineExample.new
30
+ expect { object.open }.to change { object.opened_at }.from(nil).to(instance_of(::Time))
31
+ end
32
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aasm
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.1.1
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Thorsten Boettger
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-08-11 00:00:00.000000000 Z
12
+ date: 2021-05-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: concurrent-ruby
@@ -115,20 +115,14 @@ dependencies:
115
115
  requirements:
116
116
  - - ">="
117
117
  - !ruby/object:Gem::Version
118
- version: 0.1.17
119
- - - "<"
120
- - !ruby/object:Gem::Version
121
- version: 0.1.20
118
+ version: 0.1.21
122
119
  type: :development
123
120
  prerelease: false
124
121
  version_requirements: !ruby/object:Gem::Requirement
125
122
  requirements:
126
123
  - - ">="
127
124
  - !ruby/object:Gem::Version
128
- version: 0.1.17
129
- - - "<"
130
- - !ruby/object:Gem::Version
131
- version: 0.1.20
125
+ version: 0.1.21
132
126
  - !ruby/object:Gem::Dependency
133
127
  name: pry
134
128
  requirement: !ruby/object:Gem::Requirement
@@ -246,6 +240,7 @@ files:
246
240
  - spec/models/active_record/instance_level_skip_validation_example.rb
247
241
  - spec/models/active_record/invalid_persistor.rb
248
242
  - spec/models/active_record/localizer_test_model.rb
243
+ - spec/models/active_record/namespaced.rb
249
244
  - spec/models/active_record/no_direct_assignment.rb
250
245
  - spec/models/active_record/no_scope.rb
251
246
  - spec/models/active_record/persisted_state.rb
@@ -256,6 +251,7 @@ files:
256
251
  - spec/models/active_record/silent_persistor.rb
257
252
  - spec/models/active_record/simple_new_dsl.rb
258
253
  - spec/models/active_record/thief.rb
254
+ - spec/models/active_record/timestamp_example.rb
259
255
  - spec/models/active_record/transactor.rb
260
256
  - spec/models/active_record/transient.rb
261
257
  - spec/models/active_record/validator.rb
@@ -302,6 +298,7 @@ files:
302
298
  - spec/models/mongoid/silent_persistor_mongoid.rb
303
299
  - spec/models/mongoid/simple_mongoid.rb
304
300
  - spec/models/mongoid/simple_new_dsl_mongoid.rb
301
+ - spec/models/mongoid/timestamp_example_mongoid.rb
305
302
  - spec/models/mongoid/validator_mongoid.rb
306
303
  - spec/models/multi_transitioner.rb
307
304
  - spec/models/multiple_transitions_that_differ_only_by_guard.rb
@@ -343,6 +340,8 @@ files:
343
340
  - spec/models/sub_classing.rb
344
341
  - spec/models/super_class.rb
345
342
  - spec/models/this_name_better_not_be_in_use.rb
343
+ - spec/models/timestamps_example.rb
344
+ - spec/models/timestamps_with_named_machine_example.rb
346
345
  - spec/models/valid_state_name.rb
347
346
  - spec/spec_helper.rb
348
347
  - spec/spec_helpers/active_record.rb
@@ -407,6 +406,7 @@ files:
407
406
  - spec/unit/states_on_one_line_example_spec.rb
408
407
  - spec/unit/subclassing_multiple_spec.rb
409
408
  - spec/unit/subclassing_spec.rb
409
+ - spec/unit/timestamps_spec.rb
410
410
  - spec/unit/transition_spec.rb
411
411
  - test/minitest_helper.rb
412
412
  - test/unit/minitest_matcher_test.rb
@@ -451,6 +451,7 @@ test_files:
451
451
  - spec/models/active_record/instance_level_skip_validation_example.rb
452
452
  - spec/models/active_record/invalid_persistor.rb
453
453
  - spec/models/active_record/localizer_test_model.rb
454
+ - spec/models/active_record/namespaced.rb
454
455
  - spec/models/active_record/no_direct_assignment.rb
455
456
  - spec/models/active_record/no_scope.rb
456
457
  - spec/models/active_record/persisted_state.rb
@@ -461,6 +462,7 @@ test_files:
461
462
  - spec/models/active_record/silent_persistor.rb
462
463
  - spec/models/active_record/simple_new_dsl.rb
463
464
  - spec/models/active_record/thief.rb
465
+ - spec/models/active_record/timestamp_example.rb
464
466
  - spec/models/active_record/transactor.rb
465
467
  - spec/models/active_record/transient.rb
466
468
  - spec/models/active_record/validator.rb
@@ -507,6 +509,7 @@ test_files:
507
509
  - spec/models/mongoid/silent_persistor_mongoid.rb
508
510
  - spec/models/mongoid/simple_mongoid.rb
509
511
  - spec/models/mongoid/simple_new_dsl_mongoid.rb
512
+ - spec/models/mongoid/timestamp_example_mongoid.rb
510
513
  - spec/models/mongoid/validator_mongoid.rb
511
514
  - spec/models/multi_transitioner.rb
512
515
  - spec/models/multiple_transitions_that_differ_only_by_guard.rb
@@ -548,6 +551,8 @@ test_files:
548
551
  - spec/models/sub_classing.rb
549
552
  - spec/models/super_class.rb
550
553
  - spec/models/this_name_better_not_be_in_use.rb
554
+ - spec/models/timestamps_example.rb
555
+ - spec/models/timestamps_with_named_machine_example.rb
551
556
  - spec/models/valid_state_name.rb
552
557
  - spec/spec_helper.rb
553
558
  - spec/spec_helpers/active_record.rb
@@ -612,6 +617,7 @@ test_files:
612
617
  - spec/unit/states_on_one_line_example_spec.rb
613
618
  - spec/unit/subclassing_multiple_spec.rb
614
619
  - spec/unit/subclassing_spec.rb
620
+ - spec/unit/timestamps_spec.rb
615
621
  - spec/unit/transition_spec.rb
616
622
  - test/minitest_helper.rb
617
623
  - test/unit/minitest_matcher_test.rb