aasm 4.8.0 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4976d2633b75f1b3ee17661e698a4aa3c5ff96bb
4
- data.tar.gz: 19598287ca29eb957579fef45c2054efa227b8f2
3
+ metadata.gz: d4a393ed849111b8beee789a849433679c36f940
4
+ data.tar.gz: 905f16c76e62d5481bcfe55f4bb935039b0c7726
5
5
  SHA512:
6
- metadata.gz: 71e5ccc6259fbb7400b7e2c4c14da15a1ad2275a59ca94d23469afc1e1c5212f3a8a38b68f7fc5ed99b466bcef3df350c331d9525b324274d85dc47ded71132b
7
- data.tar.gz: a3688432d71fe26211b511d665b13b9a171a3e79196dc4eac3fb6fae5f63b60ae82f4e15d20a5e7b627b02dfdf1d47e8643b67bad9d45d6585848dfb8a501864
6
+ metadata.gz: 1e64e9fffd9c560a166ab728105d3891ca9a6a8784c27b70f83d9fd91ed7ccde1c839662f8e7e099ef4128db6bc91a690a30c8fbb3c0f83e4bfeba7d2c0ad67f
7
+ data.tar.gz: 574cafabbe8b20e1bf9db7ef87ff10c358dd814fd3bc86fc2f843824c9be8deb4ad645c9a0273a7ecf861bfb6136b519476f1b749a0d3706e798b24a3dd92da8
@@ -1,5 +1,15 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 4.9.0
4
+
5
+ * add support for callback classes (`after` only) (see [issue #316](https://github.com/aasm/aasm/pull/316) for details, thanks to [@mlr](https://github.com/mlr))
6
+ * allow easier extension of _AASM_ (utilising the idea of _ApplicationRecords_ from _Rails 5_) (see [issue #296](https://github.com/aasm/aasm/pull/296) for details, thanks to [@mlr](https://github.com/mlr))
7
+ * support pessimistic locking for _ActiveRecord_ (see [issue #283](https://github.com/aasm/aasm/pull/283) for details, thanks to [@HoyaBoya](https://github.com/HoyaBoya))
8
+ * fix: support database sharding for _ActiveRecord_ (see [issue #289](https://github.com/aasm/aasm/pull/289) for details, thanks to [@scambra](https://github.com/scambra))
9
+ * fix: some issues with RubyMotion (see [issue #318](https://github.com/aasm/aasm/pull/318) for details, thanks to [@Infotaku](https://github.com/Infotaku))
10
+ * fix: Rails generator now features the correct namespace (see [issue #328](https://github.com/aasm/aasm/pull/328) and [issue #329](https://github.com/aasm/aasm/pull/329) for details, thanks to [@anilmaurya](https://github.com/anilmaurya))
11
+
12
+
3
13
  ## 4.8.0
4
14
 
5
15
  * add support for [dynamoid](http://joshsymonds.com/Dynamoid/) (see [issue #300](https://github.com/aasm/aasm/pull/300) for details, thanks to [@LeeChSien](https://github.com/LeeChSien))
data/README.md CHANGED
@@ -106,6 +106,7 @@ class Job
106
106
  end
107
107
 
108
108
  transitions :from => :sleeping, :to => :running, :after => Proc.new {|*args| set_process(*args) }
109
+ transitions :from => :running, :to => :finished, :after => LogRunTime
109
110
  end
110
111
 
111
112
  event :sleep do
@@ -136,12 +137,34 @@ class Job
136
137
  end
137
138
 
138
139
  end
140
+
141
+ class LogRunTime
142
+ def call
143
+ log "Job was running for X seconds"
144
+ end
145
+ end
139
146
  ```
140
147
 
141
148
  In this case `do_something` is called before actually entering the state `sleeping`,
142
149
  while `notify_somebody` is called after the transition `run` (from `sleeping` to `running`)
143
150
  is finished.
144
151
 
152
+ AASM will also initialize `LogRunTime` and run the `call` method for you after the transition from `runnung` to `finished` in the example above. You can pass arguments to the class by defining an initialize method on it, like this:
153
+
154
+ ```
155
+ class LogRunTime
156
+ # optional args parameter can be omitted, but if you define initialize
157
+ # you must accept the model instance as the first parameter to it.
158
+ def initialize(job, args = {})
159
+ @job = job
160
+ end
161
+
162
+ def call
163
+ log "Job was running for #{@job.run_time} seconds"
164
+ end
165
+ end
166
+ ```
167
+
145
168
  Here you can see a list of all possible callbacks, together with their order of calling:
146
169
 
147
170
  ```ruby
@@ -394,6 +417,71 @@ SimpleMultipleExample.aasm(:work).states
394
417
  *Final note*: Support for multiple state machines per class is a pretty new feature
395
418
  (since version `4.3`), so please bear with us in case it doesn't work as expected.
396
419
 
420
+ ### Extending AASM
421
+
422
+ AASM allows you to easily extend `AASM::Base` for your own application purposes.
423
+
424
+ Let's suppose we have common logic across many AASM models. We can embody this logic in a sub-class of `AASM::Base`.
425
+
426
+ ```
427
+ class CustomAASMBase < AASM::Base
428
+ # A custom transiton that we want available across many AASM models.
429
+ def count_transitions!
430
+ klass.class_eval do
431
+ aasm :with_klass => CustomAASMBase do
432
+ after_all_transitions :increment_transition_count
433
+ end
434
+ end
435
+ end
436
+
437
+ # A custom annotation that we want available across many AASM models.
438
+ def requires_guards!
439
+ klass.class_eval do
440
+ attr_reader :authorizable_called,
441
+ :transition_count,
442
+ :fillable_called
443
+
444
+ def authorizable?
445
+ @authorizable_called = true
446
+ end
447
+
448
+ def fillable?
449
+ @fillable_called = true
450
+ end
451
+
452
+ def increment_transition_count
453
+ @transition_count ||= 0
454
+ @transition_count += 1
455
+ end
456
+ end
457
+ end
458
+ end
459
+ ```
460
+
461
+ When we declare our model that has an AASM state machine, we simply declare the AASM block with a `:with` key to our own class.
462
+
463
+ ```
464
+ class SimpleCustomExample
465
+ include AASM
466
+
467
+ # Let's build an AASM state machine with our custom class.
468
+ aasm :with_klass => CustomAASMBase do
469
+ requires_guards!
470
+ count_transitions!
471
+
472
+ state :initialised, :initial => true
473
+ state :filled_out
474
+ state :authorised
475
+
476
+ event :fill_out do
477
+ transitions :from => :initialised, :to => :filled_out, :guard => :fillable?
478
+ end
479
+ event :authorise do
480
+ transitions :from => :filled_out, :to => :authorised, :guard => :authorizable?
481
+ end
482
+ end
483
+ end
484
+ ```
397
485
 
398
486
 
399
487
  ### ActiveRecord
@@ -693,6 +781,41 @@ end
693
781
 
694
782
  which then leads to `transaction(:requires_new => false)`, the Rails default.
695
783
 
784
+ ### Pessimistic Locking
785
+
786
+ AASM supports [Active Record pessimistic locking via `with_lock`](http://api.rubyonrails.org/classes/ActiveRecord/Locking/Pessimistic.html#method-i-with_lock) for database persistence layers.
787
+
788
+ | Option | Purpose |
789
+ | ------ | ------- |
790
+ | `false` (default) | No lock is obtained | |
791
+ | `true` | Obtain a blocking pessimistic lock e.g. `FOR UPDATE` |
792
+ | String | Obtain a lock based on the SQL string e.g. `FOR UPDATE NOWAIT` |
793
+
794
+
795
+ ```ruby
796
+ class Job < ActiveRecord::Base
797
+ include AASM
798
+
799
+ aasm :requires_lock => true do
800
+ ...
801
+ end
802
+
803
+ ...
804
+ end
805
+ ```
806
+
807
+ ```ruby
808
+ class Job < ActiveRecord::Base
809
+ include AASM
810
+
811
+ aasm :requires_lock => 'FOR UPDATE NOWAIT' do
812
+ ...
813
+ end
814
+
815
+ ...
816
+ end
817
+ ```
818
+
696
819
 
697
820
  ### Column name & migration
698
821
 
@@ -761,8 +884,12 @@ Job.aasm.states_for_select
761
884
 
762
885
  To use AASM with a RubyMotion project, use it with the [motion-bundler](https://github.com/archan937/motion-bundler) gem.
763
886
 
764
- Warning: Due to the way key-value observation (KVO) works in iOS,
887
+ Warnings:
888
+ - Due to the way key-value observation (KVO) works in iOS,
765
889
  you currently CANNOT use AASM with an object you are observing. (Yes.. that's pretty sad).
890
+ - Due to RubyMotion Proc's lack of 'source_location' method, it may be harder
891
+ to find out the origin of a "cannot transition from" error. I would recommend using
892
+ the 'instance method symbol / string' way whenever possible when defining guardians and callbacks.
766
893
 
767
894
 
768
895
  ### Testing
@@ -38,6 +38,12 @@ module AASM
38
38
 
39
39
  AASM::StateMachine[self][state_machine_name] ||= AASM::StateMachine.new(state_machine_name)
40
40
 
41
+ # use a default despite the DSL configuration default.
42
+ # this is because configuration hasn't been setup for the AASM class but we are accessing a DSL option already for the class.
43
+ aasm_klass = options[:with_klass] || AASM::Base
44
+
45
+ raise ArgumentError, "The class #{aasm_klass} must inherit from AASM::Base!" unless aasm_klass.ancestors.include?(AASM::Base)
46
+
41
47
  @aasm ||= {}
42
48
  if @aasm[state_machine_name]
43
49
  # make sure to use provided options
@@ -46,7 +52,7 @@ module AASM
46
52
  end
47
53
  else
48
54
  # create a new base
49
- @aasm[state_machine_name] = AASM::Base.new(
55
+ @aasm[state_machine_name] = aasm_klass.new(
50
56
  self,
51
57
  state_machine_name,
52
58
  AASM::StateMachine[self][state_machine_name],
@@ -1,12 +1,13 @@
1
1
  module AASM
2
2
  class Base
3
3
 
4
- attr_reader :state_machine
4
+ attr_reader :klass,
5
+ :state_machine
5
6
 
6
7
  def initialize(klass, name, state_machine, options={}, &block)
7
8
  @klass = klass
8
9
  @name = name
9
- # @state_machine = @klass.aasm(@name).state_machine
10
+ # @state_machine = klass.aasm(@name).state_machine
10
11
  @state_machine = state_machine
11
12
  @state_machine.config.column ||= (options[:column] || default_column).to_sym
12
13
  # @state_machine.config.column = options[:column].to_sym if options[:column] # master
@@ -24,15 +25,23 @@ module AASM
24
25
  # use requires_new for nested transactions (in ActiveRecord)
25
26
  configure :requires_new_transaction, true
26
27
 
28
+ # use pessimistic locking (in ActiveRecord)
29
+ # true for FOR UPDATE lock
30
+ # string for a specific lock type i.e. FOR UPDATE NOWAIT
31
+ configure :requires_lock, false
32
+
27
33
  # set to true to forbid direct assignment of aasm_state column (in ActiveRecord)
28
34
  configure :no_direct_assignment, false
29
35
 
36
+ # allow a AASM::Base sub-class to be used for state machine
37
+ configure :with_klass, AASM::Base
38
+
30
39
  configure :enum, nil
31
40
 
32
41
  # make sure to raise an error if no_direct_assignment is enabled
33
42
  # and attribute is directly assigned though
34
43
  aasm_name = @name
35
- @klass.send :define_method, "#{@state_machine.config.column}=", ->(state_name) do
44
+ klass.send :define_method, "#{@state_machine.config.column}=", ->(state_name) do
36
45
  if self.class.aasm(:"#{aasm_name}").state_machine.config.no_direct_assignment
37
46
  raise AASM::NoDirectAssignmentError.new(
38
47
  'direct assignment of AASM column has been disabled (see AASM configuration for this class)'
@@ -63,19 +72,19 @@ module AASM
63
72
 
64
73
  # define a state
65
74
  def state(name, options={})
66
- @state_machine.add_state(name, @klass, options)
75
+ @state_machine.add_state(name, klass, options)
67
76
 
68
- if @klass.instance_methods.include?("#{name}?")
69
- warn "#{@klass.name}: The aasm state name #{name} is already used!"
77
+ if klass.instance_methods.include?("#{name}?")
78
+ warn "#{klass.name}: The aasm state name #{name} is already used!"
70
79
  end
71
80
 
72
81
  aasm_name = @name
73
- @klass.send :define_method, "#{name}?", ->() do
82
+ klass.send :define_method, "#{name}?", ->() do
74
83
  aasm(:"#{aasm_name}").current_state == :"#{name}"
75
84
  end
76
85
 
77
- unless @klass.const_defined?("STATE_#{name.upcase}")
78
- @klass.const_set("STATE_#{name.upcase}", name)
86
+ unless klass.const_defined?("STATE_#{name.upcase}")
87
+ klass.const_set("STATE_#{name.upcase}", name)
79
88
  end
80
89
  end
81
90
 
@@ -83,8 +92,8 @@ module AASM
83
92
  def event(name, options={}, &block)
84
93
  @state_machine.add_event(name, options, &block)
85
94
 
86
- if @klass.instance_methods.include?("may_#{name}?".to_sym)
87
- warn "#{@klass.name}: The aasm event name #{name} is already used!"
95
+ if klass.instance_methods.include?("may_#{name}?".to_sym)
96
+ warn "#{klass.name}: The aasm event name #{name} is already used!"
88
97
  end
89
98
 
90
99
  # an addition over standard aasm so that, before firing an event, you can ask
@@ -92,16 +101,16 @@ module AASM
92
101
  # on the transition will let this happen.
93
102
  aasm_name = @name
94
103
 
95
- @klass.send :define_method, "may_#{name}?", ->(*args) do
104
+ klass.send :define_method, "may_#{name}?", ->(*args) do
96
105
  aasm(:"#{aasm_name}").may_fire_event?(:"#{name}", *args)
97
106
  end
98
107
 
99
- @klass.send :define_method, "#{name}!", ->(*args, &block) do
108
+ klass.send :define_method, "#{name}!", ->(*args, &block) do
100
109
  aasm(:"#{aasm_name}").current_event = :"#{name}!"
101
110
  aasm_fire_event(:"#{aasm_name}", :"#{name}", {:persist => true}, *args, &block)
102
111
  end
103
112
 
104
- @klass.send :define_method, "#{name}", ->(*args, &block) do
113
+ klass.send :define_method, "#{name}", ->(*args, &block) do
105
114
  aasm(:"#{aasm_name}").current_event = :"#{name}"
106
115
  aasm_fire_event(:"#{aasm_name}", :"#{name}", {:persist => false}, *args, &block)
107
116
  end
@@ -145,7 +154,7 @@ module AASM
145
154
 
146
155
  # aasm.event(:event_name).human?
147
156
  def human_event_name(event) # event_name?
148
- AASM::Localizer.new.human_event_name(@klass, event)
157
+ AASM::Localizer.new.human_event_name(klass, event)
149
158
  end
150
159
 
151
160
  def states_for_select
@@ -15,9 +15,15 @@ module AASM
15
15
  # for ActiveRecord: use requires_new for nested transactions?
16
16
  attr_accessor :requires_new_transaction
17
17
 
18
+ # for ActiveRecord: use pessimistic locking
19
+ attr_accessor :requires_lock
20
+
18
21
  # forbid direct assignment in aasm_state column (in ActiveRecord)
19
22
  attr_accessor :no_direct_assignment
20
23
 
24
+ # allow a AASM::Base sub-class to be used for state machine
25
+ attr_accessor :with_klass
26
+
21
27
  attr_accessor :enum
22
28
  end
23
- end
29
+ end
@@ -53,13 +53,39 @@ module AASM::Core
53
53
 
54
54
  case code
55
55
  when Symbol, String
56
- arity = record.__send__(:method, code.to_sym).arity
57
- result = (arity == 0 ? record.__send__(code) : result = record.__send__(code, *args))
56
+ result = (record.__send__(:method, code.to_sym).arity == 0 ? record.__send__(code) : result = record.__send__(code, *args))
58
57
  failures << code unless result
59
58
  result
60
59
  when Proc
61
- result = (code.parameters.size == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
62
- failures << code.source_location.join('#') unless result
60
+ if code.respond_to?(:parameters)
61
+ # In Ruby's Proc, the 'arity' method is not a good condidate to know if
62
+ # we should pass the arguments or not, since its does return 0 even in
63
+ # presence of optional parameters.
64
+ result = (code.parameters.size == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
65
+
66
+ failures << code.source_location.join('#') unless result
67
+ else
68
+ # In RubyMotion's Proc, the 'parameter' method does not exists, however its
69
+ # 'arity' method works just like the one from Method, only returning 0 when
70
+ # there is no parameters whatsoever, optional or not.
71
+ result = (code.arity == 0 ? record.instance_exec(&code) : record.instance_exec(*args, &code))
72
+
73
+ # Sadly, RubyMotion's Proc does not define the method 'source_location' either.
74
+ failures << code unless result
75
+ end
76
+
77
+ result
78
+ when Class
79
+ arity = code.instance_method(:initialize).arity
80
+ if arity == 0
81
+ instance = code.new
82
+ elsif arity == 1
83
+ instance = code.new(record)
84
+ else
85
+ instance = code.new(record, *args)
86
+ end
87
+ result = instance.call
88
+ failures << instance.method(:call).source_location.join('#') unless result
63
89
  result
64
90
  when Array
65
91
  if options[:guard]
@@ -165,7 +165,15 @@ module AASM
165
165
  end
166
166
 
167
167
  begin
168
- success = options[:persist] ? self.class.transaction(:requires_new => requires_new?(state_machine_name)) { super } : super
168
+ success = if options[:persist]
169
+ self.class.transaction(:requires_new => requires_new?(state_machine_name)) do
170
+ lock!(requires_lock?(state_machine_name)) if requires_lock?(state_machine_name)
171
+ super
172
+ end
173
+ else
174
+ super
175
+ end
176
+
169
177
  if options[:persist] && success
170
178
  event.fire_callbacks(:after_commit, self, *args)
171
179
  event.fire_global_callbacks(:after_all_commits, self, *args)
@@ -184,6 +192,10 @@ module AASM
184
192
  AASM::StateMachine[self.class][state_machine_name].config.requires_new_transaction
185
193
  end
186
194
 
195
+ def requires_lock?(state_machine_name)
196
+ AASM::StateMachine[self.class][state_machine_name].config.requires_lock
197
+ end
198
+
187
199
  def aasm_validate_states
188
200
  AASM::StateMachine[self.class].keys.each do |state_machine_name|
189
201
  unless aasm_skipping_validations(state_machine_name)
@@ -82,7 +82,7 @@ module AASM
82
82
 
83
83
  def create_for_active_record(name)
84
84
  conditions = {
85
- "#{@klass.table_name}.#{@klass.aasm(@name).attribute_name}" => name.to_s
85
+ @klass.table_name => { @klass.aasm(@name).attribute_name => name.to_s }
86
86
  }
87
87
  if ActiveRecord::VERSION::MAJOR >= 3
88
88
  @klass.class_eval do
@@ -1,3 +1,3 @@
1
1
  module AASM
2
- VERSION = "4.8.0"
2
+ VERSION = "4.9.0"
3
3
  end
@@ -3,8 +3,7 @@ require 'rails/generators/named_base'
3
3
  module AASM
4
4
  module Generators
5
5
  class AASMGenerator < Rails::Generators::NamedBase
6
-
7
- source_root File.expand_path("../templates", __FILE__)
6
+ namespace "aasm"
8
7
  argument :column_name, type: :string, default: 'aasm_state'
9
8
 
10
9
  desc "Generates a model with the given NAME (if one does not exist) with aasm " <<
@@ -5,7 +5,7 @@ module ActiveRecord
5
5
  module Generators
6
6
  class AASMGenerator < ActiveRecord::Generators::Base
7
7
  include AASM::Generators::OrmHelpers
8
-
8
+ namespace "active_record:aasm"
9
9
  argument :column_name, type: :string, default: 'aasm_state'
10
10
 
11
11
  source_root File.expand_path("../templates", __FILE__)
@@ -16,11 +16,11 @@ module ActiveRecord
16
16
  else
17
17
  migration_template "migration.rb", "db/migrate/aasm_create_#{table_name}.rb"
18
18
  end
19
- end
19
+ end
20
20
 
21
21
  def generate_model
22
22
  invoke "active_record:model", [name], migration: false unless model_exists?
23
- end
23
+ end
24
24
 
25
25
  def inject_aasm_content
26
26
  content = model_contents
@@ -5,20 +5,20 @@ module Mongoid
5
5
  module Generators
6
6
  class AASMGenerator < Rails::Generators::NamedBase
7
7
  include AASM::Generators::OrmHelpers
8
-
8
+ namespace "mongoid:aasm"
9
9
  argument :column_name, type: :string, default: 'aasm_state'
10
10
 
11
11
  def generate_model
12
12
  invoke "mongoid:model", [name] unless model_exists?
13
- end
13
+ end
14
14
 
15
15
  def inject_aasm_content
16
16
  inject_into_file model_path, model_contents, after: "include Mongoid::Document\n" if model_exists?
17
- end
17
+ end
18
18
 
19
19
  def inject_field_types
20
20
  inject_into_file model_path, migration_data, after: "include Mongoid::Document\n" if model_exists?
21
- end
21
+ end
22
22
 
23
23
  def migration_data
24
24
  " field :#{column_name}"
@@ -17,24 +17,19 @@ ActiveRecord::Migration.suppress_messages do
17
17
  t.string "right"
18
18
  end
19
19
 
20
- ActiveRecord::Migration.create_table "validators", :force => true do |t|
21
- t.string "name"
22
- t.string "status"
23
- end
24
- ActiveRecord::Migration.create_table "multiple_validators", :force => true do |t|
25
- t.string "name"
26
- t.string "status"
20
+ %w(validators multiple_validators).each do |table_name|
21
+ ActiveRecord::Migration.create_table table_name, :force => true do |t|
22
+ t.string "name"
23
+ t.string "status"
24
+ end
27
25
  end
28
26
 
29
- ActiveRecord::Migration.create_table "transactors", :force => true do |t|
30
- t.string "name"
31
- t.string "status"
32
- t.integer "worker_id"
33
- end
34
- ActiveRecord::Migration.create_table "multiple_transactors", :force => true do |t|
35
- t.string "name"
36
- t.string "status"
37
- t.integer "worker_id"
27
+ %w(transactors no_lock_transactors lock_transactors lock_no_wait_transactors multiple_transactors).each do |table_name|
28
+ ActiveRecord::Migration.create_table table_name, :force => true do |t|
29
+ t.string "name"
30
+ t.string "status"
31
+ t.integer "worker_id"
32
+ end
38
33
  end
39
34
 
40
35
  ActiveRecord::Migration.create_table "workers", :force => true do |t|
@@ -0,0 +1,53 @@
1
+ class CustomAASMBase < AASM::Base
2
+ # A custom transiton that we want available across many AASM models.
3
+ def count_transitions!
4
+ klass.class_eval do
5
+ aasm :with_klass => CustomAASMBase do
6
+ after_all_transitions :increment_transition_count
7
+ end
8
+ end
9
+ end
10
+
11
+ # A custom annotation that we want available across many AASM models.
12
+ def requires_guards!
13
+ klass.class_eval do
14
+ attr_reader :authorizable_called,
15
+ :transition_count,
16
+ :fillable_called
17
+
18
+ def authorizable?
19
+ @authorizable_called = true
20
+ end
21
+
22
+ def fillable?
23
+ @fillable_called = true
24
+ end
25
+
26
+ def increment_transition_count
27
+ @transition_count ||= 0
28
+ @transition_count += 1
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ class SimpleCustomExample
35
+ include AASM
36
+
37
+ # Let's build an AASM state machine with our custom class.
38
+ aasm :with_klass => CustomAASMBase do
39
+ requires_guards!
40
+ count_transitions!
41
+
42
+ state :initialised, :initial => true
43
+ state :filled_out
44
+ state :authorised
45
+
46
+ event :fill_out do
47
+ transitions :from => :initialised, :to => :filled_out, :guard => :fillable?
48
+ end
49
+ event :authorise do
50
+ transitions :from => :filled_out, :to => :authorised, :guard => :authorizable?
51
+ end
52
+ end
53
+ end
@@ -26,6 +26,54 @@ private
26
26
 
27
27
  end
28
28
 
29
+ class NoLockTransactor < ActiveRecord::Base
30
+
31
+ belongs_to :worker
32
+
33
+ include AASM
34
+
35
+ aasm :column => :status do
36
+ state :sleeping, :initial => true
37
+ state :running
38
+
39
+ event :run do
40
+ transitions :to => :running, :from => :sleeping
41
+ end
42
+ end
43
+ end
44
+
45
+ class LockTransactor < ActiveRecord::Base
46
+
47
+ belongs_to :worker
48
+
49
+ include AASM
50
+
51
+ aasm :column => :status, requires_lock: true do
52
+ state :sleeping, :initial => true
53
+ state :running
54
+
55
+ event :run do
56
+ transitions :to => :running, :from => :sleeping
57
+ end
58
+ end
59
+ end
60
+
61
+ class LockNoWaitTransactor < ActiveRecord::Base
62
+
63
+ belongs_to :worker
64
+
65
+ include AASM
66
+
67
+ aasm :column => :status, requires_lock: 'FOR UPDATE NOWAIT' do
68
+ state :sleeping, :initial => true
69
+ state :running
70
+
71
+ event :run do
72
+ transitions :to => :running, :from => :sleeping
73
+ end
74
+ end
75
+ end
76
+
29
77
  class MultipleTransactor < ActiveRecord::Base
30
78
 
31
79
  belongs_to :worker
@@ -425,6 +425,39 @@ describe 'transitions with persistence' do
425
425
  expect(persistor).not_to be_sleeping
426
426
  end
427
427
 
428
+ describe 'pessimistic locking' do
429
+ let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
430
+
431
+ subject { transactor.run! }
432
+
433
+ context 'no lock' do
434
+ let(:transactor) { NoLockTransactor.create!(:name => 'no_lock_transactor', :worker => worker) }
435
+
436
+ it 'should not invoke lock!' do
437
+ expect(transactor).to_not receive(:lock!)
438
+ subject
439
+ end
440
+ end
441
+
442
+ context 'a default lock' do
443
+ let(:transactor) { LockTransactor.create!(:name => 'lock_transactor', :worker => worker) }
444
+
445
+ it 'should invoke lock! with true' do
446
+ expect(transactor).to receive(:lock!).with(true).and_call_original
447
+ subject
448
+ end
449
+ end
450
+
451
+ context 'a FOR UPDATE NOWAIT lock' do
452
+ let(:transactor) { LockNoWaitTransactor.create!(:name => 'lock_no_wait_transactor', :worker => worker) }
453
+
454
+ it 'should invoke lock! with FOR UPDATE NOWAIT' do
455
+ expect(transactor).to receive(:lock!).with('FOR UPDATE NOWAIT').and_call_original
456
+ subject
457
+ end
458
+ end
459
+ end
460
+
428
461
  describe 'transactions' do
429
462
  let(:worker) { Worker.create!(:name => 'worker', :status => 'sleeping') }
430
463
  let(:transactor) { Transactor.create!(:name => 'transactor', :worker => worker) }
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Custom AASM::Base' do
4
+ context 'when aasm_with invoked with SimpleCustomExample' do
5
+ let(:simple_custom) { SimpleCustomExample.new }
6
+
7
+ subject do
8
+ simple_custom.fill_out!
9
+ simple_custom.authorise
10
+ end
11
+
12
+ it 'has invoked authorizable?' do
13
+ expect { subject }.to change { simple_custom.authorizable_called }.from(nil).to(true)
14
+ end
15
+
16
+ it 'has invoked fillable?' do
17
+ expect { subject }.to change { simple_custom.fillable_called }.from(nil).to(true)
18
+ end
19
+
20
+ it 'has two transition counts' do
21
+ expect { subject }.to change { simple_custom.transition_count }.from(nil).to(2)
22
+ end
23
+ end
24
+
25
+ context 'when aasm_with invoked with non AASM::Base' do
26
+ subject do
27
+ Class.new do
28
+ include AASM
29
+
30
+ aasm :with_klass => String do
31
+ end
32
+ end
33
+ end
34
+
35
+ it 'should raise an ArgumentError' do
36
+ expect { subject }.to raise_error(ArgumentError, 'The class String must inherit from AASM::Base!')
37
+ end
38
+ end
39
+ end
@@ -300,3 +300,66 @@ describe AASM::Core::Transition, '- when executing the transition with an :after
300
300
  end
301
301
 
302
302
  end
303
+
304
+ describe AASM::Core::Transition, '- when executing the transition with a Class' do
305
+ let(:state_machine) { AASM::StateMachine.new(:name) }
306
+ let(:event) { AASM::Core::Event.new(:event, state_machine) }
307
+
308
+ class AfterTransitionClass
309
+ def initialize(record)
310
+ @record = record
311
+ end
312
+
313
+ def call
314
+ "from: #{@record.aasm.from_state} to: #{@record.aasm.to_state}"
315
+ end
316
+ end
317
+
318
+ class AfterTransitionClassWithArgs
319
+ def initialize(record, args)
320
+ @record = record
321
+ @args = args
322
+ end
323
+
324
+ def call
325
+ "arg1: #{@args[:arg1]}, arg2: #{@args[:arg2]}"
326
+ end
327
+ end
328
+
329
+ class AfterTransitionClassWithoutArgs
330
+ def call
331
+ 'success'
332
+ end
333
+ end
334
+
335
+ it 'passes the record to the initialize method on the class to give access to the from_state and to_state' do
336
+ opts = {:from => 'foo', :to => 'bar', :after => AfterTransitionClass}
337
+ transition = AASM::Core::Transition.new(event, opts)
338
+ obj = double('object', :aasm => AASM::InstanceBase.new('object'))
339
+
340
+ return_value = transition.execute(obj)
341
+
342
+ expect(return_value).to eq('from: foo to: bar')
343
+ end
344
+
345
+ it 'should pass args to the initialize method on the class if it accepts them' do
346
+ opts = {:from => 'foo', :to => 'bar', :after => AfterTransitionClassWithArgs}
347
+ st = AASM::Core::Transition.new(event, opts)
348
+ args = {:arg1 => '1', :arg2 => '2'}
349
+ obj = double('object', :aasm => 'aasm')
350
+
351
+ return_value = st.execute(obj, args)
352
+
353
+ expect(return_value).to eq('arg1: 1, arg2: 2')
354
+ end
355
+
356
+ it 'should NOT pass args if the call method of the class if it does NOT accept them' do
357
+ opts = {:from => 'foo', :to => 'bar', :after => AfterTransitionClassWithoutArgs}
358
+ st = AASM::Core::Transition.new(event, opts)
359
+ obj = double('object', :aasm => 'aasm')
360
+
361
+ return_value = st.execute(obj)
362
+
363
+ expect(return_value).to eq('success')
364
+ end
365
+ 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: 4.8.0
4
+ version: 4.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Barron
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-02-05 00:00:00.000000000 Z
13
+ date: 2016-02-22 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
@@ -215,6 +215,7 @@ files:
215
215
  - spec/models/sequel/sequel_multiple.rb
216
216
  - spec/models/sequel/sequel_simple.rb
217
217
  - spec/models/silencer.rb
218
+ - spec/models/simple_custom_example.rb
218
219
  - spec/models/simple_example.rb
219
220
  - spec/models/simple_multiple_example.rb
220
221
  - spec/models/state_machine_with_failed_event.rb
@@ -260,6 +261,7 @@ files:
260
261
  - spec/unit/readme_spec.rb
261
262
  - spec/unit/reloading_spec.rb
262
263
  - spec/unit/rspec_matcher_spec.rb
264
+ - spec/unit/simple_custom_example_spec.rb
263
265
  - spec/unit/simple_example_spec.rb
264
266
  - spec/unit/simple_multiple_example_spec.rb
265
267
  - spec/unit/state_spec.rb
@@ -286,7 +288,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
286
288
  version: '0'
287
289
  requirements: []
288
290
  rubyforge_project:
289
- rubygems_version: 2.4.5
291
+ rubygems_version: 2.2.2
290
292
  signing_key:
291
293
  specification_version: 4
292
294
  summary: State machine mixin for Ruby objects
@@ -362,6 +364,7 @@ test_files:
362
364
  - spec/models/sequel/sequel_multiple.rb
363
365
  - spec/models/sequel/sequel_simple.rb
364
366
  - spec/models/silencer.rb
367
+ - spec/models/simple_custom_example.rb
365
368
  - spec/models/simple_example.rb
366
369
  - spec/models/simple_multiple_example.rb
367
370
  - spec/models/state_machine_with_failed_event.rb
@@ -407,6 +410,7 @@ test_files:
407
410
  - spec/unit/readme_spec.rb
408
411
  - spec/unit/reloading_spec.rb
409
412
  - spec/unit/rspec_matcher_spec.rb
413
+ - spec/unit/simple_custom_example_spec.rb
410
414
  - spec/unit/simple_example_spec.rb
411
415
  - spec/unit/simple_multiple_example_spec.rb
412
416
  - spec/unit/state_spec.rb