finist 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3ef4a139c70308a595e49e7ba7053a64631c33e0
4
+ data.tar.gz: 6a640ada556919739c4bc01de30d4aa3e9fba735
5
+ SHA512:
6
+ metadata.gz: c5720d02f6093f0eeb9bd20161f7b7b617f70c20ded4a85d8305cb2f61e5e38c50e2b3c18ea33c225fb35841cc6d0f28b88bbe75dc5e409a4297698965bbbd11
7
+ data.tar.gz: 2cc188dd3d8a6d7fd58bdab1c9727765ca7ddbb346132e687364617ece39a41d5127df78382838a4e333736cbe32241892c7fd4f4ece7f7cf75085a2cd2f6cae
data/.gems ADDED
@@ -0,0 +1,2 @@
1
+ redic -v 1.2.0
2
+ cutest -v 1.2.2
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015 Michel Martens
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,112 @@
1
+ Finist
2
+ ======
3
+
4
+ Redis based Finite State Machine.
5
+
6
+ Description
7
+ -----------
8
+
9
+ Finist is a finite state machine that is defined and persisted in
10
+ [Redis][redis].
11
+
12
+ Community
13
+ ---------
14
+
15
+ Meet us on IRC: [#lesscode](irc://chat.freenode.net/#lesscode) on
16
+ [freenode.net](http://freenode.net/).
17
+
18
+ Related projects
19
+ ----------------
20
+
21
+ There's an [alternative implementation for Lua][finist.lua].
22
+
23
+ Getting started
24
+ ---------------
25
+
26
+ Install [Redis][redis]. On most platforms it's as easy as grabbing
27
+ the sources, running make and then putting the `redis-server` binary
28
+ in the PATH.
29
+
30
+ Once you have it installed, you can execute `redis-server` and it
31
+ will run on `localhost:6379` by default. Check the `redis.conf`
32
+ file that comes with the sources if you want to change some settings.
33
+
34
+ Usage
35
+ -----
36
+
37
+ Finist requires a [Redic][redic] compatible client. To make things
38
+ easier, `redic` is listed as a runtime dependency so the examples
39
+ in this document will work.
40
+
41
+ ```ruby
42
+ require "finist"
43
+
44
+ # Initialize with a Redis client, the name of the machine and the
45
+ # initial state. In this example, the machine is called "order" and
46
+ # the initial status is "pending". The Redis client is connected to
47
+ # the default host (127.0.0.1:6379).
48
+ machine = Finist.new(Redic.new, "order", "pending")
49
+
50
+ # Available transitions are defined with the `on` method
51
+ # `machine.on(<event>, <initial_state>, <final_state>)`
52
+ machine.on("approve", "pending", "approved")
53
+ machine.on("cancel", "pending", "cancelled")
54
+ machine.on("cancel", "approved", "cancelled")
55
+ machine.on("reset", "cancelled", "pending")
56
+ ```
57
+
58
+ Now that the possible transitions are defined, we can check the
59
+ current state:
60
+
61
+ ```ruby
62
+ machine.state
63
+ # => "pending"
64
+ ```
65
+
66
+ And we can trigger an event:
67
+
68
+ ```ruby
69
+ machine.trigger("approve")
70
+ # => [true, "approved"]
71
+ ```
72
+
73
+ The `trigger` method returns an array of two values: the first
74
+ represents whether a transition occurred, and the second represents
75
+ the current state.
76
+
77
+ Here's what happens if an event doesn't cause a transition:
78
+
79
+ ```ruby
80
+ machine.trigger("reset")
81
+ # => [false, "approved"]
82
+ ```
83
+
84
+ Here's a convenient way to use this flag:
85
+
86
+ ```ruby
87
+ changed, state = machine.trigger("reset")
88
+
89
+ if changed
90
+ printf("State changed to %s\n", state)
91
+ end
92
+ ```
93
+
94
+ If you need to remove all the transitions for a given event, you
95
+ can use `rm`:
96
+
97
+ ```ruby
98
+ machine.rm("reset")
99
+ ```
100
+
101
+ Note that every change is persisted in Redis.
102
+
103
+ Installation
104
+ ------------
105
+
106
+ ```
107
+ $ gem install finist
108
+ ```
109
+
110
+ [redis]: http://redis.io
111
+ [redic]: https://github.com/amakawa/redic
112
+ [finist.lua]: https://github.com/soveran/finist.lua
data/finist.gemspec ADDED
@@ -0,0 +1,15 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "finist"
3
+ s.version = "0.0.1"
4
+ s.summary = %{Redis based Finite State Machine}
5
+ s.description = %Q{Finist is a finite state machine that is defined and persisted in Redis.}
6
+ s.authors = ["Michel Martens"]
7
+ s.email = ["michel@soveran.com"]
8
+ s.homepage = "https://github.com/soveran/finist"
9
+ s.license = "MIT"
10
+
11
+ s.files = `git ls-files`.split("\n")
12
+
13
+ s.add_dependency "redic"
14
+ s.add_development_dependency "cutest"
15
+ end
data/lib/finist.rb ADDED
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+
3
+ require "redic"
4
+
5
+ class Finist
6
+ SCRIPT = <<-EOS
7
+ local curr = redis.call("GET", KEYS[1])
8
+ local next = redis.call("HGET", KEYS[2], curr)
9
+
10
+ if next then
11
+ redis.call("SET", KEYS[1], next)
12
+ return { next, true }
13
+ else
14
+ return { curr, false }
15
+ end
16
+ EOS
17
+
18
+ def initialize(redis, name, init)
19
+ @name = sprintf("finist:%s", name)
20
+ @redis = redis
21
+ @redis.call("SET", @name, init, "NX")
22
+ end
23
+
24
+ def event_key(ev)
25
+ sprintf("%s:%s", @name, ev)
26
+ end
27
+
28
+ def on(ev, curr_state, next_state)
29
+ @redis.call("HSET", event_key(ev), curr_state, next_state)
30
+ end
31
+
32
+ def rm(ev)
33
+ @redis.call("DEL", event_key(ev))
34
+ end
35
+
36
+ def state
37
+ @redis.call("GET", @name)
38
+ end
39
+
40
+ def send_event(ev)
41
+ @redis.call("EVAL", SCRIPT, "2", @name, event_key(ev))
42
+ end
43
+
44
+ def trigger(ev)
45
+ result = send_event(ev)
46
+
47
+ if result[1]
48
+ return true, result[0]
49
+ else
50
+ return false, result[0]
51
+ end
52
+ end
53
+ end
data/makefile ADDED
@@ -0,0 +1,4 @@
1
+ .PHONY: test
2
+
3
+ test:
4
+ cutest -r ./test/helper.rb ./test/*.rb
data/test/all.rb ADDED
@@ -0,0 +1,60 @@
1
+ require_relative "helper"
2
+
3
+ test do
4
+ c = Redic.new
5
+
6
+ fsm = Finist.new(c, "myfsm", "pending")
7
+
8
+ fsm.on("approve", "pending", "approved")
9
+ fsm.on("cancel", "pending", "cancelled")
10
+ fsm.on("cancel", "approved", "cancelled")
11
+ fsm.on("reset", "cancelled", "pending")
12
+
13
+ # Verify initial state
14
+ assert_equal("pending", fsm.state)
15
+
16
+ # Send an event
17
+ fsm.trigger("approve")
18
+
19
+ # Verify transition to "approved"
20
+ assert_equal("approved", fsm.state)
21
+
22
+ # Send an event
23
+ fsm.trigger("cancel")
24
+
25
+ # Verify transition to "cancelled"
26
+ assert_equal("cancelled", fsm.state)
27
+
28
+ # Send an event
29
+ fsm.trigger("approve")
30
+
31
+ # Verify state remains as "cancelled"
32
+ assert_equal("cancelled", fsm.state)
33
+
34
+ # Create a different fsm with client
35
+ fsm2 = Finist.new(c, "myfsm", "pending")
36
+
37
+ # Verify state remains as "cancelled"
38
+ assert_equal("cancelled", fsm2.state)
39
+
40
+ # A successful event returns true
41
+ changed, state = fsm.trigger("reset")
42
+
43
+ assert_equal(true, changed)
44
+ assert_equal("pending", state)
45
+
46
+ # An unsuccessful event returns false
47
+ changed, state = fsm.trigger("reset")
48
+
49
+ assert_equal(false, changed)
50
+ assert_equal("pending", state)
51
+
52
+ # Delete an event
53
+ fsm.rm("approve")
54
+
55
+ # Non existent events return false
56
+ changed, state = fsm.trigger("approve")
57
+
58
+ assert_equal(false, changed)
59
+ assert_equal("pending", state)
60
+ end
data/test/helper.rb ADDED
@@ -0,0 +1 @@
1
+ require_relative "../lib/finist"
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: finist
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Michel Martens
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-02-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: redic
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: cutest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Finist is a finite state machine that is defined and persisted in Redis.
42
+ email:
43
+ - michel@soveran.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - .gems
49
+ - LICENSE
50
+ - README.md
51
+ - finist.gemspec
52
+ - lib/finist.rb
53
+ - makefile
54
+ - test/all.rb
55
+ - test/helper.rb
56
+ homepage: https://github.com/soveran/finist
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubyforge_project:
76
+ rubygems_version: 2.0.14
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: Redis based Finite State Machine
80
+ test_files: []