ex_machina 0.0.1 → 0.0.2

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: 65774af491192a74ddda0683c63af42d4de122a1
4
- data.tar.gz: 8e22de0cc6d23b9d20dc755e3dbab298efd2a9e4
3
+ metadata.gz: bc8cb9dc7e92353478947c8c9a88c166b5c02061
4
+ data.tar.gz: b612897d0b21304de584ea98a3801d6e7a76ea35
5
5
  SHA512:
6
- metadata.gz: e50f7e90615c99ef72b6bddfecffdfcfc4df135d8f550ff0656092e1398eec8fc975c725a7110204c19e7294c134be5724273d26c2ba3c5c11c0c54932bce341
7
- data.tar.gz: 77e6132fe89cb046378a284e77147529fbf5114c71a99e3f07a3a9e3b6b51e4c646c290023fd5a30b44c7ba9dd216f1757a22fa7ed62c6426e746f923f6f7083
6
+ metadata.gz: 25b5ef8875fb3de5cf84bc7d1ae856c1345cae080deef13a3d04442be5106154aaf915b3682846876323b5fd229ecbe81002d5653637be037d98a198f10dc057
7
+ data.tar.gz: e60ebe4a9de753d450a408b00302771c333e044df90d76c6305ced6d9a8b46a9303ede4086456a55afc5a3ba91c0d907d923bcc241df986f487e0ba8b13cc31d
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ *.gem
data/README.md CHANGED
@@ -1,8 +1,15 @@
1
+ [![wercker status](https://app.wercker.com/status/32429e43b793f95c450ddd2a89efdb21/s/master "wercker status")](https://app.wercker.com/project/bykey/32429e43b793f95c450ddd2a89efdb21)
2
+
1
3
  # ExMachina
2
4
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ex_machina`. To experiment with that code, run `bin/console` for an interactive prompt.
5
+ A simple implementation of [Finine-state machine](https://en.wikipedia.org/wiki/Finite-state_machine) using OOP following the principles:
6
+
7
+ - Machine: a set of status, events and transitions
8
+ - Status: the state of a machine
9
+ - Event: a action performed on each status
10
+ - Transition: the graph of status for a given event
4
11
 
5
- TODO: Delete this and the text above, and describe your gem
12
+ The *machine* is declared on target class that knowns the possible *status* and *events*. Each event should be implemented on its own class, where the *transitions* are declared too. ExMachina manages the flow, execution and transitions of each event and status.
6
13
 
7
14
  ## Installation
8
15
 
@@ -22,7 +29,78 @@ Or install it yourself as:
22
29
 
23
30
  ## Usage
24
31
 
25
- TODO: Write usage instructions here
32
+ Include the `ExMachina::Machine` on target object and define the status and events calling `has_status` and `has_events` respectively:
33
+
34
+ Here a classic example of a state machine of an Engine, which have too events: start and stop:
35
+
36
+ ```ruby
37
+ class Engine
38
+ include ExMachina::Machine
39
+
40
+ has_status [:stopped, :running]
41
+ has_events Engine::Start, Engine::Stop
42
+
43
+ attr_accessor :status, :fuel
44
+ def initialize(status = "stopped", fuel = 10)
45
+ @status, @fuel = satus, fuel
46
+ end
47
+ end
48
+ ```
49
+
50
+ And implement each event on its own class. Here was made on subclasses:
51
+
52
+ ```ruby
53
+ class Engine::Start
54
+ include ExMachina::Event
55
+
56
+ transition from: :stopped, to: :running, if: :has_fuel?, after: :consumes_fuel
57
+
58
+ def has_fuel?
59
+ context.fuel > 0
60
+ end
61
+
62
+ def perform(execution)
63
+ # do something here
64
+
65
+ execution.success!
66
+ end
67
+
68
+ def consumes_fuel
69
+ context.fuel -= 1
70
+ end
71
+ end
72
+
73
+ class Engine::Stop
74
+ include ExMachina::Event
75
+
76
+ transition from: :running, to: :stopped
77
+ end
78
+ ```
79
+
80
+ Here is an usage example:
81
+
82
+ ```ruby
83
+ engine = Engine.new # => #<Engine:0x007fb77c065758>
84
+ engine.status # "stopped"
85
+ engine.fuel # 10
86
+
87
+ engine.can_start? # true
88
+ engine.start # true
89
+ engine.status # "running"
90
+
91
+ engine.running? # true
92
+ engine.start # false
93
+ engine.errors # ["No transitions defined from 'running' status"]
94
+
95
+ engine.stop # true
96
+ engine.status # "stopped"
97
+ engine.stopped? # true
98
+ engine.fuel # 9
99
+
100
+ engine.fuel = 0 # 0
101
+ engine.can_start? # false
102
+ engine.start # false
103
+ ```
26
104
 
27
105
  ## Development
28
106
 
@@ -34,8 +112,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
34
112
 
35
113
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ex_machina. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
114
 
37
-
38
115
  ## License
39
116
 
40
117
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
-
@@ -0,0 +1,13 @@
1
+ module ExMachina
2
+ module Adapter
3
+ module ActiveRecord
4
+ def within_transaction(&block)
5
+ raise NotImplementedError, "not supported yet"
6
+ end
7
+
8
+ def persist(model)
9
+ model.save
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module ExMachina
2
+ module Adapter
3
+ module Memory
4
+ def within_transaction(&block)
5
+ block.call
6
+ end
7
+
8
+ def persist(model)
9
+ # do nothing
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module ExMachina
2
+ module Adapter
3
+ module Sequel
4
+ def within_transaction(&block)
5
+ DB.transaction(&block)
6
+ end
7
+
8
+ def persist(model)
9
+ model.save
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,18 @@
1
+ require "ex_machina/adapter/memory"
2
+ require "ex_machina/adapter/sequel"
3
+ require "ex_machina/adapter/active_record"
4
+
5
+ module ExMachina
6
+ module Adapter
7
+ def self.included(base)
8
+ if defined?(::Sequel)
9
+ base.include Adapter::Sequel
10
+ elsif defined?(::ActiveRecord)
11
+ base.include Adapter::ActiveRecord
12
+ else
13
+ base.include Adapter::Memory
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -1,6 +1,6 @@
1
1
  module ExMachina
2
2
  module Event
3
- class Runner
3
+ class Execution
4
4
 
5
5
  STATUSES = [
6
6
  SUCCESS = :success,
@@ -86,8 +86,11 @@ module ExMachina
86
86
 
87
87
  def eligible?
88
88
  if transition.conditional?
89
- invoke(event, transition.do_if, self) ||
90
- invoke(event, transition.do_unless, self)
89
+ if transition.do_if
90
+ invoke(transition.do_if)
91
+ elsif transition.do_unless
92
+ !invoke(transition.do_unless)
93
+ end
91
94
  else
92
95
  true
93
96
  end
@@ -98,41 +101,29 @@ module ExMachina
98
101
 
99
102
  return skipped! unless eligible?
100
103
 
101
- invoke(event, transition.do_before, self)
104
+ invoke(transition.do_before)
102
105
  begin
103
- call = invoke(event, :call, self)
104
- invoke(event, transition.do_after, self)
106
+ call = invoke(:perform)
107
+ invoke(transition.do_after)
105
108
  finish!(call)
106
109
  rescue StandardError => ex
107
110
  error!(ex)
108
111
  end
109
112
 
110
113
  if success?
111
- invoke(event, :change_status, self)
112
- invoke(event, transition.do_success, self)
114
+ invoke(:transit)
115
+ invoke(transition.do_success)
113
116
  elsif failure?
114
- invoke(event, transition.do_failure, self)
117
+ invoke(transition.do_failure)
115
118
  end
116
119
 
117
120
  call
118
121
  end
119
122
 
120
- def invoke(context, meth, *args)
121
- return if meth.nil?
123
+ protected
122
124
 
123
- if meth.respond_to?(:call)
124
- all_args = Array([context, *args])
125
- meth_params = meth.parameters
126
- meth_args = *all_args.first(meth_params.size)
127
-
128
- meth.call(*meth_args)
129
-
130
- elsif context.respond_to?(meth)
131
- meth_params = context.method(meth).parameters
132
- meth_args = *args.first(meth_params.size)
133
-
134
- context.send(meth, *meth_args)
135
- end
125
+ def invoke(meth, arg = self)
126
+ Util.invoke_method(event, meth, arg)
136
127
  end
137
128
  end
138
129
  end
@@ -5,8 +5,6 @@ module ExMachina
5
5
  attr_reader :from, :to
6
6
  attr_reader :do_if, :do_unless, :do_before, :do_after, :do_success, :do_failure
7
7
 
8
- attr_accessor :previous, :current, :context, :result
9
-
10
8
  def initialize(options)
11
9
  @from = Array(options.fetch(:from)).map { |status| normalize(status) }
12
10
  @to = normalize(options.fetch(:to))
@@ -1,13 +1,15 @@
1
1
  require "ex_machina/event/transition"
2
- require "ex_machina/event/runner"
2
+ require "ex_machina/event/execution"
3
3
  require "ex_machina/event/validations"
4
+ require "ex_machina/adapter"
4
5
 
5
6
  module ExMachina
6
7
  module Event
7
8
  def self.included(base)
8
- base.extend(ClassMethods)
9
- base.include(InstanceMethods)
10
- base.include(Validations)
9
+ base.extend ClassMethods
10
+ base.include InstanceMethods
11
+ base.include Validations
12
+ base.include Adapter
11
13
  end
12
14
 
13
15
  module ClassMethods
@@ -27,7 +29,7 @@ module ExMachina
27
29
  self.new(context).can_fire?
28
30
  end
29
31
  def event
30
- self.name.demodulize.underscore
32
+ @event ||= Util::String.new(self.name).demodulize.underscore
31
33
  end
32
34
  end
33
35
 
@@ -38,31 +40,40 @@ module ExMachina
38
40
  @context = context
39
41
  end
40
42
 
43
+ # Available transitions
41
44
  def transitions
42
45
  self.class
43
46
  .transitions
44
47
  .select { |transition| transition.from?(status) }
45
48
  end
46
49
 
50
+ # The context current status
47
51
  def status
48
52
  context.status
49
53
  end
50
54
 
51
- def change_status(execution)
55
+ # Called on event success
56
+ def transit(execution)
52
57
  context.status = execution.current.to_s
53
- context.save
58
+ persist(context)
59
+ end
60
+
61
+ # Override this method on event implementation
62
+ def perform
63
+ true
54
64
  end
55
65
 
56
66
  def fire
57
67
  validate
58
- return unless can_fire?
68
+ return false unless can_fire?
59
69
 
60
70
  result = false
61
71
  within_transaction do
62
72
  transitions.each do |transition|
63
- runner = Runner.new(self, transition)
64
- result = runner.run
73
+ runner = Execution.new(self, transition)
74
+ runner.run
65
75
 
76
+ result = runner.success?
66
77
  break result unless runner.skipped?
67
78
  end
68
79
  end
@@ -79,14 +90,6 @@ module ExMachina
79
90
  valid?
80
91
  end
81
92
 
82
- def call
83
- raise NotImplementedError, "method 'call' must be implemented on event class"
84
- end
85
-
86
- def within_transaction(&block)
87
- # TODO implement strategy for transaction
88
- DB.transaction(&block)
89
- end
90
93
  end
91
94
  end
92
95
  end
@@ -1,7 +1,7 @@
1
1
  module ExMachina
2
- module Eventable
2
+ module Machine
3
3
  def self.included(base)
4
- base.extend ClassMethods
4
+ base.extend ClassMethods
5
5
  base.include InstanceMethods
6
6
  end
7
7
 
@@ -0,0 +1,39 @@
1
+ module ExMachina
2
+ module Util
3
+ class String < ::String
4
+ # Convert 'MyModule::MyClass' to 'MyClass'
5
+ def demodulize
6
+ self.split("::").last
7
+ end
8
+
9
+ # Convert 'MyClass' to 'my_class'
10
+ def underscore
11
+ self
12
+ .gsub("::", "/")
13
+ .gsub(/(^[A-Z])/) { |match| "#{match.downcase}" }
14
+ .gsub(/([A-Z])/) { |match| "_#{match.downcase}" }
15
+ end
16
+ end
17
+
18
+ extend self
19
+
20
+ # Invoke method or lambda on given context passing correspondent args
21
+ def invoke_method(context, meth, *args)
22
+ return if meth.nil?
23
+
24
+ if meth.respond_to?(:call)
25
+ all_args = Array([context, *args])
26
+ meth_params = meth.parameters
27
+ meth_args = *all_args.first(meth_params.size)
28
+
29
+ meth.call(*meth_args)
30
+
31
+ elsif context.respond_to?(meth)
32
+ meth_params = context.method(meth).parameters
33
+ meth_args = *args.first(meth_params.size)
34
+
35
+ context.send(meth, *meth_args)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,3 +1,3 @@
1
1
  module ExMachina
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/ex_machina.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require "ex_machina/version"
2
+ require "ex_machina/util"
3
+ require "ex_machina/machine"
2
4
  require "ex_machina/event"
3
- require "ex_machina/eventable"
5
+ require "ex_machina/adapter"
4
6
 
5
7
  module ExMachina
6
8
  end
data/wercker.yml ADDED
@@ -0,0 +1,7 @@
1
+ box: ruby:2.3.0
2
+ build:
3
+ steps:
4
+ - bundle-install
5
+ - script:
6
+ name: rspec
7
+ code: bundle exec rspec
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ex_machina
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Panachi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-03-05 00:00:00.000000000 Z
11
+ date: 2016-03-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -69,12 +69,18 @@ files:
69
69
  - bin/setup
70
70
  - ex_machina.gemspec
71
71
  - lib/ex_machina.rb
72
+ - lib/ex_machina/adapter.rb
73
+ - lib/ex_machina/adapter/active_record.rb
74
+ - lib/ex_machina/adapter/memory.rb
75
+ - lib/ex_machina/adapter/sequel.rb
72
76
  - lib/ex_machina/event.rb
73
- - lib/ex_machina/event/runner.rb
77
+ - lib/ex_machina/event/execution.rb
74
78
  - lib/ex_machina/event/transition.rb
75
79
  - lib/ex_machina/event/validations.rb
76
- - lib/ex_machina/eventable.rb
80
+ - lib/ex_machina/machine.rb
81
+ - lib/ex_machina/util.rb
77
82
  - lib/ex_machina/version.rb
83
+ - wercker.yml
78
84
  homepage: https://github.com/rpanachi/ex_machina
79
85
  licenses:
80
86
  - MIT