stateful_enum 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a5b8fa0430831e495e52e5fad80462db2100a9a8
4
+ data.tar.gz: ace6e802d2fa0abd9b047c23215dd0e2b8a70033
5
+ SHA512:
6
+ metadata.gz: c2b6c81796a16ded357ff40cac7c2cfc37eb6570516d685ef59afe897a8d34cb270ace9e5e3b514aeface34072f5c3186e26cb69b8320acdbdc6cef039431093
7
+ data.tar.gz: 43019bdc355a9352af8a674f42a10b4d12f4fe64776c17089e731ea15109f0ab5b7c95ff476fbc3c493ba601e4570f00f51aa28514b3d9ff14ad1adc97de3715
@@ -0,0 +1,13 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /log/
11
+ /test/dummy/log/
12
+
13
+ .byebug_history
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.4.0
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in stateful_enum.gemspec
4
+ gemspec
5
+
6
+ gem 'byebug'
@@ -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.
@@ -0,0 +1,119 @@
1
+ # StatefulEnum
2
+
3
+ stateful_enum is a state machine gem built on top of ActiveRecord's built-in ActiveRecord::Enum.
4
+
5
+
6
+ ## Installation
7
+
8
+ Add this line to your Rails app's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'stateful_enum'
12
+ ```
13
+
14
+ And bundle.
15
+
16
+
17
+ ## Motivation
18
+
19
+ ### You Ain't Gonna Need Abstraction
20
+
21
+ stateful_enum depends on ActiveRecord. If you prefer a well-abstracted state machine library that supports multiple datastores, or Plain Old Ruby Objects (who needs that feature?), I'm sorry but this gem is not for you.
22
+
23
+ ### I Hate Saving States in a VARCHAR Column
24
+
25
+ From a database design point of view, I prefer to save state data in an INTEGER column rather than saving the state name directly in a VARCHAR column.
26
+
27
+ ### :heart: ActiveRecord::Enum
28
+
29
+ ActiveRecord 4.1+ has a very simple and useful built-in Enum DSL that provides human-friendly API over integer values in DB.
30
+
31
+ ### Method Names Should be Verbs
32
+
33
+ AR::Enum automatically defines Ruby methods per each label. However, Enum labels are in most cases adjectives or past participle, which often creates weird method names.
34
+ What we really want to define as methods are the transition events between states, and not the states themselves.
35
+
36
+
37
+ ## Usage
38
+
39
+ The stateful_enum gem extends AR::Enum definition to take a block with a similar DSL to the [state_machine](https://github.com/pluginaweek/state_machine) gem.
40
+
41
+ Example:
42
+ ```ruby
43
+ class Bug < ApplicationRecord
44
+ enum status: {unassigned: 0, assigned: 1, resolved: 2} do
45
+ event :assign do
46
+ transition :unassigned => :assigned
47
+ end
48
+ event :resolve do
49
+ transition [:unassigned, :assigned] => :resolved
50
+ end
51
+ end
52
+ end
53
+ ```
54
+
55
+ ### Defining the States
56
+
57
+ Just call the AR::Enum's `enum` method. The only difference from the original `enum` method is that our `enum` call takes a block.
58
+ Please see the full API documentation of [AR::Enum](http://edgeapi.rubyonrails.org/classes/ActiveRecord/Enum.html) for more information.
59
+
60
+ ### Defining the Events
61
+
62
+ You can declare events through `event` method inside of an `enum` block. Then stateful_enum defines the following methods per each event:
63
+
64
+ **An instance method to fire the event**
65
+
66
+ ```ruby
67
+ @bug.assign # does nothing if a valid transition for the current state is not defined
68
+ ```
69
+
70
+ **An instance method with `!` to fire the event**
71
+ ```ruby
72
+ @bug.assign! # raises if a valid transition for the current state is not defined
73
+ ```
74
+
75
+ **A predicate method that returns if the event is fireable**
76
+ ```ruby
77
+ @bug.can_assign? # returns if the `assign` event can be called on this bug or not
78
+ ```
79
+
80
+ **An instance method that returns the state name after an event**
81
+ ```ruby
82
+ @bug.assign_transition #=> :assigned
83
+ ```
84
+
85
+ ### Defining the Transitions
86
+
87
+ You can define state transitions through `transition` method inside of an `event` block.
88
+
89
+ There are a few important details to note regarding this feature:
90
+
91
+ * The `transition` method takes a Hash each key of which is state "from" transitions to the Hash value.
92
+ * The "from" states and the "to" states should both be given in Symbols.
93
+ * 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.
94
+
95
+ ### Error handling
96
+
97
+ **TODO**
98
+
99
+ ### Event hooks
100
+
101
+ **TODO**
102
+
103
+ ### Guards (:if and :unless options)
104
+
105
+ **TODO**
106
+
107
+ ### Transition from "all"
108
+
109
+ **TODO**
110
+
111
+
112
+ ## Contributing
113
+
114
+ Pull requests are welcome on GitHub at https://github.com/amatsuda/stateful_enum.
115
+
116
+
117
+ ## License
118
+
119
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task default: :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "stateful_enum"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,2 @@
1
+ require 'stateful_enum/version'
2
+ require 'stateful_enum/railtie'
@@ -0,0 +1,20 @@
1
+ require 'stateful_enum/machine'
2
+
3
+ module StatefulEnum
4
+ module ActiveRecordEnumExtension
5
+ # enum status: {unassigned: 0, assigned: 1, resolved: 2, closed: 3} do
6
+ # event :assign do
7
+ # transition :unassigned => :assigned
8
+ # end
9
+ # end
10
+ def enum(definitions, &block)
11
+ enum = super definitions
12
+
13
+ if block
14
+ definitions.each_key do |column|
15
+ StatefulEnum::Machine.new self, column, enum[column], &block
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,64 @@
1
+ module StatefulEnum
2
+ class Machine
3
+ def initialize(model, column, states, &block)
4
+ @model, @column, @states, @event_names = model, column, states, []
5
+
6
+ # undef non-verb methods e.g. Model#active!
7
+ states.each_key do |state|
8
+ @model.send :undef_method, "#{state}!"
9
+ end
10
+
11
+ instance_eval(&block) if block
12
+ end
13
+
14
+ def event(name, &block)
15
+ raise "event: :#{name} has already been defined." if @event_names.include? name
16
+ Event.new @model, @column, @states, name, &block
17
+ @event_names << name
18
+ end
19
+
20
+ class Event
21
+ def initialize(model, column, states, name, &block)
22
+ @model, @column, @states, @name, @transitions = model, column, states, name, {}
23
+
24
+ instance_eval(&block) if block
25
+
26
+ define_transition_methods
27
+ end
28
+
29
+ def define_transition_methods
30
+ column, name, transitions = @column, @name, @transitions
31
+
32
+ @model.send(:define_method, name) do
33
+ if (to = transitions[self.send(column).to_sym])
34
+ self.class.instance_variable_get(:@_enum_methods_module).instance_method("#{to}!").bind(self).call
35
+ else
36
+ false
37
+ end
38
+ end
39
+
40
+ @model.send(:define_method, "#{name}!") do
41
+ send(name) || raise('Invalid transition')
42
+ end
43
+
44
+ @model.send(:define_method, "can_#{name}?") do
45
+ transitions.has_key? self.send(column).to_sym
46
+ end
47
+
48
+ @model.send(:define_method, "#{name}_transition") do
49
+ transitions[self.send(column).to_sym]
50
+ end
51
+ end
52
+
53
+ def transition(transitions)
54
+ transitions.each_pair do |from, to|
55
+ raise "Undefined state #{to}" unless @states.has_key? to
56
+ Array(from).each do |f|
57
+ raise "Undefined state #{f}" unless @states.has_key? f
58
+ @transitions[f] = to
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,9 @@
1
+ require 'stateful_enum/active_record_extension'
2
+
3
+ module StatefulEnum
4
+ class Railtie < ::Rails::Railtie
5
+ ActiveSupport.on_load :active_record do
6
+ ::ActiveRecord::Base.extend StatefulEnum::ActiveRecordEnumExtension
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module StatefulEnum
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'stateful_enum/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "stateful_enum"
8
+ spec.version = StatefulEnum::VERSION
9
+ spec.authors = ["Akira Matsuda"]
10
+ spec.email = ["ronnie@dio.jp"]
11
+
12
+ spec.summary = 'A state machine plugin on top of ActiveRecord::Enum'
13
+ spec.description = 'A state machine plugin on top of ActiveRecord::Enum'
14
+ spec.homepage = 'https://github.com/amatsuda/stateful_enum'
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.11"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "minitest", "~> 5.0"
25
+ spec.add_development_dependency 'rails'
26
+ spec.add_development_dependency 'sqlite3'
27
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stateful_enum
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Akira Matsuda
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-03-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: sqlite3
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: A state machine plugin on top of ActiveRecord::Enum
84
+ email:
85
+ - ronnie@dio.jp
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".travis.yml"
92
+ - Gemfile
93
+ - MIT-LICENSE
94
+ - README.md
95
+ - Rakefile
96
+ - bin/console
97
+ - bin/setup
98
+ - lib/stateful_enum.rb
99
+ - lib/stateful_enum/active_record_extension.rb
100
+ - lib/stateful_enum/machine.rb
101
+ - lib/stateful_enum/railtie.rb
102
+ - lib/stateful_enum/version.rb
103
+ - stateful_enum.gemspec
104
+ homepage: https://github.com/amatsuda/stateful_enum
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.6.1
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: A state machine plugin on top of ActiveRecord::Enum
128
+ test_files: []