cequel_stateful_enum 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e3a756dda6366a683accf33e3c848cfe9593b689
4
+ data.tar.gz: 443654533689d282e506c30085f0d65e442b87d8
5
+ SHA512:
6
+ metadata.gz: 5a5fb6eedcbf1eef21bdcc3dea6dcab39f2eb3f51bde0452fcfe3ff5926b3fb0d2dbc65d17ad81cb95521682130efc7bb05607829c73f03966f722ec1ab4258d
7
+ data.tar.gz: cda07ca03c09476e88aba710d33a291a42091d60595e89870e6f289513510052f48e66e35720f37bf9407885e96dd49d27943ea1183188ad67fe7a16bca7327e
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Akira Matsuda
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,115 @@
1
+ # CequelStatefulEnum
2
+
3
+ cequel_stateful_enum is a simple state machine gem built on top of [Cequel](https://github.com/cequel/cequel)'s built-in enum column.
4
+ This gem is totally based on [stateful_enum](https://github.com/amatsuda/stateful_enum) gem for ActiveRecord. Huge thanks to Akira Matsuda!
5
+ This gem in not depends on Rails.
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your app's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'cequel_stateful_enum'
14
+ ```
15
+
16
+ And bundle.
17
+
18
+
19
+ ## Usage
20
+
21
+ The cequel_stateful_enum gem extends Cequel's `column` definition to take a block with a similar DSL to the [state_machine](https://github.com/pluginaweek/state_machine) gem.
22
+
23
+ Example:
24
+ ```ruby
25
+ class Bug
26
+ include Cequel::Record
27
+
28
+ column :status, :enum, values: { unassigned: 0, assigned: 1, resolved: 2, closed: 3 } do
29
+ event :assign do
30
+ transition :unassigned => :assigned
31
+ end
32
+
33
+ event :resolve do
34
+ before do
35
+ self.resolved_at = Time.now
36
+ end
37
+
38
+ transition [:unassigned, :assigned] => :resolved
39
+ end
40
+
41
+ event :close do
42
+ transition all - [:closed] => :closed
43
+
44
+ after :notify_author_about_status
45
+
46
+ after do
47
+ BugCache.remove_from_open_cache(self)
48
+ end
49
+ end
50
+ end
51
+
52
+ # ...
53
+
54
+ end
55
+ ```
56
+
57
+ ### Defining the States
58
+
59
+ Just call the Cequel's `column` method with `:enum` type. The only difference from the original method is that our `column` call takes a block.
60
+
61
+ ### Defining the Events
62
+
63
+ You can declare events through `event` method inside of an `column` block. Then cequel_stateful_enum defines the following methods per each event:
64
+
65
+ **An instance method to fire the event**
66
+
67
+ ```ruby
68
+ @bug.assign # does nothing and returns false if a valid transition for the current state is not defined
69
+ ```
70
+
71
+ **An instance method with `!` to fire the event**
72
+ ```ruby
73
+ @bug.assign! # raises if a valid transition for the current state is not defined
74
+ ```
75
+
76
+ **A predicate method that returns if the event is fireable**
77
+ ```ruby
78
+ @bug.can_assign? # returns if the `assign` event can be called on this bug or not and all `if` and `unless` conditions are met
79
+ ```
80
+
81
+ ### Defining the Transitions
82
+
83
+ You can define state transitions through `transition` method inside of an `event` block.
84
+
85
+ There are a few important details to note regarding this feature:
86
+
87
+ * The `transition` method takes a Hash each key of which is state "from" transitions to the Hash value.
88
+ * The "from" states and the "to" states should both be given in Symbols.
89
+ * The "from" state can be multiple states, in which case the key can be given as an Array of states, as shown in the usage example.
90
+ * The "from" state can be `all` that means all defined states.
91
+
92
+ ### :if and :unless Condition
93
+
94
+ The `transition` method takes an `:if` and/or `:unless` option as a Proc or Symbol.
95
+
96
+ Example:
97
+ ```ruby
98
+ event :assign do
99
+ transition :unassigned => :assigned, if: -> { assigned_to.any? }, unless: :blocked?
100
+ end
101
+ ```
102
+
103
+ ### Event Hooks
104
+
105
+ You can define `before` and `after` event hooks inside of an `event` block as shown in the example above. Symbols and Proc objects are supported.
106
+
107
+
108
+ ## Contributing
109
+
110
+ Pull requests are welcome on GitHub at https://github.com/Xanders/cequel_stateful_enum.
111
+
112
+
113
+ ## License
114
+
115
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,19 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'cequel_stateful_enum'
6
+ spec.version = '1.0.0'
7
+ spec.authors = ['Xanders', 'Akira Matsuda']
8
+ spec.email = ['necropolis@inbox.ru', 'ronnie@dio.jp']
9
+
10
+ spec.summary = 'A state machine plugin on top of Cequel'
11
+ spec.homepage = 'https://github.com/Xanders/cequel_stateful_enum'
12
+ spec.license = 'MIT'
13
+
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_dependency 'cequel'
18
+ spec.add_development_dependency 'rspec'
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cequel'
4
+ require 'cequel_stateful_enum/machine'
5
+
6
+ module CequelStatefulEnum
7
+ module Extension
8
+ # column :status, :enum, values: { opened: 1, closed: 2 } do
9
+ # event :close do
10
+ # transition :opened => :closed
11
+ # end
12
+ # end
13
+ def column(name, type, options = {}, &block)
14
+ super
15
+
16
+ if type == :enum && block
17
+ states = options[:values]
18
+ states = states.keys if states.is_a?(Hash)
19
+ CequelStatefulEnum::Machine.new(self, name, states, &block)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ Cequel::Record::Properties::ClassMethods.prepend CequelStatefulEnum::Extension
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CequelStatefulEnum
4
+ Error = Class.new(RuntimeError)
5
+ DefinitionError = Class.new(Error)
6
+ WorkflowError = Class.new(Error)
7
+ StateError = Class.new(WorkflowError)
8
+ ConditionError = Class.new(WorkflowError)
9
+
10
+ class Machine
11
+ def initialize(model, column, states, &block)
12
+ @model, @column, @states, @event_names = model, column, states, []
13
+ instance_eval(&block)
14
+ end
15
+
16
+ def event(name, &block)
17
+ raise DefinitionError, "event #{name} has already been defined" if @event_names.include? name
18
+ Event.new @model, @column, @states, name, &block
19
+ @event_names << name
20
+ end
21
+
22
+ class Event
23
+ def initialize(model, column, states, name, &block)
24
+ can = "can_#{name}?"
25
+ raise DefinitionError, "one of the #{name}, #{name}! or #{can} methods already defined" if ([name, "#{name}!", can] & model.instance_methods).any?
26
+
27
+ @states, @name, @transitions, @before, @after = states, name, {}, [], []
28
+ instance_eval(&block) if block
29
+
30
+ transitions, before, after = @transitions, @before, @after
31
+
32
+ # defining event methods
33
+ model.class_eval do
34
+ define_method name do |danger = false|
35
+ next false unless send(can, danger)
36
+ to = transitions[send(column).to_sym].first
37
+ before.each { |callback| instance_eval(&callback) }
38
+ send("#{column}=", to)
39
+ after.each { |callback| instance_eval(&callback) }
40
+ true
41
+ end
42
+
43
+ define_method "#{name}!" do
44
+ send(name, true)
45
+ end
46
+
47
+ define_method can do |danger = false|
48
+ from = send(column).to_sym
49
+ to, condition = transitions[from]
50
+ if !to
51
+ raise StateError, "can't fire #{name} event from state #{from}" if danger
52
+ false
53
+ elsif condition && !instance_exec(&condition)
54
+ raise ConditionError, "conditions for #{name} event does not met" if danger
55
+ false
56
+ else
57
+ true
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def transition(transitions)
64
+ condition = transitions.delete(:if)
65
+ unless condition.nil? || condition.is_a?(Symbol) || condition.respond_to?(:call)
66
+ raise ArgumentError, "`if` condition can be nil, Symbol or callable object, but #{condition.class} given"
67
+ end
68
+ if condition.is_a?(Symbol)
69
+ symbol = condition
70
+ condition = -> { send(symbol) }
71
+ end
72
+ if unless_condition = transitions.delete(:unless)
73
+ unless unless_condition.nil? || unless_condition.is_a?(Symbol) || unless_condition.respond_to?(:call)
74
+ raise ArgumentError, "`unless` condition can be nil, Symbol or callable object, but #{unless_condition.class} given"
75
+ end
76
+ if unless_condition.is_a?(Symbol)
77
+ symbol = unless_condition
78
+ unless_condition = -> { send(symbol) }
79
+ end
80
+ condition = if condition
81
+ if_condition = condition
82
+ -> { instance_exec(&if_condition) && !instance_exec(&unless_condition) }
83
+ else
84
+ -> { !instance_exec(&unless_condition) }
85
+ end
86
+ end
87
+
88
+ transitions.each_pair do |froms, to|
89
+ raise DefinitionError, "undefined state #{to}" unless @states.include? to
90
+ Array(froms).each do |from|
91
+ raise DefinitionError, "undefined state #{from}" unless @states.include? from
92
+ raise DefinitionError, "duplicate entry: transition from #{from} to #{@transitions[from].first} has already been defined" if @transitions[from]
93
+ @transitions[from] = [to, condition]
94
+ end
95
+ end
96
+ end
97
+
98
+ def all
99
+ @states
100
+ end
101
+
102
+ def before(symbol = nil, &block)
103
+ raise ArgumentError, 'use Symbol or block for `before` callback' unless block_given? ? symbol.nil? : symbol.is_a?(Symbol)
104
+ block ||= -> { send(symbol) }
105
+ @before.push(block)
106
+ end
107
+
108
+ def after(symbol = nil, &block)
109
+ raise ArgumentError, 'use Symbol or block for `after` callback' unless block_given? ? symbol.nil? : symbol.is_a?(Symbol)
110
+ block ||= -> { send(symbol) }
111
+ @after.push(block)
112
+ end
113
+ end
114
+ end
115
+ end
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cequel_stateful_enum
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Xanders
8
+ - Akira Matsuda
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2017-03-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: cequel
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rspec
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description:
43
+ email:
44
+ - necropolis@inbox.ru
45
+ - ronnie@dio.jp
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - MIT-LICENSE
51
+ - README.md
52
+ - cequel_stateful_enum.gemspec
53
+ - lib/cequel_stateful_enum.rb
54
+ - lib/cequel_stateful_enum/machine.rb
55
+ homepage: https://github.com/Xanders/cequel_stateful_enum
56
+ licenses:
57
+ - MIT
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.6.8
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: A state machine plugin on top of Cequel
79
+ test_files: []