stateful-simple 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: 9fe68e13582d8019215abdbe923c7c2cbbbca3ca
4
+ data.tar.gz: 7f6f02d7f9dba92e7942c00de3d2c5e9530e8c9e
5
+ SHA512:
6
+ metadata.gz: 2cfb5c2f1245d89312ab0b105c1de27226fa230547070f389e5b32bd822a151397511e4fb536646a60a96c1a77a9b19bc75c1f41a5a09586cda70a12fb06e5d8
7
+ data.tar.gz: 33ff7b160a3bee7fd366cd5c991bdc32bf976d0e971af573360f437ebc6c1aecf1ce99c349326c152c6e51ca3088e86e1d16860bb23ab550b11dfd390932dbd1
@@ -0,0 +1,20 @@
1
+ Copyright 2018 Chris Liaw
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,81 @@
1
+ # Stateful
2
+
3
+ Stateful is inspired by event machine....I think...
4
+
5
+ It was an old library (> 5 years) converted to gem to facilitate state transfer of a record from one to another.
6
+
7
+ Idea is to have different chained state and its associated action that causes the state changed.
8
+
9
+ For example, in real world visualization, an user account's status can be seen from
10
+ ```
11
+ (event: activate)
12
+ active -------------------->>> inactive
13
+ <<<--------------------
14
+ (event: inactivate)
15
+ ```
16
+
17
+ Hence the activate event/action will cause the user state changed from active to inactive and inactivate event/action shall cause the user state changed from inactive to active.
18
+
19
+ The major usage of this gem is now at the view of the rails application, the developer shall know what is the next possible action that could display to the user.
20
+ Now system can prompt user "inactivate user" instead of edit screen via "change status" link and select another status from the list.
21
+
22
+
23
+ ## Usage
24
+ Stateful tied to ActiveRecord. In order to use:
25
+
26
+ ```ruby
27
+ class User < ApplicationRecord
28
+ # Indicate that stateful gem to be used. With initial status set to "active" (:active.to_s)
29
+ stateful initial: :active
30
+
31
+ # transform is state transfer specification, from state :active to :inactive
32
+ transform :active => :inactive do
33
+ # forward means from :active to :inactive, the event name is "inactive"
34
+ forward :inactivate
35
+ # backward means from :inactive to :active, the event name is "active"
36
+ backward :activate
37
+ end
38
+
39
+ # if there are other state transfer specification, can add more...
40
+
41
+ end
42
+ ```
43
+ Noted however that the database field used by the stateful is 'state' (string field).
44
+
45
+ Once the model is activated with stateful, it can be changed its state by invoking the appropriate event/action by invoking the event name with exclamation mark
46
+
47
+ ```ruby
48
+ @model.<event/action name>!
49
+ ```
50
+ or
51
+ ```ruby
52
+ @model.send "#{<event/action name>}!"
53
+ ```
54
+
55
+ Example to change the status from "active" to "inactive"
56
+ ```ruby
57
+ @model.inactivate!
58
+ ```
59
+
60
+ In the event that the action is not actually the next possible action, the status remained untouched and false shall be returned.
61
+
62
+
63
+ ## Installation
64
+ Add this line to your application's Gemfile:
65
+
66
+ ```ruby
67
+ gem 'stateful'
68
+ ```
69
+
70
+ And then execute:
71
+ ```bash
72
+ $ bundle
73
+ ```
74
+
75
+ Or install it yourself as:
76
+ ```bash
77
+ $ gem install stateful
78
+ ```
79
+
80
+ ## License
81
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Stateful'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ require 'bundler/gem_tasks'
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,2 @@
1
+ //= link_directory ../javascripts/stateful .js
2
+ //= link_directory ../stylesheets/stateful .css
@@ -0,0 +1,13 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file. JavaScript code in this file should be added after the last require_* statement.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,5 @@
1
+ module Stateful
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Stateful
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Stateful
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module Stateful
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Stateful
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Stateful</title>
5
+ <%= stylesheet_link_tag "stateful/application", media: "all" %>
6
+ <%= javascript_include_tag "stateful/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+
11
+ <%= yield %>
12
+
13
+ </body>
14
+ </html>
@@ -0,0 +1,2 @@
1
+ Stateful::Engine.routes.draw do
2
+ end
@@ -0,0 +1,187 @@
1
+ require "stateful/engine"
2
+
3
+ module Stateful
4
+ class Event
5
+ attr_accessor :new_events
6
+ def initialize(states,es_table)
7
+ # mapping of { :event => [{:transition => {:from_state => :to_state}, :guard => Proc..., },
8
+ # {:transition => {:from_state => :to_state}, :guard => Proc...,}
9
+ # ] }
10
+ @es_table = es_table
11
+ @states = states
12
+ @new_events = []
13
+ end
14
+
15
+ def forward(event,opts={})
16
+ #puts "Forward Event : #{event}"
17
+ #puts "States : #{@states}"
18
+ if @es_table[event] == nil
19
+ @new_events << event
20
+ @es_table[event] = []
21
+ end
22
+ @es_table[event] << { :transition => { @states.keys[0] => @states.values[0] }, :guard => opts[:guard] }
23
+ #p @event_states
24
+ end
25
+
26
+ def backward(event,opts={})
27
+ #puts "Backward Event : #{event}"
28
+ #puts "States : #{@states}"
29
+ if @es_table[event] == nil
30
+ @new_events << event
31
+ @es_table[event] = []
32
+ end
33
+ @es_table[event] << { :transition => { @states.values[0] => @states.keys[0] }, :guard => opts[:guard] }
34
+ #p @event_states
35
+ end
36
+
37
+ def fire_event(event,record,auto_commit = true)
38
+ current = record.state.to_sym
39
+ #p record.event_states_table[event]
40
+ @success = false
41
+ #record.event_states_table[event].each do |evt|
42
+ @es_table[event].each do |evt|
43
+ if evt[:transition].keys[0] == current
44
+ if evt[:guard] != nil
45
+ if evt[:guard].call(record)
46
+ record.state = evt[:transition].values[0].to_s if evt[:transition].keys[0] == current
47
+ record.save if auto_commit # to comply to '!' notication of the method
48
+ @success = true
49
+ break # break here to honour the first guard found and return true
50
+ end
51
+ else
52
+ record.state = evt[:transition].values[0].to_s if evt[:transition].keys[0] == current
53
+ record.save if auto_commit # to comply to '!' notication of the method
54
+ @success = true
55
+ end
56
+ end
57
+ end
58
+ @success
59
+ end
60
+ end
61
+ # end Event class
62
+ end
63
+
64
+ module Stateful
65
+ module StateMachine
66
+ extend ActiveSupport::Concern
67
+
68
+ #mattr_accessor :_states, :initial, :options, :event_states_table
69
+
70
+ included do
71
+ end
72
+
73
+ module ClassMethods
74
+
75
+ def stateful(opts = {})
76
+ options = {
77
+ initial: "open",
78
+ column_name: "state"
79
+ }
80
+
81
+ options.merge!(opts)
82
+ #opts[:initial] = "open" if opts[:initial] == nil or opts[:initial].empty?
83
+ #opts[:column_name] = "state"
84
+
85
+ #@@_states = []
86
+ #@@initial = options[:initial]
87
+ #@@options = options
88
+ #@@event_states_table = {}
89
+ self.class_variable_set(:@@_states,[])
90
+ #self.class_variable_set(:@@initial,opts[:initial])
91
+ self.class_variable_set(:@@initial,options[:initial])
92
+ self.class_variable_set(:@@options, options)
93
+ self.class_variable_set(:@@event_states_table,{})
94
+ end
95
+
96
+ def transform(states,opts={},&block)
97
+ #puts "From state : #{states.keys[0]}"
98
+ _states = class_variable_get :@@_states
99
+ event_states_table = class_variable_get :@@event_states_table
100
+ _states << states.keys[0].to_sym if !_states.include?(states.keys[0].to_sym)
101
+ #puts "To state : #{states.values[0]}"
102
+ _states << states.values[0].to_sym if !_states.include?(states.values[0].to_sym)
103
+ e = Event.new(states,event_states_table)
104
+ e.instance_eval(&block) if block
105
+ e.new_events.each do |evt|
106
+ define_method("#{evt}!") {
107
+ e.fire_event(evt,self)
108
+ }
109
+
110
+ define_method("#{evt}") {
111
+ e.fire_event(evt,self,false)
112
+ }
113
+ end
114
+ #puts "Event states table"
115
+ #p event_states_table
116
+ end
117
+
118
+ def states
119
+ class_variable_get :@@_states
120
+ end
121
+
122
+ end # end ClassMethods
123
+
124
+ # instance methods
125
+ def possible_events
126
+ @events = []
127
+ opts = self.class.class_variable_get :@@options
128
+ if self.has_attribute?(opts[:column_name].to_sym)
129
+
130
+ @current = send(opts[:column_name].to_sym).to_sym #self.state.to_sym
131
+ event_states_table = self.class.class_variable_get :@@event_states_table
132
+ event_states_table.keys.each do |k|
133
+ event_states_table[k].each do |s|
134
+ if s[:transition].keys[0] == @current and !@events.include?(k)
135
+ if s[:guard] != nil
136
+ puts "guard result #{s[:guard].call(self)} for #{k}"
137
+ if s[:guard].call(self)
138
+ @events << k
139
+ end
140
+ else
141
+ @events << k
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ end
148
+ @events
149
+ end
150
+ alias :next_actions :possible_events
151
+
152
+ def initialize(*arg)
153
+ super(*arg)
154
+ # 23 Aug 2014 - guard is to handle migration
155
+ # If a model is not stateful but later it become stateful in subsequent migration script
156
+ # and at the same time, there are default data created in the initial migration
157
+ # script which creating new records. Migration will failed since there is no
158
+ # state column. State column only exist further down the migration.
159
+ # If the state field does not exist, just skipped that first. After all, it is not
160
+ # important by the time initial migration scirpt was run
161
+ begin
162
+ opts = self.class.class_variable_get :@@options
163
+ #opts = @@options
164
+ if self.attributes.keys.include? opts[:column_name].to_s
165
+ if self.class.class_variable_defined? :@@initial
166
+ self.send("#{opts[:column_name]}=", opts[:initial])
167
+ #self.state = self.class.class_variable_get :@@initial
168
+ #self.state = self.state.to_s if self.state != nil
169
+ end
170
+ end
171
+
172
+ rescue Exception => ex
173
+ end
174
+ #opts = self.class.class_variable_get :@@options
175
+ ##opts = @@options
176
+ #if self.attributes.keys.include? opts[:column_name].to_s
177
+ # if self.class.class_variable_defined? :@@initial
178
+ # self.send("#{opts[:column_name]}=", opts[:initial])
179
+ # #self.state = self.class.class_variable_get :@@initial
180
+ # #self.state = self.state.to_s if self.state != nil
181
+ # end
182
+ #end
183
+ end
184
+
185
+ end
186
+ end
187
+
@@ -0,0 +1,12 @@
1
+ module Stateful
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Stateful
4
+
5
+ config.to_prepare do
6
+ # include into ApplicationController
7
+ #ActionController::Base.send :include, Canopus::Concerns::Controllers::Authenticator
8
+ ActiveRecord::Base.send :include, Stateful::StateMachine
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module Stateful
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :stateful do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stateful-simple
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Chris Liaw
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-05-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
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: ''
42
+ email:
43
+ - chrisliaw@antrapol.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - MIT-LICENSE
49
+ - README.md
50
+ - Rakefile
51
+ - app/assets/config/stateful_manifest.js
52
+ - app/assets/javascripts/stateful/application.js
53
+ - app/assets/stylesheets/stateful/application.css
54
+ - app/controllers/stateful/application_controller.rb
55
+ - app/helpers/stateful/application_helper.rb
56
+ - app/jobs/stateful/application_job.rb
57
+ - app/mailers/stateful/application_mailer.rb
58
+ - app/models/stateful/application_record.rb
59
+ - app/views/layouts/stateful/application.html.erb
60
+ - config/routes.rb
61
+ - lib/stateful.rb
62
+ - lib/stateful/engine.rb
63
+ - lib/stateful/version.rb
64
+ - lib/tasks/stateful_tasks.rake
65
+ homepage: ''
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.5.2
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: ''
89
+ test_files: []