simple_states 1.1.0.rc11 → 2.0.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: 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