ex_machina 0.0.1 → 0.0.2

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: 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