aasm 4.8.0 → 4.9.0

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