just_state_machine 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/.travis.yml +4 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +264 -0
- data/Rakefile +6 -0
- data/bin/console +15 -0
- data/bin/setup +7 -0
- data/examples/active_record_state_machine.rb +59 -0
- data/examples/simple_state_machine.rb +34 -0
- data/jsm.gemspec +31 -0
- data/lib/jsm.rb +20 -0
- data/lib/jsm/base.rb +71 -0
- data/lib/jsm/client.rb +46 -0
- data/lib/jsm/client/active_model.rb +24 -0
- data/lib/jsm/client/active_record.rb +13 -0
- data/lib/jsm/client_extension.rb +67 -0
- data/lib/jsm/event.rb +88 -0
- data/lib/jsm/event_executor/active_model.rb +25 -0
- data/lib/jsm/event_executor/active_record.rb +16 -0
- data/lib/jsm/event_executor/base.rb +29 -0
- data/lib/jsm/exceptions.rb +9 -0
- data/lib/jsm/machines.rb +10 -0
- data/lib/jsm/states.rb +35 -0
- data/lib/jsm/validator.rb +12 -0
- data/lib/jsm/validators.rb +17 -0
- data/lib/jsm/version.rb +3 -0
- metadata +187 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2c74ea95f48cdea857973b9cd65ff7985ef34f21
|
4
|
+
data.tar.gz: 0fa48156ccd7ef61c450855c053b2d362a5e34ee
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0ca6342ddbe7928a00f6afa8bc9adc5cb9ee09832e877c275f431a22e7a97e411e9d8913a8243b57213e3fa33956e4d4182af0e3168a56137231e2d64270a172
|
7
|
+
data.tar.gz: bfa61404cbf03c319c0e09ccf46ca01298ee8a1c12f6a2b17d26db169689bef2b3d03d1d764797eec1395810af7e4e45419461a2450ab90ef9329f97ed0a7f00
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# Contributor Code of Conduct
|
2
|
+
|
3
|
+
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
+
|
5
|
+
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
+
|
7
|
+
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
+
|
9
|
+
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
+
|
11
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
+
|
13
|
+
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 wendy0402
|
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,264 @@
|
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/jsm.svg)](https://badge.fury.io/rb/jsm)
|
2
|
+
[![Code Climate](https://codeclimate.com/github/wendy0402/jsm/badges/gpa.svg)](https://codeclimate.com/github/wendy0402/jsm)
|
3
|
+
# Jsm
|
4
|
+
|
5
|
+
JSM is abbreviation of Just State Machine. The purpose is to simplify and increase the clarity of code related with state. JSM support validations before do transition. It help you to prevent unwanted transition. It also support integration with `ActiveModel` and `ActiveRecord`.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'jsm'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install jsm
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### State Machine Definition
|
26
|
+
To use `JSM`, a state machine class need to be created. Define your state machine here, such as:
|
27
|
+
* state
|
28
|
+
* event
|
29
|
+
* transition
|
30
|
+
* state validation
|
31
|
+
* attribute(the client state value)
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class UserStateMachine < Jsm::Base
|
35
|
+
attribute_name :title
|
36
|
+
|
37
|
+
state :beginner
|
38
|
+
state :intermediate
|
39
|
+
state :master
|
40
|
+
|
41
|
+
validate :intermediate do |user|
|
42
|
+
(20..50).include?(user.current_level)
|
43
|
+
end
|
44
|
+
|
45
|
+
validate :master do |user|
|
46
|
+
user.current_level > 50
|
47
|
+
end
|
48
|
+
|
49
|
+
event :upgrade_title do
|
50
|
+
transition from: [:beginner], to: :intermediate
|
51
|
+
transition from: [:intermediate], to: :master
|
52
|
+
end
|
53
|
+
|
54
|
+
event :downgrade_title do
|
55
|
+
transition from: :intermediate, to: :beginner
|
56
|
+
transition from: :master, to: :intermediate
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
### Client
|
62
|
+
To use the state machine definition specify it as below
|
63
|
+
```ruby
|
64
|
+
class User
|
65
|
+
include Jsm::Client
|
66
|
+
jsm_use UserStateMachine # your state machine class here
|
67
|
+
|
68
|
+
attr_accessor :title # same with attribute_name in UserStateMachine
|
69
|
+
#your code here
|
70
|
+
end
|
71
|
+
```
|
72
|
+
**note**: Client class should have instance variable same with `attribute_name` specified in state machine class.
|
73
|
+
|
74
|
+
This also provides you with a couple of public methods(based on event) for instances of the class `User`:
|
75
|
+
```ruby
|
76
|
+
user = User.new
|
77
|
+
user.upgrade_title # run event confirm, return true/ false
|
78
|
+
user.upgrade_title! # run event confirm, raise error Jsm::IllegalTransitionError if failed
|
79
|
+
user.can_upgrade_title? # check if can run event successfully, return true/false
|
80
|
+
|
81
|
+
user.downgrade_title
|
82
|
+
user.downgrade_title!
|
83
|
+
user.can_downgrade_title?
|
84
|
+
```
|
85
|
+
|
86
|
+
### Validation
|
87
|
+
This is useful, when you want to allow transition to a specified state allowed when it pass the validation. Validation should return true if passed validation and false if failed.
|
88
|
+
**note**: Dont forget to define the state first, because if not then Jsm will raise error `Jsm::InvalidStateError`. This is to prevent typo when add new validation
|
89
|
+
``` ruby
|
90
|
+
class UserStateMachine < Jsm::Base
|
91
|
+
# many codes here
|
92
|
+
|
93
|
+
state :intermediate
|
94
|
+
validate :intermediate do |user|
|
95
|
+
(20..50).include?(user.current_level)
|
96
|
+
end
|
97
|
+
# many codes here
|
98
|
+
end
|
99
|
+
```
|
100
|
+
|
101
|
+
### Event
|
102
|
+
when an event is triggered, it run `validate`. If passed, then it run `transition`. In the event of having multiple transitions, the first transition that successfully completes will stop other transitions to be executed.
|
103
|
+
```ruby
|
104
|
+
class UserStateMachine < Jsm::Base
|
105
|
+
attribute_name :level
|
106
|
+
|
107
|
+
state :beginner
|
108
|
+
state :intermediate
|
109
|
+
state :master
|
110
|
+
|
111
|
+
event :upgrade_title do
|
112
|
+
transition from: [:beginner], to: :intermediate
|
113
|
+
transition from: [:intermediate], to: :master
|
114
|
+
end
|
115
|
+
|
116
|
+
event :downgrade_title do
|
117
|
+
transition from: :intermediate, to: :beginner
|
118
|
+
transition from: :master, to: :intermediate
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Client Class
|
123
|
+
class User
|
124
|
+
include Jsm::Client
|
125
|
+
jsm_use UserStateMachine # your state machine class here
|
126
|
+
|
127
|
+
attr_accessor :title # same with attribute_name in UserStateMachine
|
128
|
+
def initialize
|
129
|
+
@title = :beginner
|
130
|
+
@level = 1
|
131
|
+
end
|
132
|
+
#your code here
|
133
|
+
end
|
134
|
+
|
135
|
+
user = User.new
|
136
|
+
user.title # :beginner
|
137
|
+
user.upgrade_title # true
|
138
|
+
user.title # :intermediate
|
139
|
+
```
|
140
|
+
|
141
|
+
## Active Model Integration
|
142
|
+
```ruby
|
143
|
+
class UserStateMachine < Jsm::Base
|
144
|
+
attribute_name :title
|
145
|
+
|
146
|
+
state :unconfirmed
|
147
|
+
state :beginner
|
148
|
+
state :intermediate
|
149
|
+
state :master
|
150
|
+
|
151
|
+
validate :intermediate do |user|
|
152
|
+
unless (20..50).include?(user.current_level)
|
153
|
+
user.errors.add(:title, 'is not between 20 and 50')
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
validate :master do |user|
|
158
|
+
unless user.current_level > 50
|
159
|
+
user.errors.add(:title, 'have not reached 50')
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
event :upgrade_title do
|
164
|
+
transition from: [:beginner], to: :intermediate
|
165
|
+
transition from: [:intermediate], to: :master
|
166
|
+
end
|
167
|
+
|
168
|
+
event :downgrade_title do
|
169
|
+
transition from: :intermediate, to: :beginner
|
170
|
+
transition from: :master, to: :intermediate
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
# Client Class
|
175
|
+
class User
|
176
|
+
include ActiveModel::Model
|
177
|
+
include Jsm::Client
|
178
|
+
include Jsm::Client::ActiveModel
|
179
|
+
jsm_use UserStateMachine # your state machine class here
|
180
|
+
|
181
|
+
attr_accessor :title # same with attribute_name in UserStateMachine
|
182
|
+
attr_accessor :level
|
183
|
+
def initialize
|
184
|
+
@title = :beginner
|
185
|
+
@level = 1
|
186
|
+
end
|
187
|
+
#your code here
|
188
|
+
end
|
189
|
+
```
|
190
|
+
|
191
|
+
`Jsm` support `ActiveModel`. In the `client` class include the `Jsm::Client::ActiveModel`. when run an event. It will auto saved the object.
|
192
|
+
|
193
|
+
### Validation
|
194
|
+
It also support validation from `ActiveModel` . Validation checked based on `errors` value in the `instance`. you can add an error to the errors object. This will prevent the state from being changed
|
195
|
+
```
|
196
|
+
user = User.new
|
197
|
+
user.level # 1
|
198
|
+
user.level = 18
|
199
|
+
user.upgrade_title # false
|
200
|
+
user.errors[:title] # ["is not between 20 and 50"]
|
201
|
+
```
|
202
|
+
## ActiveRecord Integration
|
203
|
+
```ruby
|
204
|
+
class UserStateMachine < Jsm::Base
|
205
|
+
attribute_name :title
|
206
|
+
|
207
|
+
state :unconfirmed
|
208
|
+
state :beginner
|
209
|
+
state :intermediate
|
210
|
+
state :master
|
211
|
+
|
212
|
+
validate :intermediate do |user|
|
213
|
+
unless (20..50).include?(user.current_level)
|
214
|
+
user.errors.add(:title, 'is not between 20 and 50')
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
validate :master do |user|
|
219
|
+
unless user.current_level > 50
|
220
|
+
errors.add(:title, 'have not reached 50')
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
event :upgrade_title do
|
225
|
+
transition from: [:beginner], to: :intermediate
|
226
|
+
transition from: [:intermediate], to: :master
|
227
|
+
end
|
228
|
+
|
229
|
+
event :downgrade_title do
|
230
|
+
transition from: :intermediate, to: :beginner
|
231
|
+
transition from: :master, to: :intermediate
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Client Class
|
236
|
+
class User
|
237
|
+
include ActiveModel::Model
|
238
|
+
include Jsm::Client
|
239
|
+
include Jsm::Client::ActiveModel
|
240
|
+
jsm_use UserStateMachine # your state machine class here
|
241
|
+
|
242
|
+
attr_accessor :title # same with attribute_name in UserStateMachine
|
243
|
+
attr_accessor :level
|
244
|
+
def initialize
|
245
|
+
@title = :beginner
|
246
|
+
@level = 1
|
247
|
+
end
|
248
|
+
#your code here
|
249
|
+
end
|
250
|
+
```
|
251
|
+
`Jsm` support `ActiveRecord`. In the `client` class include the `Jsm::Client::ActiveRecord`. It also support `ActiveRecord` Validation. The behavior is same with `ActiveModel` client.
|
252
|
+
|
253
|
+
## Development
|
254
|
+
|
255
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
256
|
+
|
257
|
+
## Contributing
|
258
|
+
|
259
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wendy0402/jsm. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
260
|
+
|
261
|
+
|
262
|
+
## License
|
263
|
+
|
264
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "jsm"
|
5
|
+
Dir[File.expand_path("../../examples/**/*.rb",__FILE__)].each{ |f| require f }
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
11
|
+
# require "pry"
|
12
|
+
# Pry.start
|
13
|
+
|
14
|
+
require "irb"
|
15
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
class ActiveRecordSM < Jsm::Base
|
3
|
+
attribute_name :relationship
|
4
|
+
|
5
|
+
state "single"
|
6
|
+
state "in_relationship"
|
7
|
+
state "married"
|
8
|
+
state "divorced"
|
9
|
+
state "widowed"
|
10
|
+
|
11
|
+
event :start_dating do
|
12
|
+
transition from: "single", to: "in_relationship"
|
13
|
+
end
|
14
|
+
|
15
|
+
event :marry do
|
16
|
+
transition from: ["single", "in_relationship"], to: "married"
|
17
|
+
end
|
18
|
+
|
19
|
+
event :cheating do
|
20
|
+
transition from: "in_relationship", to: "single"
|
21
|
+
transition from: "married", to: "divorced"
|
22
|
+
end
|
23
|
+
|
24
|
+
event :divorce do
|
25
|
+
transition from: "married", to: "divorced"
|
26
|
+
end
|
27
|
+
|
28
|
+
validate "married" do |user|
|
29
|
+
unless user.approved_by_parents
|
30
|
+
user.errors.add(:approved_by_parents, 'can not marry, you havent been approved')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
ActiveRecord::Base.configurations = {
|
36
|
+
'db1' => {
|
37
|
+
:adapter => 'sqlite3',
|
38
|
+
:encoding => 'utf8',
|
39
|
+
:database => ':memory:',
|
40
|
+
}
|
41
|
+
}
|
42
|
+
ActiveRecord::Base.establish_connection(:db1)
|
43
|
+
|
44
|
+
class UserAR < ActiveRecord::Base
|
45
|
+
include Jsm::Client
|
46
|
+
include Jsm::Client::ActiveRecord
|
47
|
+
self.table_name = 'users'
|
48
|
+
|
49
|
+
validates_presence_of :age
|
50
|
+
jsm_use ActiveRecordSM
|
51
|
+
end
|
52
|
+
|
53
|
+
UserAR.connection.drop_table('users') if UserAR.connection.table_exists?(:users)
|
54
|
+
UserAR.connection.create_table :users do |a|
|
55
|
+
a.string :relationship
|
56
|
+
a.string :name
|
57
|
+
a.boolean :approved_by_parents
|
58
|
+
a.text :age
|
59
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class UserStateMachine < Jsm::Base
|
2
|
+
attribute_name :relationship
|
3
|
+
|
4
|
+
state :single
|
5
|
+
state :in_relationship
|
6
|
+
state :married
|
7
|
+
state :divorced
|
8
|
+
state :widowed
|
9
|
+
|
10
|
+
event :start_dating do
|
11
|
+
transition from: :single, to: :in_relationship
|
12
|
+
end
|
13
|
+
|
14
|
+
event :marry do
|
15
|
+
transition from: [:single, :in_relationship], to: :married
|
16
|
+
end
|
17
|
+
|
18
|
+
event :cheating do
|
19
|
+
transition from: :in_relationship, to: :single
|
20
|
+
transition from: :married, to: :divorced
|
21
|
+
end
|
22
|
+
|
23
|
+
event :divorce do
|
24
|
+
transition from: :married, to: :divorced
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class UserBasic
|
30
|
+
include Jsm::Client
|
31
|
+
jsm_use UserStateMachine
|
32
|
+
|
33
|
+
attr_accessor :relationship
|
34
|
+
end
|
data/jsm.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'jsm/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "just_state_machine"
|
8
|
+
spec.version = Jsm::VERSION
|
9
|
+
spec.authors = ["wendy0402"]
|
10
|
+
spec.email = ["wendykurniawan92@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "Just State Machine, State Machine That support custom validations, it also known with Jsm"
|
13
|
+
spec.description = "Just StateMachine"
|
14
|
+
spec.homepage = "https://github.com/wendy0402/jsm"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
|
18
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.10"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec"
|
26
|
+
spec.add_development_dependency "pry"
|
27
|
+
spec.add_development_dependency "pry-doc"
|
28
|
+
spec.add_development_dependency "looksee"
|
29
|
+
spec.add_development_dependency "activerecord", "~> 4.1"
|
30
|
+
spec.add_development_dependency "sqlite3"
|
31
|
+
end
|
data/lib/jsm.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Jsm
|
2
|
+
require "jsm/version"
|
3
|
+
require "jsm/states"
|
4
|
+
require "jsm/event"
|
5
|
+
require "jsm/exceptions"
|
6
|
+
require "jsm/machines"
|
7
|
+
require "jsm/base"
|
8
|
+
require "jsm/client"
|
9
|
+
require "jsm/client/active_model"
|
10
|
+
require "jsm/client/active_record"
|
11
|
+
require "jsm/client_extension"
|
12
|
+
require "jsm/validator"
|
13
|
+
require "jsm/validators"
|
14
|
+
|
15
|
+
module EventExecutor
|
16
|
+
require "jsm/event_executor/base"
|
17
|
+
require "jsm/event_executor/active_model"
|
18
|
+
require "jsm/event_executor/active_record"
|
19
|
+
end
|
20
|
+
end
|
data/lib/jsm/base.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# this module used as extension for state machine class
|
2
|
+
# The DSL is built to define the state, event, and transition that happen
|
3
|
+
class Jsm::Base
|
4
|
+
|
5
|
+
# define attribute name of state attribute in the client class
|
6
|
+
def self.attribute_name(attribute_name = nil)
|
7
|
+
if attribute_name.nil?
|
8
|
+
@attribute_name
|
9
|
+
else
|
10
|
+
@attribute_name = attribute_name
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# add new state to class
|
15
|
+
# example
|
16
|
+
# state :x
|
17
|
+
# state :y
|
18
|
+
def self.state(name, params = {})
|
19
|
+
@states ||= Jsm::States.new
|
20
|
+
@states.add_state(name, initial: params[:initial])
|
21
|
+
end
|
22
|
+
|
23
|
+
# list of all states
|
24
|
+
def self.states
|
25
|
+
@states.list
|
26
|
+
end
|
27
|
+
|
28
|
+
# add new event to the class and add its transition
|
29
|
+
# example:
|
30
|
+
# event :do_this do
|
31
|
+
# transition from: :x, to: :y
|
32
|
+
# transition from: [:j, :g], to: :z
|
33
|
+
def self.event(name, &block)
|
34
|
+
@events ||= {}
|
35
|
+
if !@events[name].nil?
|
36
|
+
raise Jsm::InvalidEventError, "event #{name} has been registered"
|
37
|
+
end
|
38
|
+
|
39
|
+
@events[name] = Jsm::Event.new(name, states: @states, &block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# get list of all events
|
43
|
+
def self.events
|
44
|
+
@events
|
45
|
+
end
|
46
|
+
|
47
|
+
# add validation of a state(when changes to the targeted state, check whether passed this validation or not)
|
48
|
+
# example:
|
49
|
+
# state :y
|
50
|
+
# validate :y do |obj|
|
51
|
+
# obj.name == 'testMe'
|
52
|
+
# end
|
53
|
+
def self.validate(state_name, &block)
|
54
|
+
unless @states.has_state?(state_name)
|
55
|
+
raise Jsm::InvalidStateError, "there is no state y"
|
56
|
+
end
|
57
|
+
|
58
|
+
@validators ||= Jsm::Validators.new
|
59
|
+
@validators.add_validator(state_name, Jsm::Validator.new(:state, state_name, &block))
|
60
|
+
end
|
61
|
+
|
62
|
+
# list all validators that exist
|
63
|
+
def self.validators
|
64
|
+
@validators
|
65
|
+
end
|
66
|
+
|
67
|
+
def initialize(klass)
|
68
|
+
@klass = klass
|
69
|
+
Jsm::ClientExtension.decorate(@klass, state_machine: self.class)
|
70
|
+
end
|
71
|
+
end
|
data/lib/jsm/client.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Jsm::Client
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
base.send(:include, InstanceMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module InstanceMethods
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval <<-EOFILE, __FILE__, __LINE__
|
10
|
+
private :jsm_set_state
|
11
|
+
EOFILE
|
12
|
+
end
|
13
|
+
|
14
|
+
def state_machine
|
15
|
+
self.class.respond_to?(:state_machine) ? self.class.state_machine : nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# able to get current state from here instead check from the targeted attribute
|
19
|
+
def current_state
|
20
|
+
attr_state = state_machine.attribute_name
|
21
|
+
instance_variable_get("@#{attr_state}".to_sym)
|
22
|
+
end
|
23
|
+
|
24
|
+
# used for set new state by JSM
|
25
|
+
def jsm_set_state(val)
|
26
|
+
attr_state = state_machine.attribute_name
|
27
|
+
instance_variable_set("@#{attr_state}".to_sym, val)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
def jsm_use(state_machine)
|
33
|
+
self.class_eval <<-EODEF, __FILE__, __LINE__
|
34
|
+
def self.state_machine
|
35
|
+
#{state_machine}
|
36
|
+
end
|
37
|
+
EODEF
|
38
|
+
Jsm::Machines.add_machines(self, state_machine.new(self))
|
39
|
+
end
|
40
|
+
|
41
|
+
#define type of event executor to be used
|
42
|
+
def jsm_event_executor
|
43
|
+
Jsm::EventExecutor::Base
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Jsm::Client::ActiveModel
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
base.send(:include, InstanceMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module InstanceMethods
|
8
|
+
def current_state
|
9
|
+
attr_state = state_machine.attribute_name
|
10
|
+
send(attr_state)
|
11
|
+
end
|
12
|
+
|
13
|
+
def jsm_set_state(val)
|
14
|
+
attr_state = state_machine.attribute_name
|
15
|
+
send("#{attr_state}=", val)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def jsm_event_executor
|
21
|
+
Jsm::EventExecutor::ActiveModel
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Jsm::Client::ActiveRecord
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
# Basically ActiveModel and ActiveRecord almost have the same behavior for its instance
|
5
|
+
base.send(:include, Jsm::Client::ActiveModel::InstanceMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def jsm_event_executor
|
10
|
+
Jsm::EventExecutor::ActiveRecord
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Jsm::ClientExtension define all custom methods for the model that use the state machine class
|
2
|
+
# E.g: Define method for states
|
3
|
+
class Jsm::ClientExtension
|
4
|
+
def self.decorate(klass, params = {})
|
5
|
+
client_extension = new(klass, params)
|
6
|
+
client_extension.define_states_method
|
7
|
+
client_extension.define_event_method
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :klass, :state_machine, :event_executor
|
11
|
+
def initialize(klass, params = {})
|
12
|
+
@klass = klass
|
13
|
+
@state_machine = params[:state_machine]
|
14
|
+
@event_executor = klass.jsm_event_executor.new(validators: @state_machine.validators)
|
15
|
+
unless @state_machine.attribute_name
|
16
|
+
raise Jsm::NoAttributeError, "please assign the attribute_name first in class #{@state_machine.name}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# define method for all states to check status equal with a states
|
21
|
+
# e.g: states: [:x, :y]
|
22
|
+
# it will define method `x?` and `y?`
|
23
|
+
def define_states_method
|
24
|
+
state_machine.states.each do |state|
|
25
|
+
state_name = state.name
|
26
|
+
klass.send(:define_method, "#{state_name}?".to_sym) do
|
27
|
+
self.current_state == state_name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# define all event that has been defined
|
33
|
+
# it consists of 3 types of method:
|
34
|
+
# * method to check whether can do event( e.g: can_married? )
|
35
|
+
# * method to run an event and return bollean( e.g: married )
|
36
|
+
# * method to run an event and raise error if failed( e.g: married! )
|
37
|
+
def define_event_method
|
38
|
+
state_machine.events.each do |event_name, event|
|
39
|
+
event.attribute_name = state_machine.attribute_name
|
40
|
+
define_can_event_method(event_name, event)
|
41
|
+
define_event_execution_method(event_name, event)
|
42
|
+
define_event_execution_method!(event_name, event)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
def define_can_event_method(event_name, event)
|
48
|
+
executor = event_executor
|
49
|
+
klass.send(:define_method, "can_#{event_name}?") do
|
50
|
+
executor.can_be_executed?(event, self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def define_event_execution_method(event_name, event)
|
55
|
+
executor = event_executor
|
56
|
+
klass.send(:define_method, "#{event_name}") do
|
57
|
+
executor.execute(event, self)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def define_event_execution_method!(event_name, event)
|
62
|
+
executor = event_executor
|
63
|
+
klass.send(:define_method, "#{event_name}!") do
|
64
|
+
executor.execute!(event, self)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/jsm/event.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
# Jsm::Event handle event related task registered by the main module.
|
2
|
+
# It do transition process from one state to another state
|
3
|
+
class Jsm::Event
|
4
|
+
attr_reader :name, :states, :transitions
|
5
|
+
attr_accessor :attribute_name
|
6
|
+
|
7
|
+
::Jsm::Transition = Struct.new(:from, :to)
|
8
|
+
def initialize(name, params = {}, &block)
|
9
|
+
@name = name
|
10
|
+
@states = params[:states]
|
11
|
+
@transitions = []
|
12
|
+
instance_eval(&block) if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
# register a transition into the. When Event is executed,
|
16
|
+
# these transitions is transitioning an object into `to` state,
|
17
|
+
# if their current state match with one of the `from` state.
|
18
|
+
# the argument input is params `from` and params `to`.
|
19
|
+
# Both params should be exist
|
20
|
+
def transition(params = {})
|
21
|
+
validate_params(params)
|
22
|
+
validate_state_transition(params)
|
23
|
+
from = params[:from].is_a?(Array) ? params[:from] : [params[:from]]
|
24
|
+
transition = Jsm::Transition.new(from, params[:to])
|
25
|
+
@transitions.push(transition)
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# execute the event, and do a transition
|
30
|
+
# if the object current state match with the from state of a transition
|
31
|
+
def execute(object)
|
32
|
+
transition = can_be_transitioning_to(object)
|
33
|
+
if transition
|
34
|
+
change_state_obj(object, transition.to)
|
35
|
+
true
|
36
|
+
else
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# check when running this event, which transitions can change the state of object state
|
42
|
+
# return the transition object
|
43
|
+
def can_be_transitioning_to(object)
|
44
|
+
transitions.find{ |transition| transition.from.include?(obj_state(object)) }
|
45
|
+
end
|
46
|
+
|
47
|
+
# method to check whether this event can be executed
|
48
|
+
def can_be_executed?(object)
|
49
|
+
from_states = transitions.map(&:from).flatten
|
50
|
+
from_states.include?(object.current_state)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def obj_state(object)
|
56
|
+
object.current_state
|
57
|
+
end
|
58
|
+
|
59
|
+
def change_state_obj(object, to_state)
|
60
|
+
object.send("jsm_set_state", to_state)
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_params(params = {})
|
64
|
+
from = params[:from]
|
65
|
+
if (from.respond_to?(:empty) && from.empty?) || !from
|
66
|
+
raise ArgumentError, "transition is invalid, missing required parameter from"
|
67
|
+
end
|
68
|
+
|
69
|
+
to = params[:to]
|
70
|
+
|
71
|
+
if (to.respond_to?(:empty) && to.empty?) || !to
|
72
|
+
raise ArgumentError, "transition is invalid, missing required parameter to"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_state_transition(params = {})
|
77
|
+
from = Array(params[:from])
|
78
|
+
to = params[:to]
|
79
|
+
invalid_state = from.select {|state_name| !states.has_state?(state_name) }
|
80
|
+
unless invalid_state.empty?
|
81
|
+
raise Jsm::InvalidTransitionError, "parameter from is invalid. there is no state #{invalid_state.join(', ')} in list"
|
82
|
+
end
|
83
|
+
|
84
|
+
unless states.has_state?(to)
|
85
|
+
raise Jsm::InvalidTransitionError, "parameter to is invalid. there is no state #{to} in list"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Jsm::EventExecutor::ActiveModel < Jsm::EventExecutor::Base
|
2
|
+
# it execute event for the object.
|
3
|
+
# If transition failed or invalid by validation toward the object,
|
4
|
+
# then it will return false
|
5
|
+
def execute(event, obj)
|
6
|
+
if can_be_executed?(event, obj)
|
7
|
+
event.execute(obj)
|
8
|
+
else
|
9
|
+
false
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# check if the obj possible to execute the event
|
14
|
+
def can_be_executed?(event, obj)
|
15
|
+
state = event.can_be_transitioning_to(obj)
|
16
|
+
attribute_name = obj.class.state_machine.attribute_name
|
17
|
+
obj.valid?
|
18
|
+
if state
|
19
|
+
validators.validate(state.to, obj)
|
20
|
+
else
|
21
|
+
obj.errors.add(attribute_name, 'no transitions match')
|
22
|
+
end
|
23
|
+
obj.errors.empty?
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Jsm::EventExecutor::ActiveRecord < Jsm::EventExecutor::ActiveModel
|
2
|
+
def execute(event, obj)
|
3
|
+
if can_be_executed?(event, obj)
|
4
|
+
result = false
|
5
|
+
# do transaction to prevent shit happen
|
6
|
+
ActiveRecord::Base.transaction do
|
7
|
+
obj.class.lock
|
8
|
+
event.execute(obj)
|
9
|
+
result = obj.save
|
10
|
+
end
|
11
|
+
result
|
12
|
+
else
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# this class is the base for adapter
|
2
|
+
# it can be extended for ActiveRecord adapter
|
3
|
+
class Jsm::EventExecutor::Base
|
4
|
+
attr_reader :validators
|
5
|
+
def initialize(params = {})
|
6
|
+
@validators = params[:validators] || Jsm::Validators.new
|
7
|
+
end
|
8
|
+
|
9
|
+
# it execute event for the object.
|
10
|
+
# If transition failed or invalid by validation toward the object,
|
11
|
+
# then it will return false
|
12
|
+
def execute(event, obj)
|
13
|
+
can_be_executed?(event, obj) ? event.execute(obj) : false
|
14
|
+
end
|
15
|
+
|
16
|
+
# check if the obj possible to execute the event(passed the validation and can do transition)
|
17
|
+
def can_be_executed?(event, obj)
|
18
|
+
state = event.can_be_transitioning_to(obj)
|
19
|
+
!!state && validators.validate(state.to, obj)
|
20
|
+
end
|
21
|
+
|
22
|
+
# same with execute, but if its failed raise error
|
23
|
+
def execute!(event, obj)
|
24
|
+
unless execute(event, obj)
|
25
|
+
raise Jsm::IllegalTransitionError, "there is no matching transitions or invalid, Cant do event #{event.name}"
|
26
|
+
end
|
27
|
+
true
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
class Jsm::NotUniqueStateError < StandardError; end
|
2
|
+
class Jsm::InvalidStateError < StandardError; end
|
3
|
+
|
4
|
+
class Jsm::InvalidTransitionError < StandardError; end
|
5
|
+
class Jsm::IllegalTransitionError < StandardError;end
|
6
|
+
|
7
|
+
class Jsm::InvalidEventError < StandardError;end
|
8
|
+
|
9
|
+
class Jsm::NoAttributeError < StandardError;end
|
data/lib/jsm/machines.rb
ADDED
data/lib/jsm/states.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Jsm::States job is to collect all states
|
2
|
+
class Jsm::States
|
3
|
+
attr_reader :list
|
4
|
+
::Jsm::State = Struct.new(:name, :initial)
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@list = []
|
8
|
+
end
|
9
|
+
|
10
|
+
# register new state into the list
|
11
|
+
# @param state_name
|
12
|
+
def add_state(state_name, params = {})
|
13
|
+
initial = params.fetch(:initial) { false }
|
14
|
+
if !state_unique?(state_name)
|
15
|
+
raise Jsm::NotUniqueStateError, "state #{state_name} has been defined"
|
16
|
+
end
|
17
|
+
|
18
|
+
state = create_state(state_name, initial)
|
19
|
+
list.push(state)
|
20
|
+
end
|
21
|
+
|
22
|
+
def has_state?(state_name)
|
23
|
+
list.any? { |state| state.name == state_name}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def create_state(state_name, initial)
|
29
|
+
Jsm::State.new(state_name, initial)
|
30
|
+
end
|
31
|
+
|
32
|
+
def state_unique?(state_name)
|
33
|
+
list.all? {|state| state.name != state_name }
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Jsm::Validators
|
2
|
+
def initialize
|
3
|
+
@list = Hash.new { |validators, state| validators[state] = [] }
|
4
|
+
end
|
5
|
+
|
6
|
+
def add_validator(name, validator)
|
7
|
+
@list[name].push(validator)
|
8
|
+
end
|
9
|
+
|
10
|
+
def [](name)
|
11
|
+
@list[name]
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate(name, obj)
|
15
|
+
@list[name].all? { |validator| validator.validate(obj) }
|
16
|
+
end
|
17
|
+
end
|
data/lib/jsm/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: just_state_machine
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- wendy0402
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-10-17 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.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
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: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
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: pry-doc
|
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
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: looksee
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activerecord
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '4.1'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '4.1'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: Just StateMachine
|
126
|
+
email:
|
127
|
+
- wendykurniawan92@gmail.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- ".rspec"
|
134
|
+
- ".travis.yml"
|
135
|
+
- CODE_OF_CONDUCT.md
|
136
|
+
- Gemfile
|
137
|
+
- LICENSE.txt
|
138
|
+
- README.md
|
139
|
+
- Rakefile
|
140
|
+
- bin/console
|
141
|
+
- bin/setup
|
142
|
+
- examples/active_record_state_machine.rb
|
143
|
+
- examples/simple_state_machine.rb
|
144
|
+
- jsm.gemspec
|
145
|
+
- lib/jsm.rb
|
146
|
+
- lib/jsm/base.rb
|
147
|
+
- lib/jsm/client.rb
|
148
|
+
- lib/jsm/client/active_model.rb
|
149
|
+
- lib/jsm/client/active_record.rb
|
150
|
+
- lib/jsm/client_extension.rb
|
151
|
+
- lib/jsm/event.rb
|
152
|
+
- lib/jsm/event_executor/active_model.rb
|
153
|
+
- lib/jsm/event_executor/active_record.rb
|
154
|
+
- lib/jsm/event_executor/base.rb
|
155
|
+
- lib/jsm/exceptions.rb
|
156
|
+
- lib/jsm/machines.rb
|
157
|
+
- lib/jsm/states.rb
|
158
|
+
- lib/jsm/validator.rb
|
159
|
+
- lib/jsm/validators.rb
|
160
|
+
- lib/jsm/version.rb
|
161
|
+
homepage: https://github.com/wendy0402/jsm
|
162
|
+
licenses:
|
163
|
+
- MIT
|
164
|
+
metadata: {}
|
165
|
+
post_install_message:
|
166
|
+
rdoc_options: []
|
167
|
+
require_paths:
|
168
|
+
- lib
|
169
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
|
+
requirements:
|
176
|
+
- - ">="
|
177
|
+
- !ruby/object:Gem::Version
|
178
|
+
version: '0'
|
179
|
+
requirements: []
|
180
|
+
rubyforge_project:
|
181
|
+
rubygems_version: 2.4.3
|
182
|
+
signing_key:
|
183
|
+
specification_version: 4
|
184
|
+
summary: Just State Machine, State Machine That support custom validations, it also
|
185
|
+
known with Jsm
|
186
|
+
test_files: []
|
187
|
+
has_rdoc:
|