cequel_stateful_enum 1.0.0

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