transitions 1.0.0 → 1.0.1

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