transitions 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +18 -1
  3. data/.travis.yml +5 -0
  4. data/CHANGELOG.md +4 -0
  5. data/Gemfile +7 -6
  6. data/LICENSE.txt +21 -0
  7. data/README.md +9 -4
  8. data/Rakefile +9 -4
  9. data/bin/console +7 -0
  10. data/lib/active_model/transitions.rb +19 -12
  11. data/lib/transitions.rb +15 -15
  12. data/lib/transitions/event.rb +19 -15
  13. data/lib/transitions/machine.rb +23 -22
  14. data/lib/transitions/presenter.rb +2 -6
  15. data/lib/transitions/state.rb +10 -7
  16. data/lib/transitions/state_transition.rb +37 -6
  17. data/lib/transitions/version.rb +1 -1
  18. data/transitions.gemspec +23 -24
  19. metadata +39 -45
  20. data/.ruby-gemset +0 -1
  21. data/MIT-LICENSE.txt +0 -21
  22. data/test/active_record/test_active_record.rb +0 -326
  23. data/test/active_record/test_active_record_scopes.rb +0 -64
  24. data/test/active_record/test_active_record_timestamps.rb +0 -132
  25. data/test/active_record/test_custom_select.rb +0 -33
  26. data/test/event/test_event.rb +0 -72
  27. data/test/event/test_event_arguments.rb +0 -29
  28. data/test/event/test_event_being_fired.rb +0 -26
  29. data/test/event/test_event_checks.rb +0 -33
  30. data/test/helper.rb +0 -18
  31. data/test/machine/machine_template.rb +0 -27
  32. data/test/machine/test_available_states_listing.rb +0 -24
  33. data/test/machine/test_fire_event_machine.rb +0 -29
  34. data/test/machine/test_machine.rb +0 -66
  35. data/test/state/test_state.rb +0 -71
  36. data/test/state/test_state_predicate_method.rb +0 -32
  37. data/test/state_transition/test_state_transition.rb +0 -45
  38. data/test/state_transition/test_state_transition_event_failed_callback.rb +0 -36
  39. data/test/state_transition/test_state_transition_event_fired_callback.rb +0 -44
  40. data/test/state_transition/test_state_transition_guard_check.rb +0 -66
  41. data/test/state_transition/test_state_transition_on_transition_callback.rb +0 -48
  42. data/test/state_transition/test_state_transition_success_callback.rb +0 -49
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7e55aa8a7f0f38f8999f825fccd4d8d7fe0891c9
4
- data.tar.gz: d5fa39fd8b267931511f9f11e5820982ff8fdc80
3
+ metadata.gz: 0e0eecbbaa7a8c858df9799e370497da5c55691a
4
+ data.tar.gz: 2aeaec6cb95f64080a9a2a03a19ac1d9885db568
5
5
  SHA512:
6
- metadata.gz: 4f0b94f49e68756946a20fa98993e6bcd59ae9035647d2b8a6fc2d9475112f8ec5eec3c81a7695a6d1d95ca931dee872adafe1ebb91ca0b83778bf240bba39be
7
- data.tar.gz: 100ae8e228de1386b6c5ae2ea83be8ef1974126fb789a15ae121fdeeb88596cbc429671eb6f31bb72cf5b7fc1ad55ff1cffea2244cbfcba07d359415a98427c8
6
+ metadata.gz: 14bdc0e63e4cad7f5cf626022738509b61878964b1fd5c86eb8c87b6550ad45e8381af5d15333ca021a5c043edf521e8b4a2360d4d3f82d6587bc2537e756818
7
+ data.tar.gz: 8e5142a6a0448e8cb29b2b4bdd08cb8ac5bd8776ff9c2f16ae7bf900775cb4768fe70e7bbdf5d82f72ed0a006a95288350c3ba9910c4436d5702b585dbfe4c0e
data/.rubocop.yml CHANGED
@@ -1,4 +1,21 @@
1
1
  Metrics/LineLength:
2
2
  Max: 120
3
- Style/Documentation:
3
+ Documentation:
4
4
  Enabled: false
5
+ Lint/AssignmentInCondition:
6
+ Enabled: false
7
+ # For whatever reasons disabling the cops below via source comment does **not** work.
8
+ # This happens only for event.rb, disabling those cops in other files via comment works well.
9
+ Metrics/MethodLength:
10
+ Exclude:
11
+ - 'lib/transitions/event.rb'
12
+ Metrics/AbcSize:
13
+ Exclude:
14
+ - 'lib/transitions/event.rb'
15
+ Metrics/CyclomaticComplexity:
16
+ Exclude:
17
+ - 'lib/transitions/event.rb'
18
+ Style/ConditionalAssignment:
19
+ Exclude:
20
+ - 'lib/transitions/event.rb'
21
+ - 'lib/transitions/state.rb'
data/.travis.yml CHANGED
@@ -1,12 +1,17 @@
1
1
  sudo: false
2
2
  cache: bundler
3
3
  language: ruby
4
+ bundler_args: --without local_development
5
+ script:
6
+ - bundle exec rake
4
7
  rvm:
5
8
  - 2.1
6
9
  - 2.2
7
10
  - rbx-2
8
11
  - jruby
9
12
  matrix:
13
+ allow_failures:
14
+ - rvm: rbx-2
10
15
  fast_finish: true
11
16
  notifications:
12
17
  email:
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # 1.0.1
2
+
3
+ * (avyy) Fix @state_machine lookup for Presenter's methods
4
+
1
5
  # 1.0.0
2
6
 
3
7
  * (troessner) Only support ruby 2.1 and 2.2.
data/Gemfile CHANGED
@@ -2,11 +2,12 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'activerecord', '~> 3.2.14'
6
- gem 'activerecord-jdbcsqlite3-adapter', platforms: :jruby
7
-
8
- platforms :ruby do
9
- gem 'sqlite3'
5
+ group :local_development do
10
6
  gem 'pry'
11
- gem 'byebug'
7
+ gem 'sqlite3'
8
+
9
+ platforms :mri do
10
+ gem 'pry-byebug'
11
+ gem 'pry-stack_explorer'
12
+ end
12
13
  end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Timo Rößner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -57,7 +57,7 @@ class Product
57
57
  end
58
58
  ```
59
59
  In this example we assume that you are in a rails project using Bundler, which
60
- would automatically require `transitions`. If this is not the case for you you
60
+ would automatically require `transitions`. If this is not the case then you
61
61
  have to add
62
62
  ```ruby
63
63
  require 'transitions'
@@ -69,7 +69,7 @@ wherever you load your dependencies in your application.
69
69
 
70
70
  * You can only use one state machine per model. While in theory you can
71
71
  define two or more, this won't work as you would expect. Not supporting
72
- this was intentional, if you're interested in the ratione look up version
72
+ this was intentional, if you're interested in the rational look up version
73
73
  0.1.0 in the CHANGELOG.
74
74
 
75
75
  * Use symbols, not strings for declaring the state machine. Using strings is
@@ -106,13 +106,13 @@ the bang(!)-version will call `save!`. The `can_discontinue?` method will not
106
106
  modify state but instead returns a boolean letting you know if a given
107
107
  transition is possible.
108
108
 
109
- In addition, a `can_transition?` method is added to the object that expects one or more event names as arguments. This semi-verbose method name is used to avoid collission with [https://github.com/ryanb/cancan](the authorization gem CanCan).
109
+ In addition, a `can_transition?` method is added to the object that expects one or more event names as arguments. This semi-verbose method name is used to avoid collisions with [https://github.com/ryanb/cancan](the authorization gem CanCan).
110
110
  ```ruby
111
111
  >> Product.new.can_transition? :out_of_stock
112
112
  => true
113
113
  ```
114
114
 
115
- If you need to get all available transitions for current state you can simply call:
115
+ If you need to get all available transitions for the current state you can simply call:
116
116
  ```ruby
117
117
  >> Product.new.available_transitions
118
118
  => [:discontinued, :out_of_stock]
@@ -289,6 +289,11 @@ end
289
289
  Any arguments passed to the event method will be passed on to the `guard`
290
290
  predicate.
291
291
 
292
+ Note that guards will **not** raise on failure on their own. This means that if you want to
293
+ treat the failure of a guard exceptional you'll need to raise an exception yourself explicitly
294
+ in that guard (see [here](https://github.com/troessner/transitions/issues/149) for the
295
+ corresponding discussion).
296
+
292
297
 
293
298
  #### Timestamps
294
299
 
data/Rakefile CHANGED
@@ -1,14 +1,19 @@
1
- require 'bundler'
2
- Bundler::GemHelper.install_tasks
3
- Bundler.setup
1
+ require 'bundler/gem_tasks'
4
2
 
5
3
  require 'appraisal'
6
-
7
4
  require 'rake/testtask'
5
+ require 'rubocop/rake_task'
6
+
8
7
  Rake::TestTask.new(:test) do |test|
9
8
  test.libs = %w(lib test)
10
9
  test.pattern = 'test/**/test_*.rb'
11
10
  test.verbose = true
12
11
  end
13
12
 
13
+ RuboCop::RakeTask.new do |task|
14
+ task.options << '--display-cop-names'
15
+ task.patterns = ['lib/**/*.rb']
16
+ end
17
+
14
18
  task default: :test
19
+ task default: :rubocop
data/bin/console ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'transitions'
5
+
6
+ require 'pry'
7
+ Pry.start
@@ -3,7 +3,7 @@ module ActiveModel
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- class ::Transitions::Machine
6
+ ::Transitions::Machine.class_eval do
7
7
  unless method_defined?(:new_transitions_initialize) || method_defined?(:new_transitions_update)
8
8
  attr_reader :attribute_name
9
9
  alias_method :old_transitions_initialize, :initialize
@@ -64,28 +64,35 @@ module ActiveModel
64
64
  self[transitions_state_column_name] && self[transitions_state_column_name].to_sym
65
65
  end
66
66
 
67
+ #
68
+ # rubocop:disable Metrics/AbcSize
69
+ #
67
70
  def set_initial_state
68
- # In case we use a query with a custom select that excludes our state attribute name we need to skip the initialization below.
69
- if self.attribute_names.include?(transitions_state_column_name.to_s) && state_not_set?
70
- self[transitions_state_column_name] = self.class.get_state_machine.initial_state.to_s
71
- self.class.get_state_machine.state_index[self[transitions_state_column_name].to_sym].call_action(:enter, self)
72
- end
71
+ # In case we use a query with a custom select that excludes our state attribute
72
+ # name we need to skip the initialization below.
73
+ return unless attribute_names.include?(transitions_state_column_name.to_s) && state_not_set?
74
+ self[transitions_state_column_name] = self.class.get_state_machine.initial_state.to_s
75
+ self.class.get_state_machine.state_index[self[transitions_state_column_name].to_sym].call_action(:enter, self)
73
76
  end
74
77
 
75
78
  def state_presence
76
- unless self[transitions_state_column_name].present?
77
- errors.add(transitions_state_column_name, :presence)
78
- end
79
+ return if self[transitions_state_column_name].present?
80
+ errors.add(transitions_state_column_name, :presence)
79
81
  end
80
82
 
81
83
  def state_inclusion
82
- unless self.class.get_state_machine.states.map { |s| s.name.to_s }.include?(self[transitions_state_column_name].to_s)
83
- errors.add(transitions_state_column_name, :inclusion, value: self[transitions_state_column_name])
84
- end
84
+ return if state_included?
85
+ errors.add(transitions_state_column_name, :inclusion, value: self[transitions_state_column_name])
85
86
  end
86
87
 
87
88
  def state_not_set?
88
89
  self[transitions_state_column_name].nil?
89
90
  end
91
+
92
+ def state_included?
93
+ self.class.get_state_machine.states
94
+ .map { |s| s.name.to_s }
95
+ .include?(self[transitions_state_column_name].to_s)
96
+ end
90
97
  end
91
98
  end
data/lib/transitions.rb CHANGED
@@ -6,11 +6,13 @@ require 'transitions/state_transition'
6
6
  require 'transitions/version'
7
7
 
8
8
  module Transitions
9
- class InvalidTransition < StandardError; end
9
+ class InvalidTransition < StandardError; end
10
10
  class InvalidMethodOverride < StandardError; end
11
11
  include Presenter
12
12
 
13
13
  module ClassMethods
14
+ include Presenter
15
+
14
16
  def inherited(klass)
15
17
  super # Make sure we call other callbacks possibly defined upstream the ancestor chain.
16
18
  klass.state_machine = state_machine
@@ -26,37 +28,34 @@ module Transitions
26
28
  block ? @state_machine.update(options, &block) : @state_machine
27
29
  end
28
30
 
31
+ # rubocop:disable Style/AccessorMethodName
29
32
  def get_state_machine
30
33
  @state_machine
31
34
  end
32
-
33
- def available_states
34
- @state_machine.states.map(&:name).sort_by(&:to_s)
35
- end
36
-
37
- def available_events
38
- @state_machine.events.keys.sort
39
- end
40
35
  end
41
36
 
42
37
  def self.included(base)
43
38
  base.extend(ClassMethods)
44
39
  end
45
40
 
41
+ def get_state_machine
42
+ self.class.get_state_machine
43
+ end
44
+
46
45
  def update_current_state(new_state, persist = false)
47
- sm = self.class.get_state_machine
48
- ivar = sm.current_state_variable
46
+ ivar = get_state_machine.current_state_variable
49
47
 
50
48
  if Transitions.active_model_descendant?(self.class)
51
49
  write_state(new_state) if persist
52
- write_state_without_persistence(new_state) # TODO This seems like a duplicate, `write_new` already calls `write_state_without_persistence`.
50
+ # TODO: This seems like a duplicate, `write_new` already calls `write_state_without_persistence`.
51
+ write_state_without_persistence(new_state)
53
52
  end
54
53
 
55
54
  instance_variable_set(ivar, new_state)
56
55
  end
57
56
 
58
57
  def available_transitions
59
- self.class.get_state_machine.events_for(current_state)
58
+ get_state_machine.events_for(current_state)
60
59
  end
61
60
 
62
61
  def can_transition?(*events)
@@ -70,7 +69,7 @@ module Transitions
70
69
  end
71
70
 
72
71
  def current_state
73
- sm = self.class.get_state_machine
72
+ sm = get_state_machine
74
73
  ivar = sm.current_state_variable
75
74
 
76
75
  value = instance_variable_get(ivar)
@@ -84,6 +83,7 @@ module Transitions
84
83
  end
85
84
 
86
85
  def self.active_model_descendant?(klazz)
87
- defined?(ActiveModel) && klazz.included_modules.include?(ActiveModel::Dirty) # Checking directly for "ActiveModel" wouldn't work so we use some arbitrary module close to it.
86
+ # Checking directly for "ActiveModel" wouldn't work so we use some arbitrary module close to it.
87
+ defined?(ActiveModel) && klazz.included_modules.include?(ActiveModel::Dirty)
88
88
  end
89
89
  end
@@ -1,9 +1,12 @@
1
1
  module Transitions
2
+ # rubocop:disable Metrics/ClassLength
2
3
  class Event
3
4
  attr_reader :name, :success, :timestamp
4
5
 
5
6
  def initialize(machine, name, options = {}, &block)
6
- @machine, @name, @transitions = machine, name, []
7
+ @machine = machine
8
+ @name = name
9
+ @transitions = []
7
10
  if machine
8
11
  machine.klass.send(:define_method, "#{name}!") do |*args|
9
12
  machine.fire_event(name, self, true, *args)
@@ -34,12 +37,12 @@ module Transitions
34
37
  next_state = nil
35
38
  transitions.each do |transition|
36
39
  next if to_state && !Array(transition.to).include?(to_state)
37
- if transition.executable?(obj, *args)
38
- next_state = to_state || Array(transition.to).first
39
- transition.execute(obj, *args)
40
- update_event_timestamp(obj, next_state) if timestamp_defined?
41
- break
42
- end
40
+ next unless transition.executable?(obj, *args)
41
+
42
+ next_state = to_state || Array(transition.to).first
43
+ transition.execute(obj, *args)
44
+ update_event_timestamp(obj, next_state) if timestamp_defined?
45
+ break
43
46
  end
44
47
  # Update timestamps on obj if a timestamp has been defined
45
48
  next_state
@@ -53,11 +56,11 @@ module Transitions
53
56
  @transitions.select { |t| t.from? state }.any? { |t| t.executable?(obj, *args) }
54
57
  end
55
58
 
56
- def ==(event)
57
- if event.is_a? Symbol
58
- name == event
59
+ def ==(other)
60
+ if other.is_a? Symbol
61
+ name == other
59
62
  else
60
- name == event.name
63
+ name == other.name
61
64
  end
62
65
  end
63
66
 
@@ -100,8 +103,8 @@ module Transitions
100
103
 
101
104
  # If @timestamp is true, try a default timestamp name
102
105
  def default_timestamp_name(obj, next_state)
103
- at_name = '%s_at' % next_state
104
- on_name = '%s_on' % next_state
106
+ at_name = "#{next_state}_at"
107
+ on_name = "#{next_state}_on"
105
108
  case
106
109
  when obj.respond_to?(at_name) then at_name
107
110
  when obj.respond_to?(on_name) then on_name
@@ -128,12 +131,13 @@ module Transitions
128
131
  when Proc
129
132
  callback_names
130
133
  when Symbol
131
- lambda { |record| record.send(callback_names) }
134
+ ->(record) { record.send(callback_names) }
132
135
  end
133
136
  end
134
137
 
135
138
  def error_message_for_invalid_transitions(obj)
136
- "Can't fire event `#{name}` in current state `#{obj.current_state}` for `#{obj.class.name}` #{obj.class < ActiveRecord::Base && obj.persisted? ? "with ID #{obj.id} " : nil}"
139
+ "Can't fire event `#{name}` in current state `#{obj.current_state}` for `#{obj.class.name}`"\
140
+ " #{obj.class < ActiveRecord::Base && obj.persisted? ? "with ID #{obj.id} " : nil}"
137
141
  end
138
142
  end
139
143
  end
@@ -5,7 +5,10 @@ module Transitions
5
5
  attr_reader :klass, :auto_scopes
6
6
 
7
7
  def initialize(klass, options = {}, &block)
8
- @klass, @states, @state_index, @events = klass, [], {}, {}
8
+ @klass = klass
9
+ @states = []
10
+ @state_index = {}
11
+ @events = {}
9
12
  update(options, &block)
10
13
  end
11
14
 
@@ -21,7 +24,9 @@ module Transitions
21
24
  self
22
25
  end
23
26
 
24
- # TODO There is still way to much parameter passing around going on down below.
27
+ #
28
+ # rubocop:disable Metrics/MethodLength
29
+ #
25
30
  def fire_event(event, record, persist, *args)
26
31
  handle_state_exit_callback record
27
32
  if new_state = transition_to_new_state(record, event, *args)
@@ -35,12 +40,9 @@ module Transitions
35
40
  return false
36
41
  end
37
42
  rescue => e
38
- if record.respond_to?(:event_failed)
39
- record.send(:event_failed, event)
40
- return false
41
- else
42
- raise e
43
- end
43
+ raise e unless record.respond_to?(:event_failed)
44
+ record.send(:event_failed, event)
45
+ return false
44
46
  end
45
47
 
46
48
  def events_for(state)
@@ -49,7 +51,7 @@ module Transitions
49
51
  end
50
52
 
51
53
  def current_state_variable
52
- # TODO Refactor me away.
54
+ # TODO: Refactor me away.
53
55
  :@current_state
54
56
  end
55
57
 
@@ -68,9 +70,8 @@ module Transitions
68
70
  end
69
71
 
70
72
  def handle_event_fired_callback(record, new_state, event)
71
- if record.respond_to?(:event_fired, true)
72
- record.send(:event_fired, record.current_state, new_state, event)
73
- end
73
+ return unless record.respond_to?(:event_fired, true)
74
+ record.send(:event_fired, record.current_state, new_state, event)
74
75
  end
75
76
 
76
77
  def handle_event_success_callback(record, event)
@@ -78,18 +79,16 @@ module Transitions
78
79
  end
79
80
 
80
81
  def handle_event_failed_callback(record, event)
81
- if record.respond_to?(:event_failed, true)
82
- record.send(:event_failed, event)
83
- end
82
+ return unless record.respond_to?(:event_failed, true)
83
+ record.send(:event_failed, event)
84
84
  end
85
85
 
86
86
  def state(name, options = {})
87
- unless @state_index.key? name # Just ignore duplicates
88
- state = State.new(name, machine: self)
89
- state.update options
90
- @state_index[name] = state
91
- @states << state
92
- end
87
+ return if @state_index.key?(name) # Just ignore duplicates
88
+ state = State.new(name, machine: self)
89
+ state.update options
90
+ @state_index[name] = state
91
+ @states << state
93
92
  end
94
93
 
95
94
  def event(name, options = {}, &block)
@@ -100,7 +99,9 @@ module Transitions
100
99
  @states.each do |state|
101
100
  state_name = state.name.to_s
102
101
  if @klass.respond_to?(state_name)
103
- fail InvalidMethodOverride, "Transitions: Can not define scope `#{state_name}` because there is already an equally named method defined - either rename the existing method or the state."
102
+ fail InvalidMethodOverride,
103
+ "Transitions: Can not define scope `#{state_name}` because there is already"\
104
+ 'an equally named method defined - either rename the existing method or the state.'
104
105
  end
105
106
  @klass.scope state_name, -> { @klass.where(@klass.state_machine.attribute_name => state_name) }
106
107
  end