solidstate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/README.md +104 -0
  4. data/Rakefile +2 -0
  5. data/lib/solidstate.rb +92 -0
  6. data/solidstate.gemspec +23 -0
  7. metadata +125 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 10ee93a954561985d002e7f8db5ffe186a792bba
4
+ data.tar.gz: e979280cea908e17a924718f7f20a247d79c11b9
5
+ SHA512:
6
+ metadata.gz: 3480386e65adfdd682c7bd05a4a67d49a50660ac102b16fc7cbe7c334d50ab3829989b0e710af6a6e6f8d54d7b1ec352736727adb2f7955a2664a4103c70ce01
7
+ data.tar.gz: 1ccbbca8ab19e933ff4894036fdf46f9c48daa1c264d37d573917303848f1b34d90e269f2de771a451e508028d697ebf381c8d3cacabf497701e61c41f0c9ace
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ # specified in tuktuk.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,104 @@
1
+ SolidState
2
+ ==========
3
+
4
+ Minuscule but solid state machine for Ruby classes. The only dependency is that your model responds to a getter and setter for `state`.
5
+
6
+ ``` ruby
7
+
8
+ # simplest example, using just an accessor
9
+ class Post
10
+ include SolidState
11
+
12
+ attr_accessor :state
13
+ states :draft, :published
14
+ end
15
+
16
+ # in its simplest form you just declare the possible states.
17
+ # if it's a simple class you just get boolean methods for checking
18
+ # whether the current status is X or Y.
19
+
20
+ p = Post.new
21
+ p.state # => 'draft'
22
+ p.draft? # true
23
+ p.published? # => false
24
+ p.state = 'published'
25
+ p.published? # => true
26
+
27
+ # now, if the model class responds to validates_inclusion_of, it will
28
+ # mark the record invalid if an unknown state is set.
29
+
30
+ # let's assume this is actually an ActiveRecord class, and the
31
+ # table contains a column named 'state'.
32
+
33
+ class Post < ActiveRecord::Base
34
+ include SolidState
35
+
36
+ states :draft, :published
37
+ end
38
+
39
+ p = Post.new
40
+ p.state = 'published'
41
+ p.valid? # => true
42
+ p.state = 'deleted'
43
+ p.valid? # => false
44
+
45
+ # ok, now let's gets get fancier. we're going to declare transitions
46
+ # which will govern the possible directions in which an object's state
47
+ # can move to.
48
+
49
+ class Subscriber < ActiveRecord::Base
50
+ include SolidState
51
+
52
+ states :inactive, :active, :unsubscribed, :disabled do
53
+ transitions :from => :inactive, :to => :active
54
+ transitions :from => :active, :to => [:unsubscribed, :disabled]
55
+ transitions :from => :unsubscribed, :to => :active
56
+ end
57
+ end
58
+
59
+ s = Subscriber.new
60
+ s.state # => 'inactive'
61
+
62
+ # since we declared transitions, we can now call #{state}! which
63
+ # checks whether the instance can transition to that state and
64
+ # if so, sets the new state and optionally saves the record.
65
+
66
+ s.active! # => true
67
+
68
+ s.inactive! # => raises InvalidTransitionError
69
+
70
+ # this also works outside transition methods, of course.
71
+ s.reload # => true
72
+ s.active? # => true
73
+
74
+ s.state = 'inactive'
75
+ s.valid? # => false
76
+
77
+ # the last trick this library does is that it optionally lets you
78
+ # declare callback methods that are called whenever a transition
79
+ # method succeeds. just define a method called #once_[state] in
80
+ # your model.
81
+
82
+ class Subscriber
83
+ def once_unsubscribed
84
+ puts "Sorry to see you go!"
85
+ end
86
+ end
87
+
88
+ # ...
89
+ s.unsubscribed! # => prints "Sorry to see you go!"
90
+ ```
91
+
92
+ That's about it. For examples check the `examples` directory in this repo.
93
+
94
+ # Contributions
95
+
96
+ You're more than welcome. Send a pull request, including tests, and make sure you don't break anything. That's it.
97
+
98
+ # Author
99
+
100
+ Tomás Pollak
101
+
102
+ # Copyright
103
+
104
+ (c) Fork Limited. MIT license.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/lib/solidstate.rb ADDED
@@ -0,0 +1,92 @@
1
+ module SolidState
2
+ extend ActiveSupport::Concern
3
+
4
+ class InvalidTransitionError < ArgumentError; end
5
+
6
+ STATE_ATTRIBUTE = :state.freeze
7
+
8
+ module ClassMethods
9
+
10
+ def states(*list, &block)
11
+ raise "This is not a list of names" unless list.first.is_a?(String)
12
+ list = list.collect(&:to_s)
13
+
14
+ @@states = list
15
+ @@state_transitions = {}
16
+
17
+ def self.states
18
+ @@states
19
+ end
20
+
21
+ def self.state_transitions
22
+ @@state_transitions
23
+ end
24
+
25
+ if respond_to?(:validates_inclusion_of)
26
+ validates_inclusion_of STATE_ATTRIBUTE, in: list
27
+ end
28
+
29
+ scope :with_state, lambda { |state|
30
+ return query if state.blank?
31
+ where(STATE_ATTRIBUTE => state)
32
+ } if respond_to?(:scope)
33
+
34
+ list.each do |s|
35
+ scope(s, lambda { where(STATE_ATTRIBUTE => s) }) if respond_to?(:scope)
36
+
37
+ define_method "#{s}?" do
38
+ state.to_sym == s.to_sym
39
+ end
40
+ end
41
+
42
+ yield if block_given?
43
+ end
44
+
45
+ def transitions(opts)
46
+ validate :ensure_valid_transition if respond_to?(:validate)
47
+
48
+ from = opts.delete(:from) or raise ":from required"
49
+ to = opts.delete(:to) or raise ":to required"
50
+ to = [to] unless to.is_a?(Array)
51
+
52
+ # puts "From #{from} to #{to.join(' or ')}"
53
+ to.each do |dest|
54
+
55
+ state_transitions[from.to_sym] ||= []
56
+ state_transitions[from.to_sym].push(dest.to_sym)
57
+
58
+ define_method("#{dest}!") do
59
+ unless set_state(dest.to_sym)
60
+ raise InvalidTransition.new("Cannot transition from #{state} to #{dest}")
61
+ end
62
+
63
+ if !respond_to?(:valid?) or (valid? && save)
64
+ send("once_#{dest}", from) if respond_to?("once_#{dest}")
65
+ true
66
+ else
67
+ false
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def set_state(new_state)
75
+ return false unless can_transition_to?(new_state)
76
+ self.state = new_state
77
+ end
78
+
79
+ def ensure_valid_transition
80
+ if send("#{STATE_ATTRIBUTE}_changed?") and !can_transition_to?(state)
81
+ errors.add(STATE_ATTRIBUTE, "can't transition from current state to #{state}")
82
+ end
83
+ end
84
+
85
+ def can_transition_to?(new_state)
86
+ return true if state.to_sym == new_state.to_sym
87
+
88
+ possible = self.class.state_transitions[state.to_sym] || []
89
+ possible.include?(new_state.to_sym)
90
+ end
91
+
92
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "solidstate"
5
+ s.version = '0.1.0'
6
+ s.platform = Gem::Platform::RUBY
7
+ s.authors = ['Tomás Pollak']
8
+ s.email = ['tomas@forkhq.com']
9
+ s.homepage = "https://github.com/tomas/solidstate"
10
+ s.summary = "Minuscule state machine."
11
+ s.description = "Minuscule state machine."
12
+
13
+ s.required_rubygems_version = ">= 1.3.6"
14
+ s.add_development_dependency "bundler", ">= 1.0.0"
15
+ s.add_development_dependency "rspec", '~> 3.0', '>= 3.0.0'
16
+ s.add_development_dependency "activerecord" #, ">= 4.0.0"
17
+ s.add_development_dependency "sqlite3" #, ">= 4.0.0"
18
+ s.add_development_dependency "mongo_mapper" #, ">= 4.0.0"
19
+
20
+ s.files = `git ls-files`.split("\n")
21
+ s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
22
+ s.require_path = 'lib'
23
+ end
metadata ADDED
@@ -0,0 +1,125 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solidstate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tomás Pollak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-03-11 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.0.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ - - ">="
35
+ - !ruby/object:Gem::Version
36
+ version: 3.0.0
37
+ type: :development
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '3.0'
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 3.0.0
47
+ - !ruby/object:Gem::Dependency
48
+ name: activerecord
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: sqlite3
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: mongo_mapper
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ description: Minuscule state machine.
90
+ email:
91
+ - tomas@forkhq.com
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - Gemfile
97
+ - README.md
98
+ - Rakefile
99
+ - lib/solidstate.rb
100
+ - solidstate.gemspec
101
+ homepage: https://github.com/tomas/solidstate
102
+ licenses: []
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 1.3.6
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.2.0
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Minuscule state machine.
124
+ test_files: []
125
+ has_rdoc: