progressive 0.0.1

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: 4f5298ec0afc9ff8c78d6e31ca7c6c9627b13b73
4
+ data.tar.gz: e9c8610f56ff36f77c97ee6b30e64cedecb8f5f0
5
+ SHA512:
6
+ metadata.gz: cf893ff9135ed2235e5ac9121909805d3fe75e8643e60ee1328cfe33eb240efbfeab8696544bf87b21164001f6eaa435615133051ba0bb3b6158a05cc4f1285e
7
+ data.tar.gz: bff326e328e8a8881227c90a1913bdd4099c2dfac6d0cc12bfcaf7dad87a335bb3ca41fa2e0b4f13b2db0186bfa2d4012fe4ec0404cf97d9fa24fb5e726e3d93
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in progressive.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 dewski
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,151 @@
1
+ # Progressive
2
+
3
+ A simple ActiveModel backed state machine.
4
+
5
+ ## Why yet another state machine?
6
+
7
+ You may be asking why another state machine implementation? Well, first off YASM.
8
+ Second, most other implementations rely on class level attributes and make it
9
+ difficult for inheritance and the rare case when a subject will need to have
10
+ _multiple_ states.
11
+
12
+ If you only need to have 1 state for a model, Progressive works fine for that too.
13
+
14
+ Progressive interacts with the states and events for a model at an instance level,
15
+ not class level. There are no magic methods defined based on your states or events
16
+ it relies on method missing so it's very interoperable.
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ gem 'progressive'
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install progressive
31
+
32
+ ## Usage
33
+
34
+ ### Defining states on the subject model:
35
+
36
+ ```ruby
37
+ class State < Progressive::State
38
+
39
+ end
40
+
41
+ class User < ActiveRecord::Base
42
+ include Progression::Subject
43
+
44
+ has_many :states, :as => :subject, :dependent => :destroy
45
+
46
+ states do
47
+ state :pending do
48
+ event :potential
49
+ end
50
+
51
+ state :potential do
52
+ event :interview => :interviewing
53
+ event :archive => :archived
54
+ end
55
+
56
+ state :interviewing do
57
+ event :potential
58
+ event :archive => :archived
59
+ event :hire => :hired
60
+ end
61
+
62
+ state :hired
63
+ end
64
+
65
+ after_hire :user_rollup
66
+ before_interview :notify_creator
67
+
68
+ def user_rollup
69
+ # Do something
70
+ end
71
+
72
+ def notify_creator
73
+
74
+ end
75
+ end
76
+ ```
77
+
78
+ ### Interfacing with the state
79
+
80
+ ```ruby
81
+ actor = User.first
82
+ user = User.first
83
+
84
+ state = user.states.first
85
+ state.state # => 'pending'
86
+ state.default_state # => :pending
87
+ state.to(:archived, :actor => actor) # => false
88
+ state.to(:potential, :actor => actor) # => true
89
+ state.state # => 'potential'
90
+ state.specification # => Progressive::Specification
91
+ state.current_state # => Progressive::Specification::State
92
+ ```
93
+
94
+ ### Short circuiting an event
95
+
96
+ Since Progressive uses the same ActiveModel callbacks you're familiar with in
97
+ your ActiveRecord models, you can short circuit the event just by returning
98
+ a falsey statement within each callback.
99
+
100
+ You can add a before or after callback for any event. To see which events you
101
+ have available just check the Progressive specification:
102
+
103
+ ```ruby
104
+ Video.specification.event_names # => [:converting, :publish]
105
+ ```
106
+
107
+ Example:
108
+
109
+ ```ruby
110
+ class Video < ActiveRecord::Base
111
+ include Progression::Subject
112
+
113
+ has_one :state, :as => :subject, :dependent => :destroy
114
+
115
+ states do
116
+ state :pending do
117
+ event :converting
118
+ end
119
+
120
+ state :converting do
121
+ event :publish => :published
122
+ end
123
+
124
+ state :published
125
+ end
126
+
127
+ before_converting :valid_file_size?
128
+
129
+ def valid_file_size?
130
+ file_size < 1.gigabyte
131
+ end
132
+ end
133
+
134
+ video = Video.first
135
+ video.file_size # => 2.gigabytes
136
+ video.state # => :pending
137
+ video.converting # => false
138
+ video.state # => :pending
139
+ ```
140
+
141
+ ## Maintainers
142
+
143
+ - [@dewski](/dewski)
144
+
145
+ ## Contributing
146
+
147
+ 1. Fork it
148
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
149
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
150
+ 4. Push to the branch (`git push origin my-new-feature`)
151
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,9 @@
1
+ require 'progressive/version'
2
+
3
+ module Progressive
4
+ # Your code goes here...
5
+ end
6
+
7
+ require 'progressive/specification'
8
+ require 'progressive/subject'
9
+ require 'progressive/state'
@@ -0,0 +1,113 @@
1
+ module Progressive
2
+ class Specification
3
+ class State
4
+ attr_reader :events
5
+
6
+ def initialize(&block)
7
+ @events = {}
8
+ return unless block.present?
9
+ instance_eval(&block)
10
+ end
11
+
12
+ # Public: Defines events
13
+ #
14
+ # args - Can either be symbol (:potential) or hash (:archive => :archived)
15
+ #
16
+ # Returns Progression::Specification::Event
17
+ def event(*args)
18
+ name, to = args.first.is_a?(Symbol) ? args.first : args.first.to_a.flatten
19
+ @events[name.to_sym] = Event.new(name, to)
20
+ end
21
+
22
+ # Public: Determine if a given event exists.
23
+ #
24
+ # state - Event name to check for.
25
+ #
26
+ # Returns true if event exists, false if not.
27
+ def event?(state)
28
+ @events.key?(state.to_sym)
29
+ end
30
+ end
31
+
32
+ class Event
33
+ attr_reader :name
34
+ attr_reader :to
35
+
36
+ def initialize(name, to = nil)
37
+ @name = name
38
+ @to = to || name
39
+ end
40
+ end
41
+
42
+ attr_reader :options
43
+ attr_reader :states
44
+
45
+ # Public: Define the different states and events the subject can go through.
46
+ #
47
+ # options - The Hash options used to build the specification (default: {}):
48
+ # :default - The default state the subject is instantiated at (optional).
49
+ # block - A required block that is used to define states and events for
50
+ # the subject.
51
+ #
52
+ # Returns Progression::Specification
53
+ def initialize(options = {}, &block)
54
+ raise MissingConfiguration if block.nil?
55
+
56
+ @options = options
57
+ @states = {}
58
+
59
+ instance_eval(&block)
60
+ end
61
+
62
+ # Public: Determine if an event exists within the specification.
63
+ #
64
+ # event - Event to check for.
65
+ #
66
+ # Returns true if exists, false if not.
67
+ def event?(event)
68
+ event_names.include?(event.to_sym)
69
+ end
70
+
71
+ # Public: All possible events that can be applied to the subject. Doesn't
72
+ # gaurantee it can progress to said states, but that they exist for the
73
+ # subject.
74
+ #
75
+ # Returns Array of Symbols.
76
+ def event_names
77
+ @event_names ||= @states.collect do |_, state|
78
+ state.events.keys
79
+ end.flatten.uniq
80
+ end
81
+
82
+ # Public: Defines states
83
+ #
84
+ # state - The name of the state
85
+ # block - block that is used to define events for the state.
86
+ #
87
+ # Returns Progression::Specification::State
88
+ def state(state, &block)
89
+ @states[state.to_sym] = State.new(&block)
90
+ end
91
+
92
+ # Public: Determine if a given state has been defined.
93
+ #
94
+ # state - The state name to check for.
95
+ #
96
+ # Returns true if defined, false if not.
97
+ def state?(state)
98
+ @states.key?(state.to_sym)
99
+ end
100
+
101
+ # Public: Returns the default state for the specification.
102
+ #
103
+ # Returns symbol.
104
+ def default_state
105
+ @default_state ||=
106
+ if options.key?(:default)
107
+ options[:default]
108
+ elsif states.any?
109
+ states.keys.first
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,48 @@
1
+ module Progressive
2
+ class State < ActiveRecord::Base
3
+ belongs_to :subject, polymorphic: true
4
+
5
+ validates :subject_type, presence: true
6
+ validates :subject_id, presence: true, uniqueness: { scope: [:subject_type] }
7
+ validates :state, presence: true
8
+
9
+ def specification
10
+ @specification ||= subject.specification
11
+ end
12
+
13
+ def method_missing(method_sym, *args, &block)
14
+ if specification.state?(method_sym)
15
+ to(method_sym, *args)
16
+ elsif method_sym.to_s[-1] == '?'
17
+ predicate = method_sym.to_s[0..-2]
18
+ state == predicate
19
+ else
20
+ super
21
+ end
22
+ end
23
+
24
+ # Public: Transition from the current state to a new state.
25
+ #
26
+ # state - The event
27
+ #
28
+ # Returns nothing.
29
+ def to(state, options = {})
30
+ return false unless current_state.event?(state)
31
+
32
+ subject.run_callbacks(:progress) do
33
+ subject.run_callbacks(state) do
34
+ update_attribute(:state, state)
35
+ end
36
+ end
37
+ end
38
+
39
+ def current_state
40
+ specification.states[state.to_sym]
41
+ end
42
+
43
+ def to_s
44
+ state
45
+ end
46
+ delegate :humanize, to: :to_s
47
+ end
48
+ end
@@ -0,0 +1,46 @@
1
+ require 'active_support/concern'
2
+ require 'active_model/callbacks'
3
+
4
+ module Progressive
5
+ module Subject
6
+ extend ActiveSupport::Concern
7
+ include ActiveModel::Callbacks
8
+
9
+ included do
10
+ class_attribute :specification
11
+
12
+ define_model_callbacks :progress, only: [:before, :after]
13
+ end
14
+
15
+ module ClassMethods
16
+ # Public: Define the different states and events the subject can go through.
17
+ #
18
+ # options - The Hash options used to build the specification (default: {}):
19
+ # :default - The default state the subject is instantiated at (optional).
20
+ # block - A required block that is used to define states and events for
21
+ # the subject.
22
+ #
23
+ # Returns Progression::Specification
24
+ def states(options = {}, &block)
25
+ self.specification = Specification.new(options, &block)
26
+ define_model_callbacks(*specification.event_names, only: [:before, :after])
27
+ end
28
+ end
29
+
30
+ def method_missing(method_sym, *args, &block)
31
+ if method_sym.to_s[-1] == '?' && specification.state?(method_sym.to_s[0..-2])
32
+ state.send(method_sym)
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def specification
39
+ self.class.specification
40
+ end
41
+
42
+ def human_state
43
+ state.humanize
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module Progressive
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'progressive/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'progressive'
8
+ spec.version = Progressive::VERSION
9
+ spec.authors = ['dewski']
10
+ spec.email = ['me@garrettbjerkhoel.com']
11
+ spec.description = 'A lightweight ActiveModel backed state machine.'
12
+ spec.summary = 'A lightweight ActiveModel backed state machine.'
13
+ spec.homepage = 'https://github.com/dewski/progressive'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activemodel', '>= 3.0.0'
22
+ spec.add_development_dependency 'bundler', '~> 1.3'
23
+ spec.add_development_dependency 'rake'
24
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: progressive
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - dewski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.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: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
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
+ description: A lightweight ActiveModel backed state machine.
56
+ email:
57
+ - me@garrettbjerkhoel.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - lib/progressive.rb
68
+ - lib/progressive/specification.rb
69
+ - lib/progressive/state.rb
70
+ - lib/progressive/subject.rb
71
+ - lib/progressive/version.rb
72
+ - progressive.gemspec
73
+ homepage: https://github.com/dewski/progressive
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.5.1
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: A lightweight ActiveModel backed state machine.
97
+ test_files: []
98
+ has_rdoc: