aasm 5.1.1 → 5.2.0

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