changes_validator 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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +67 -0
- data/Rakefile +1 -0
- data/changes_validator.gemspec +26 -0
- data/lib/changes_validator.rb +39 -0
- data/lib/changes_validator/version.rb +4 -0
- data/locales/en.yml +5 -0
- data/spec/changes_validator_spec.rb +112 -0
- data/spec/spec_helper.rb +9 -0
- metadata +129 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a9232b72a8a820e08048cd79cc4f970a1f74e7b4
|
4
|
+
data.tar.gz: 1dac8a68b32ae3f430ac4d7122b14175dbac8fcd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 61a6eea8080594c5975109fdc8a6818b11f89b9d672607830880a84ccce4d42ef53d4dc37ab52a6b0d8cc61cbe9d3eda4885f1271e2f772209e35a0fe374d7c5
|
7
|
+
data.tar.gz: 36bd3f37116a16085165378ed4c3bf0cf17fa87d813b8880fa6ad6a75871ef4cc33926bd1e4cd1c8ecac152a797d222eeadae26de1debd615f38757694b34997
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Bogdan Gusiev
|
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,67 @@
|
|
1
|
+
# ChangesValidator
|
2
|
+
|
3
|
+
ChangesValidator is the most minimalistic state machine implementation focused on validating a state changes rather than define API methods and logic.
|
4
|
+
|
5
|
+
|
6
|
+
## Why ChangesValidator?
|
7
|
+
|
8
|
+
The best way to define API and logic in Ruby is using Ruby.
|
9
|
+
ChangesValidator does only validation that Object can move from one state to another.
|
10
|
+
|
11
|
+
## Dependencies
|
12
|
+
|
13
|
+
ActiveModel
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'changes_validator'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
|
28
|
+
### Basic setup
|
29
|
+
|
30
|
+
If the state machine's main goal is to validate changess than let's implement it as a validation:
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
class Reward < AR::Base
|
34
|
+
validates! :state, :changes => {
|
35
|
+
nil => [:pending], # Initial state is always pending
|
36
|
+
:pending => [:approved, :rejected], # Pending can be changesed to to approved and rejected
|
37
|
+
:approved => :paid # Approved can only be changesed to paid
|
38
|
+
}
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Recommended to use with strict validation method: `validates!` as wrong state changes use to be programmer mistake but not user input mistake.
|
43
|
+
In this case exception will be raise and logged.
|
44
|
+
|
45
|
+
|
46
|
+
### Advanced Options
|
47
|
+
|
48
|
+
* `:message` - validation message. Can have %{value} and %{old\_value} interpolation variables.
|
49
|
+
* Default: "Can not be changesed to %{value}"
|
50
|
+
* Example: "Can not be changesed from %{old\_value} to %{value}"
|
51
|
+
* `:allow_nil` - don't apply validator if value is nil
|
52
|
+
* `:allow_blank` - don't apply validator if value is blank
|
53
|
+
* `:if` - only apply validator if specified method return true
|
54
|
+
* `:unless` - only apply validator if specified method return false
|
55
|
+
* `:strict` - if true this validator will always raise when record is invalid
|
56
|
+
* Default:
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
## Contributing
|
62
|
+
|
63
|
+
1. Fork it
|
64
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
65
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
66
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
67
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'changes_validator/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "changes_validator"
|
8
|
+
spec.version = ChangesValidator::VERSION
|
9
|
+
spec.authors = ["Bogdan Gusiev"]
|
10
|
+
spec.email = ["agresso@gmail.com"]
|
11
|
+
spec.description = %q{ActiveModel validator that acts like state machine}
|
12
|
+
spec.summary = %q{This validator allows to specify a mapping of allowed changess for given attribute and validate that this attribute can only be transfered according to changess map}
|
13
|
+
spec.homepage = "https://github.com/bogdan/changes_validator"
|
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_runtime_dependency "activemodel"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "byebug" if RUBY_VERSION >= "2.0"
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "active_model"
|
2
|
+
|
3
|
+
class ChangesValidator < ActiveModel::EachValidator
|
4
|
+
|
5
|
+
def initialize(*)
|
6
|
+
super
|
7
|
+
normalize_options
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate_each(record, attribute, value)
|
11
|
+
ensure_supports_dirty(record)
|
12
|
+
changes = record.changes[attribute.to_s]
|
13
|
+
return unless changes
|
14
|
+
changess = options[:with]
|
15
|
+
start = changes.first
|
16
|
+
destination = value
|
17
|
+
|
18
|
+
allowed_changess = changess[start]
|
19
|
+
if !allowed_changess || !Array(allowed_changess).map {|s| s.to_s }.include?(destination.to_s)
|
20
|
+
record.errors.add(attribute, :changes, :old_value => start, :message => options[:message])
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def ensure_supports_dirty(record)
|
25
|
+
unless record.respond_to?(:changes)
|
26
|
+
raise ConfigurationError, "ChangesValidator can only be applied to model with dirty support (ActiveModel::Dirty)"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ConfigurationError < Exception; end
|
31
|
+
|
32
|
+
def normalize_options
|
33
|
+
unless options[:with]
|
34
|
+
@options = {:with => options}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
I18n.load_path << File.expand_path('../../locales/en.yml', __FILE__)
|
data/locales/en.yml
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class User
|
4
|
+
include ActiveModel::Dirty
|
5
|
+
include ActiveModel::Validations
|
6
|
+
|
7
|
+
define_attribute_methods [:state]
|
8
|
+
|
9
|
+
def state
|
10
|
+
@state
|
11
|
+
end
|
12
|
+
|
13
|
+
def state=(val)
|
14
|
+
state_will_change! unless val == @state
|
15
|
+
@state = val
|
16
|
+
end
|
17
|
+
|
18
|
+
def save!
|
19
|
+
raise 'invalid' unless valid?
|
20
|
+
@previously_changed = changes
|
21
|
+
@changed_attributes.clear
|
22
|
+
end
|
23
|
+
|
24
|
+
def truthly?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def falsy?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
describe ChangesValidator do
|
37
|
+
before do
|
38
|
+
User.clear_validators!
|
39
|
+
end
|
40
|
+
let!(:user) { User.new }
|
41
|
+
it "should work" do
|
42
|
+
user.should be_valid
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should support state change according to changess table" do
|
46
|
+
User.validates :state, :changes => {nil => [:pending, :approved]}
|
47
|
+
user.state = "pending"
|
48
|
+
user.should be_valid
|
49
|
+
user.state = "approved"
|
50
|
+
user.should be_valid
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return record invalid if changesed to wrong state" do
|
54
|
+
User.validates :state, :changes => {nil => [:pending, :approved]}
|
55
|
+
user.state = "voided"
|
56
|
+
user.should_not be_valid
|
57
|
+
user.errors[:state].should_not be_empty
|
58
|
+
user.errors[:state].first.should == "can't be changesed to voided"
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
it "should raise exception when model don't support dirty" do
|
63
|
+
klass = Class.new {
|
64
|
+
include ActiveModel::Validations
|
65
|
+
attr_accessor :state
|
66
|
+
validates :state, :changes => {nil => :pending}
|
67
|
+
}
|
68
|
+
proc {
|
69
|
+
klass.new.valid?
|
70
|
+
}.should raise_error(ChangesValidator::ConfigurationError)
|
71
|
+
end
|
72
|
+
it "should be able to use custom message" do
|
73
|
+
User.validates :state, :changes => {:with => {nil => [:pending, :approved]}, :message => "can not be changesed like this"}
|
74
|
+
user.state = 'declined'
|
75
|
+
user.should_not be_valid
|
76
|
+
user.errors[:state].should_not be_empty
|
77
|
+
user.errors[:state].first.should == "can not be changesed like this"
|
78
|
+
end
|
79
|
+
it "should be able to use old_value interpolation in custom message" do
|
80
|
+
User.validates :state, :changes => {:with => {nil => :pending, :pending => [:approved]}, :message => "can not be changesed from %{old_value} to %{value}"}
|
81
|
+
user.state = 'pending'
|
82
|
+
user.save!
|
83
|
+
user.state = 'declined'
|
84
|
+
user.should_not be_valid
|
85
|
+
user.errors[:state].should_not be_empty
|
86
|
+
user.errors[:state].first.should == "can not be changesed from pending to declined"
|
87
|
+
end
|
88
|
+
it "should be able to use allow_nil option" do
|
89
|
+
User.validates :state, :changes => {:with => {nil => :pending, :pending => [:approved]}}, :allow_nil => true
|
90
|
+
user.state = 'pending'
|
91
|
+
user.save!
|
92
|
+
user.state = nil
|
93
|
+
user.should be_valid
|
94
|
+
end
|
95
|
+
it "should be able to use allow_blank option" do
|
96
|
+
User.validates :state, :changes => {:with => {nil => :pending, :pending => [:approved]}}, :allow_blank => true
|
97
|
+
user.state = 'pending'
|
98
|
+
user.save!
|
99
|
+
user.state = ' '
|
100
|
+
user.should be_valid
|
101
|
+
end
|
102
|
+
it "should be able to use if option" do
|
103
|
+
User.validates :state, :changes => {:with => {nil => :pending, :pending => [:approved]}}, :if => :falsy?
|
104
|
+
user.state = 'approved'
|
105
|
+
user.should be_valid
|
106
|
+
end
|
107
|
+
it "should be able to use unless option" do
|
108
|
+
User.validates :state, :changes => {:with => {nil => :pending, :pending => [:approved]}}, :unless => :truthly?
|
109
|
+
user.state = 'approved'
|
110
|
+
user.should be_valid
|
111
|
+
end
|
112
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$:.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'changes_validator' # and any other gems you need
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
I18n.enforce_available_locales = true
|
9
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: changes_validator
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bogdan Gusiev
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-25 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: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '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
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
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: byebug
|
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: ActiveModel validator that acts like state machine
|
84
|
+
email:
|
85
|
+
- agresso@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE.txt
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- changes_validator.gemspec
|
96
|
+
- lib/changes_validator.rb
|
97
|
+
- lib/changes_validator/version.rb
|
98
|
+
- locales/en.yml
|
99
|
+
- spec/changes_validator_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
homepage: https://github.com/bogdan/changes_validator
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.2.2
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: This validator allows to specify a mapping of allowed changess for given
|
125
|
+
attribute and validate that this attribute can only be transfered according to changess
|
126
|
+
map
|
127
|
+
test_files:
|
128
|
+
- spec/changes_validator_spec.rb
|
129
|
+
- spec/spec_helper.rb
|