simple_states 1.1.0.rc11 → 2.0.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: a6a512e5ed006c4dfb377e88a4797cd88d9ca32a
4
- data.tar.gz: 89037f2bdec8d744e84d3cb3206c875b03816641
3
+ metadata.gz: 627c1d0bd3ec1db9f71afc51615e7a1c5d8ee194
4
+ data.tar.gz: ca2532ece2327f549c0c113dcd09155f4df8b578
5
5
  SHA512:
6
- metadata.gz: dc32ade2ccd98a78da1403536cc076bb609df4544740bd883f693d9d5386ed1dace6d56e0245eccf4e107e84429afddaa40d5307dc636400edf8d99f1378476d
7
- data.tar.gz: 53065cfa84dbc9e4a4921f4477d2ce4f0c30cedcd5228e9037c6c392a683ee4e7d2de8263140786325c326c5f680d981272611793977cfd150fc6b740ca4a5e0
6
+ metadata.gz: e8295626ea865b2d3e1bfdac8e6ef2e4a5c50100ac3c0168fb68e183a03a3a8d591dfc578713d6ecae618a6ccdd9973f329068fbddde3f1039fd5ab95fd29939
7
+ data.tar.gz: c6a2062e68a1f7814a3b40634eb7aefcdb3e567bc444dd95b15c8ec2b4adcca3e03526c01829946104572f0ff3fbd29ffd9dc73aa5c38da6fad7c38fa11a8718
data/Gemfile CHANGED
@@ -1,7 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gemspec
4
-
5
3
  group :test do
6
- gem 'minitest'
4
+ gem 'rspec'
5
+ gem 'mocha'
7
6
  end
@@ -1,37 +1,31 @@
1
- PATH
2
- remote: .
3
- specs:
4
- simple_states (1.1.0.rc9)
5
- activesupport
6
-
7
1
  GEM
8
2
  remote: https://rubygems.org/
9
3
  specs:
10
- activesupport (4.2.4)
11
- i18n (~> 0.7)
12
- json (~> 1.7, >= 1.7.7)
13
- minitest (~> 5.1)
14
- thread_safe (~> 0.3, >= 0.3.4)
15
- tzinfo (~> 1.1)
16
- i18n (0.7.0)
17
- json (1.8.3)
18
- metaclass (0.0.1)
19
- minitest (5.8.0)
20
- mocha (0.14.0)
4
+ diff-lcs (1.2.5)
5
+ metaclass (0.0.4)
6
+ mocha (1.1.0)
21
7
  metaclass (~> 0.0.1)
22
- rake (0.9.6)
23
- test_declarative (0.0.5)
24
- thread_safe (0.3.5)
25
- tzinfo (1.2.2)
26
- thread_safe (~> 0.1)
8
+ rspec (3.3.0)
9
+ rspec-core (~> 3.3.0)
10
+ rspec-expectations (~> 3.3.0)
11
+ rspec-mocks (~> 3.3.0)
12
+ rspec-core (3.3.2)
13
+ rspec-support (~> 3.3.0)
14
+ rspec-expectations (3.3.1)
15
+ diff-lcs (>= 1.2.0, < 2.0)
16
+ rspec-support (~> 3.3.0)
17
+ rspec-mocks (3.3.2)
18
+ diff-lcs (>= 1.2.0, < 2.0)
19
+ rspec-support (~> 3.3.0)
20
+ rspec-support (3.3.0)
27
21
 
28
22
  PLATFORMS
29
23
  java
30
24
  ruby
31
25
 
32
26
  DEPENDENCIES
33
- minitest
34
27
  mocha
35
- rake (~> 0.9.2)
36
- simple_states!
37
- test_declarative
28
+ rspec
29
+
30
+ BUNDLED WITH
31
+ 1.10.6
@@ -1,66 +1,42 @@
1
- require 'active_support/core_ext/object/try'
1
+ require 'simple_states/states'
2
2
 
3
3
  module SimpleStates
4
- class TransitionException < RuntimeError; end
5
-
6
- autoload :Event, 'simple_states/event'
7
- autoload :States, 'simple_states/states'
4
+ class Error < RuntimeError; end
8
5
 
9
6
  class << self
10
- def included(base)
11
- base.extend(SimpleStates::ClassMethods)
12
- define_accessors(base, :state_names, :state_options, :initial_state, :events)
13
- set_defaults(base)
14
- end
15
-
16
- def define_accessors(base, *names)
17
- base.singleton_class.send(:attr_accessor, *names)
18
- base.public_class_method(*names + names.map { |name| "#{name}=".to_sym })
19
- end
20
-
21
- def set_defaults(base)
22
- base.after_initialize(:init_state) if base.respond_to?(:after_initialize)
23
- base.initial_state = :created
24
- base.state_names = []
25
- base.state_options = {}
26
- base.events = []
7
+ def included(const)
8
+ states = const.const_set(:States, States.new)
9
+ const.send(:prepend, states) if const.respond_to?(:prepend)
10
+ const.extend(ClassMethods)
11
+ const.initial_state = :created
12
+ const.after_initialize(:init_state) if const.respond_to?(:after_initialize)
27
13
  end
28
14
  end
29
15
 
30
16
  module ClassMethods
31
- def new(*)
32
- super.tap { |object| States.init(object) }
33
- end
17
+ attr_accessor :initial_state
34
18
 
35
- def allocate
36
- super.tap { |object| States.init(object) }
37
- end
38
-
39
- def states(*args)
40
- options = args.last.is_a?(Hash) ? args.pop : {}
41
- self.initial_state = options.delete(:initial).to_sym if options.key?(:initial)
42
- self.state_options = options
43
- add_states(*[self.initial_state].concat(args))
19
+ def new(*)
20
+ super.tap { |object| object.init_state }
44
21
  end
45
22
 
46
- def add_states(*names)
47
- self.state_names = self.state_names.concat(names.compact.map(&:to_sym)).uniq
23
+ def event(name, opts = {})
24
+ method = name == :all ? :update_events : :define_event
25
+ self::States.send(method, name, opts)
48
26
  end
49
27
 
50
- def event(name, options = {})
51
- add_states(options[:to], *options[:from])
52
- self.events << [name, options]
28
+ def state?(state)
29
+ states.include?(state)
53
30
  end
54
31
 
55
- def states_module
56
- const_defined?(*args) ? self::StatesProxy : const_set(:StatesProxy, Module.new)
32
+ def states
33
+ [initial_state] + self::States.states
57
34
  end
58
35
  end
59
36
 
60
- attr_reader :past_states
61
-
62
37
  def init_state
63
- self.state = self.class.initial_state if state.nil?
38
+ singleton_class.send(:include, self.class::States) unless self.class.respond_to?(:prepend)
39
+ self.state = self.class.initial_state if self.state.nil?
64
40
  end
65
41
 
66
42
  def state=(state)
@@ -68,23 +44,27 @@ module SimpleStates
68
44
  end
69
45
 
70
46
  def state
71
- super.try(:to_sym)
47
+ state = super
48
+ state.to_sym if state
72
49
  end
73
50
 
74
- def reset_state
75
- self.state = self.class.initial_state
76
- self.class.events.map { |*args| Event.new(*args).reset(self) }
51
+ def state?(state)
52
+ self.state.to_sym == state.to_sym
77
53
  end
78
54
 
79
- def past_states
80
- @past_states ||= []
55
+ def reset_state
56
+ self.state = self.class.initial_state
57
+ self.class::States.events.map { |_, event| event.reset(self) }
81
58
  end
82
59
 
83
- def state?(state, include_past = false)
84
- include_past ? was_state?(state) : self.state.try(:to_sym) == state.to_sym
60
+ def respond_to?(name)
61
+ state = name.to_s[0..-2].to_sym
62
+ name.to_s[-1] == '?' && self.class.state?(state) || super
85
63
  end
86
64
 
87
- def was_state?(state)
88
- past_states.concat([self.state.try(:to_sym)]).compact.include?(state.to_sym)
65
+ def method_missing(name, *args)
66
+ state = name.to_s[0..-2].to_sym
67
+ return super unless name.to_s[-1] == '?' && self.class.state?(state)
68
+ state?(state)
89
69
  end
90
70
  end
@@ -1,112 +1,81 @@
1
- require 'active_support/core_ext/module/delegation'
2
-
3
1
  module SimpleStates
4
- class Event
5
- attr_accessor :name, :options
6
-
7
- def initialize(name, options = {})
8
- @name = name
9
- @options = options
2
+ class Event < Struct.new(:name, :opts)
3
+ MSGS = {
4
+ invalid_state: 'If multiple target states are defined, then a valid target state must be passed as an attribute ({ state: :state }). %p given, known states: %p.',
5
+ unknown_state: 'Unknown state %p for %p for event %p. Known states are: %p'
6
+ }
7
+
8
+ def call(obj, data, opts)
9
+ return false if not ordered?(obj, data) or not applies?(obj, data)
10
+
11
+ run_callbacks(:before, obj, data)
12
+ set_attrs(obj, data)
13
+ yield
14
+ obj.save! if opts[:save]
15
+
16
+ raise_unknown_state(obj, data) unless known_state?(obj)
17
+ run_callbacks(:after, obj, data)
18
+ obj.save! if opts[:save]
19
+ true
10
20
  end
11
21
 
12
- def call(object, *args)
13
- return false if skip?(object, args) || !set_state?(object, args)
14
-
15
- raise_invalid_transition(object) unless can_transition?(object)
16
- run_callbacks(object, :before, args)
17
- set_state(object, args)
18
-
19
- yield.tap do
20
- raise_unknown_target_state(object) unless known_target_state?(object)
21
- run_callbacks(object, :after, args)
22
- object.save! if save?
23
- end
22
+ def reset(obj)
23
+ Array(opts[:to]).each { |state| set_attr(obj, :"#{state}_at", nil) }
24
24
  end
25
25
 
26
- def reset(object)
27
- write_attr(object, :"#{target_state}_at", nil)
28
- end
29
-
30
- protected
31
-
32
- def save?
33
- options[:save]
34
- end
35
-
36
- def skip?(object, args)
37
- result = false
38
- result ||= !send_methods(object, options[:if], args) if options[:if]
39
- result ||= send_methods(object, options[:unless], args) if options[:unless]
40
- result
41
- end
42
-
43
- def can_transition?(object)
44
- !options[:from] || object.state && Array(options[:from]).include?(object.state.to_sym)
45
- end
46
-
47
- def run_callbacks(object, type, args)
48
- object.save! if save?
49
- send_methods(object, options[type], args)
50
- end
51
-
52
- def set_state(object, args)
53
- data = args.last.is_a?(Hash) ? args.last : {}
54
- state = data[:state].try(:to_sym) || target_state
55
- object.past_states << object.state.to_sym if object.state
56
- object.state = state.to_sym
57
- write_attrs(object, data)
58
- end
26
+ private
59
27
 
60
- def write_attrs(object, data)
61
- data = { :"#{target_state}_at" => Time.now.utc }.merge(data)
62
- data.each { |key, value| write_attr(object, key, value) }
28
+ def run_callbacks(type, obj, data)
29
+ send_methods(opts[type], obj, data)
63
30
  end
64
31
 
65
- def write_attr(object, key, value)
66
- object.send(:"#{key}=", value) if object.respond_to?(:"#{key}=") && object.respond_to?(key) && object.send(key).nil?
32
+ def set_attrs(obj, data)
33
+ attrs = { :"#{target_state(data)}_at" => Time.now.utc }.merge(data)
34
+ attrs.each { |key, value| set_attr(obj, key, value) }
35
+ obj.state = target_state(data)
67
36
  end
68
37
 
69
- def set_state?(object, args)
70
- data = args.last.is_a?(Hash) ? args.last : {}
71
- state = data[:state].try(:to_sym) || target_state
72
- return true unless object.class.state_options[:ordered]
73
- states = object.class.state_names
74
- lft, rgt = states.index(object.state.try(:to_sym)), states.index(state)
75
- lft.nil? || rgt.nil? || lft <= rgt
38
+ def set_attr(obj, key, value)
39
+ obj.send(:"#{key}=", value) if obj.respond_to?(:"#{key}=")
76
40
  end
77
41
 
78
- def target_state
79
- options[:to] || "#{name}ed".sub(/eed$/, 'ed').to_sym
42
+ def ordered?(obj, data)
43
+ states = obj.class.states
44
+ states.index(obj.state).to_i <= states.index(target_state(data)).to_i
80
45
  end
81
46
 
82
- def send_methods(object, methods, args)
83
- Array(methods).inject(false) { |result, method| result | send_method(object, method, args) } if methods
47
+ def applies?(obj, data)
48
+ result = opts[:if].nil? || send_methods(opts[:if], obj, data)
49
+ result and opts[:unless].nil? || !send_methods(opts[:unless], obj, data)
84
50
  end
85
51
 
86
- def send_method(object, method, args)
87
- arity = self.arity(object, method)
88
- args = [name].concat(args)
89
- object.send method, *args.slice(0, arity < 0 ? args.size : arity)
52
+ def target_state(data)
53
+ to = Array(opts[:to])
54
+ return to.first if to.size == 1
55
+ state = data[:state].to_sym if data[:state]
56
+ to.include?(state) ? state : raise_invalid_state(data)
90
57
  end
91
58
 
92
- def arity(object, method)
93
- object.class.instance_method(method).arity rescue 0
59
+ def send_methods(names, obj, data)
60
+ Array(names).inject(false) do |result, name|
61
+ result | send_method(name, obj, data)
62
+ end
94
63
  end
95
64
 
96
- def now
97
- Time.respond_to?(:zone) && Time.zone ? Time.zone.now : Time.now.utc
65
+ def send_method(name, obj, data)
66
+ obj.send(name, *[self.name, data].slice(0, obj.method(name).arity.abs))
98
67
  end
99
68
 
100
- def known_target_state?(object)
101
- object.state && object.class.state_names.include?(object.state.to_sym)
69
+ def known_state?(obj)
70
+ obj.class.states.include?(obj.state)
102
71
  end
103
72
 
104
- def raise_invalid_transition(object)
105
- raise TransitionException, "#{object.inspect} can not receive event #{name.inspect} while in state #{object.state.inspect}."
73
+ def raise_invalid_state(data)
74
+ raise Error, MSGS[:invalid_state] % [data[:state], Array(opts[:to])]
106
75
  end
107
76
 
108
- def raise_unknown_target_state(object)
109
- raise TransitionException, "unknown target state #{object.state.inspect} for #{object.inspect} for event #{name.inspect}. known states are #{object.class.states.inspect}"
77
+ def raise_unknown_state(obj, data)
78
+ raise Error, MSGS[:unknown_state] % [obj.state, obj, name, obj.class.states]
110
79
  end
111
80
  end
112
81
  end
@@ -0,0 +1,7 @@
1
+ module SimpleStates
2
+ module Helpers
3
+ def to_past(string)
4
+ "#{string}ed".sub(/eed$/, 'ed')
5
+ end
6
+ end
7
+ end
@@ -1,65 +1,41 @@
1
+ require 'simple_states/event'
2
+ require 'simple_states/helpers'
3
+
1
4
  module SimpleStates
2
5
  class States < Module
3
- class << self
4
- def init(object)
5
- object.class.state_names = [object.class.initial_state] if object.class.state_names.empty?
6
- respond_to?(:prepend) ? prepend_module(object.class) : include_module(object)
7
- object.init_state unless object.singleton_class.respond_to?(:after_initialize)
8
- end
9
-
10
- def prepend_module(const)
11
- const.prepend(module_for(const)) unless const.const_defined?(:StatesProxy)
12
- end
13
-
14
- def include_module(object)
15
- object.singleton_class.send(:include, module_for(object.class))
16
- end
17
-
18
- def module_for(const)
19
- args = [:StatesProxy].concat(const.method(:const_defined?).arity != 1 ? [false] : [])
20
- const.const_defined?(*args) ? const::StatesProxy : const.const_set(:StatesProxy, create_module(const))
21
- end
22
-
23
- def create_module(const)
24
- new.tap do |mod|
25
- merge_events(const.events).each { |event| define_event(mod, event) }
26
- const.state_names.each { |name| define_state(mod, name) }
27
- end
28
- end
29
-
30
- def define_event(const, (name, options))
31
- const.send(:define_method, name) do |*args|
32
- event = args.first.is_a?(Event) ? args.shift : Event.new(name, options)
33
- event.call(self, *args) { defined?(super) ? super(*args) : true }
34
- end
6
+ include Helpers
35
7
 
36
- const.send(:define_method, :"#{name}!") do |*args|
37
- send(name, Event.new(name, options.merge(save: true)), *args)
38
- end
39
- end
8
+ def events
9
+ @events ||= {}
10
+ end
40
11
 
41
- def define_state(const, state)
42
- const.send(:define_method, :"#{state}?") do |*args|
43
- defined?(super) ? super() : state?(state, *args)
44
- end
12
+ def states
13
+ events.values.map { |event| event.opts[:to] }.flatten.compact
14
+ end
45
15
 
46
- const.send(:define_method, :"was_#{state}?") do |*args|
47
- defined?(super) ? super() : was_state?(state, *args)
16
+ def define_event(name, opts)
17
+ events[name] = Event.new(name, { to: to_past(name).to_sym }.merge(opts))
18
+
19
+ send(:define_method, name) do |data = {}, opts = {}|
20
+ self.class::States.events[name].call(self, data, opts) do
21
+ if method(name).respond_to?(:super_method)
22
+ supa = method(name).super_method
23
+ supa.call(*[name, data].slice(0, supa.arity.abs)) if supa
24
+ elsif defined?(super)
25
+ super(name, data)
26
+ end
48
27
  end
49
28
  end
50
29
 
51
- def merge_events(events)
52
- return events unless other_ix = events.index { |event| event.first == :all }
53
- other = events.slice!(other_ix)
54
- events.each_with_index do |event, ix|
55
- merge_event(event, other, ix < other_ix)
56
- end
57
- merge_events(events)
30
+ send(:define_method, :"#{name}!") do |data = {}|
31
+ send(name, data, save: true)
58
32
  end
33
+ end
59
34
 
60
- def merge_event(event, other, append = true)
61
- other.last.each do |key, value|
62
- event.last[key] = [event.last[key]].send(append ? :push : :unshift, value).compact.flatten
35
+ def update_events(_, opts)
36
+ events.values.each do |event|
37
+ opts.each do |key, value|
38
+ event.opts[key] = Array(event.opts[key]).concat(Array(value))
63
39
  end
64
40
  end
65
41
  end
@@ -1,3 +1,3 @@
1
1
  module SimpleStates
2
- VERSION = '1.1.0.rc11'
2
+ VERSION = '2.0.0'
3
3
  end
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.name = "simple_states"
8
8
  s.version = SimpleStates::VERSION
9
9
  s.authors = ["Sven Fuchs"]
10
- s.email = "svenfuchs@artweb-design.de"
10
+ s.email = "me@svenfuchs.com"
11
11
  s.homepage = "https://github.com/svenfuchs/simple_states"
12
12
  s.licenses = ['MIT']
13
13
  s.summary = "[summary]"
@@ -17,10 +17,4 @@ Gem::Specification.new do |s|
17
17
  s.platform = Gem::Platform::RUBY
18
18
  s.require_path = 'lib'
19
19
  s.rubyforge_project = '[none]'
20
-
21
- s.add_dependency 'activesupport'
22
-
23
- s.add_development_dependency 'rake', '~> 0.9.2'
24
- s.add_development_dependency 'test_declarative'
25
- s.add_development_dependency 'mocha'
26
20
  end
metadata CHANGED
@@ -1,73 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_states
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0.rc11
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-10-10 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: activesupport
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: 0.9.2
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: 0.9.2
41
- - !ruby/object:Gem::Dependency
42
- name: test_declarative
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: mocha
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - ">="
60
- - !ruby/object:Gem::Version
61
- version: '0'
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - ">="
67
- - !ruby/object:Gem::Version
68
- version: '0'
11
+ date: 2015-11-01 00:00:00.000000000 Z
12
+ dependencies: []
69
13
  description: "[description]"
70
- email: svenfuchs@artweb-design.de
14
+ email: me@svenfuchs.com
71
15
  executables: []
72
16
  extensions: []
73
17
  extra_rdoc_files: []
@@ -80,6 +24,7 @@ files:
80
24
  - Rakefile
81
25
  - lib/simple_states.rb
82
26
  - lib/simple_states/event.rb
27
+ - lib/simple_states/helpers.rb
83
28
  - lib/simple_states/states.rb
84
29
  - lib/simple_states/version.rb
85
30
  - simple_states.gemspec
@@ -98,9 +43,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
98
43
  version: '0'
99
44
  required_rubygems_version: !ruby/object:Gem::Requirement
100
45
  requirements:
101
- - - ">"
46
+ - - ">="
102
47
  - !ruby/object:Gem::Version
103
- version: 1.3.1
48
+ version: '0'
104
49
  requirements: []
105
50
  rubyforge_project: "[none]"
106
51
  rubygems_version: 2.4.5