validation-scopes 0.0.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +5 -4
- data/README.md +12 -8
- data/lib/validation_scopes.rb +41 -36
- data/spec/validation_scopes_spec.rb +123 -74
- data/validation-scopes.gemspec +1 -1
- metadata +4 -14
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
validation-scopes (0.0.
|
4
|
+
validation-scopes (0.0.3)
|
5
5
|
activemodel
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: http://rubygems.org/
|
9
9
|
specs:
|
10
|
-
activemodel (3.0.
|
11
|
-
activesupport (= 3.0.
|
10
|
+
activemodel (3.0.5)
|
11
|
+
activesupport (= 3.0.5)
|
12
12
|
builder (~> 2.1.2)
|
13
13
|
i18n (~> 0.4)
|
14
|
-
activesupport (3.0.
|
14
|
+
activesupport (3.0.5)
|
15
15
|
builder (2.1.2)
|
16
16
|
diff-lcs (1.1.2)
|
17
17
|
i18n (0.5.0)
|
@@ -28,5 +28,6 @@ PLATFORMS
|
|
28
28
|
ruby
|
29
29
|
|
30
30
|
DEPENDENCIES
|
31
|
+
activemodel
|
31
32
|
rspec
|
32
33
|
validation-scopes!
|
data/README.md
CHANGED
@@ -1,23 +1,27 @@
|
|
1
1
|
# Validation Scopes
|
2
2
|
|
3
|
-
Validation Scopes allows you to
|
3
|
+
Validation Scopes allows you to remove duplication in validations by grouping validations that share the same conditions. It works the same way as ActiveSupport's OptionMerger does, except instead of replacing duplicate keys it groups them into an array. This is so that nested if conditions work inside ActiveModel validations. Example, with result shown in comments:
|
4
4
|
|
5
5
|
class Car < ActiveRecord::Base
|
6
|
-
validation_scope :if => Proc.new { |u| u.step == 2 } do
|
7
|
-
validates_presence_of :variant
|
8
|
-
|
6
|
+
validation_scope :if => Proc.new { |u| u.step == 2 } do |v|
|
7
|
+
v.validates_presence_of :variant # , :if => Proc.new { |u| u.step == 2 }
|
8
|
+
v.validation_scope :if => :something? do |s|
|
9
|
+
s.validates_presence_of :body # , :if => [Proc.new { |u| u.step == 2 }, :something?]
|
10
|
+
end
|
9
11
|
end
|
10
12
|
|
11
|
-
validation_scope :if => Proc.new { |u|
|
12
|
-
validates_inclusion_of :outstanding_finance, :in => [true, false], :if => Proc.new { |u| u.finance == true }
|
13
|
+
validation_scope :if => Proc.new { |u| u.step == 3 } do |v|
|
14
|
+
v.validates_inclusion_of :outstanding_finance, :in => [true, false], :if => Proc.new { |u| u.finance == true }
|
13
15
|
# Duplicate keys are turned into arrays
|
14
|
-
#
|
16
|
+
# :if => [Proc.new { |u| u.finance == true }, Proc.new { |u| i.step == 3 }]
|
15
17
|
|
16
|
-
validate do
|
18
|
+
v.validate do # v.validate :if => Proc.new { |u| u.step == 3 }
|
17
19
|
errors.add(:weight, "Must be greater than 0") unless !@weight.nil? && @weight > 0
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
23
|
+
|
24
|
+
The options passed into the validation_scope method will usually be either :if or :unless, but any are accepted and they are passed onto the individual validators.
|
21
25
|
|
22
26
|
# Installation
|
23
27
|
|
data/lib/validation_scopes.rb
CHANGED
@@ -1,54 +1,59 @@
|
|
1
1
|
require 'active_support/concern'
|
2
|
-
require 'active_model'
|
3
2
|
|
4
3
|
module ValidationScopes
|
5
4
|
extend ActiveSupport::Concern
|
6
5
|
|
7
|
-
|
6
|
+
class OptionMerger
|
7
|
+
instance_methods.each do |method|
|
8
|
+
undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/
|
9
|
+
end
|
8
10
|
|
9
|
-
def
|
10
|
-
@
|
11
|
-
@_validation_scope_options = options
|
12
|
-
block.call
|
13
|
-
@_validation_scope_options = nil
|
14
|
-
@_in_validation_scope = false
|
15
|
-
@_handled_by_with = false
|
11
|
+
def initialize(context, options)
|
12
|
+
@context, @options = context, options
|
16
13
|
end
|
17
14
|
|
18
|
-
def
|
19
|
-
|
20
|
-
merge_args(args)
|
21
|
-
@_handled_by_with = true
|
22
|
-
end
|
23
|
-
super(*args, &block)
|
15
|
+
def validation_scope(options)
|
16
|
+
yield OptionMerger.new(@context, merge_options(@options, options))
|
24
17
|
end
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
18
|
+
|
19
|
+
private
|
20
|
+
def method_missing(method, *arguments, &block)
|
21
|
+
arguments << if arguments.last.respond_to?(:to_hash)
|
22
|
+
merge_options(@options, arguments.pop)
|
23
|
+
else
|
24
|
+
@options.dup
|
25
|
+
end
|
26
|
+
@context.__send__(method, *arguments, &block)
|
29
27
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
args << @_validation_scope_options.dup
|
38
|
-
elsif args.last.is_a?(Hash) && args.last.extractable_options?
|
39
|
-
options = args.extract_options!
|
40
|
-
options = options.dup
|
41
|
-
@_validation_scope_options.each_key do |key|
|
42
|
-
if options[key].nil?
|
43
|
-
options[key] = @_validation_scope_options[key]
|
28
|
+
|
29
|
+
def merge_options(options_b, options_a)
|
30
|
+
return options_b.dup if options_a.nil?
|
31
|
+
options_a = options_a.dup
|
32
|
+
options_b.each_pair do |key, value|
|
33
|
+
if options_a[key].nil?
|
34
|
+
options_a[key] = value
|
44
35
|
else
|
45
|
-
|
46
|
-
|
36
|
+
options_a[key] = Array.wrap(options_a[key]) unless options_a[key].is_a?(Array)
|
37
|
+
if value.is_a?(Array)
|
38
|
+
value.each do |v|
|
39
|
+
options_a[key] << v
|
40
|
+
end
|
41
|
+
else
|
42
|
+
options_a[key] << value
|
43
|
+
end
|
47
44
|
end
|
48
45
|
end
|
49
|
-
|
46
|
+
options_a
|
50
47
|
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module ClassMethods
|
51
|
+
|
52
|
+
def validation_scope(options, &block)
|
53
|
+
raise "Deprecated. See readme for new usage. https://github.com/stevehodgkiss/validation-scopes" if block.arity == 0
|
54
|
+
yield OptionMerger.new(self, options)
|
51
55
|
end
|
56
|
+
|
52
57
|
end
|
53
58
|
end
|
54
59
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'active_model'
|
2
3
|
|
3
4
|
class TestUser
|
4
5
|
include ActiveModel::Validations
|
@@ -13,32 +14,113 @@ class TestUser
|
|
13
14
|
def step_2?
|
14
15
|
step == 2
|
15
16
|
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
def self.method_with_options(options)
|
21
|
+
options
|
22
|
+
end
|
16
23
|
end
|
17
24
|
|
18
|
-
|
19
|
-
|
25
|
+
describe ValidationScopes do
|
26
|
+
|
27
|
+
before do
|
28
|
+
TestClass = Class.new do
|
29
|
+
include ValidationScopes
|
30
|
+
|
31
|
+
def self.method_with_options(options)
|
32
|
+
options
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.method_with_multiple_options(*options)
|
36
|
+
options
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
20
40
|
|
21
|
-
|
22
|
-
|
23
|
-
|
41
|
+
it "merges method no options" do
|
42
|
+
TestClass.class_eval do
|
43
|
+
validation_scope :if => 1 do |v|
|
44
|
+
v.method_with_options.should == {:if => 1}
|
45
|
+
end
|
46
|
+
end
|
24
47
|
end
|
25
48
|
|
26
|
-
|
27
|
-
|
49
|
+
it "merges method with options" do
|
50
|
+
TestClass.class_eval do
|
51
|
+
validation_scope :if => 1 do |v|
|
52
|
+
v.method_with_options(:if => 2).should == {:if => [2, 1]}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "merges with multiple options" do
|
58
|
+
TestClass.class_eval do
|
59
|
+
validation_scope :if => 1 do |v|
|
60
|
+
v.method_with_multiple_options(:name, :if => 2).should == [:name, {:if => [2, 1]}]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
it "merges nested scope with no method options" do
|
66
|
+
TestClass.class_eval do
|
67
|
+
validation_scope :if => 1 do |v|
|
68
|
+
v.validation_scope :if => 2 do |s|
|
69
|
+
s.method_with_options.should == {:if => [2, 1]}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "merges nested scope with method options" do
|
76
|
+
TestClass.class_eval do
|
77
|
+
validation_scope :if => 1 do |v|
|
78
|
+
v.validation_scope :if => 2 do |s|
|
79
|
+
s.method_with_options(:if => 3, :unless => 6).should == {:if => [3, 2, 1], :unless => 6}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
it "merges deep nested scope with method options" do
|
86
|
+
TestClass.class_eval do
|
87
|
+
validation_scope :if => 1 do |v|
|
88
|
+
v.validation_scope :if => 2 do |s|
|
89
|
+
s.validation_scope :unless => 6 do |u|
|
90
|
+
u.method_with_options(:if => 3).should == {:if => [3, 2, 1], :unless => 6}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
it "fails if called without a block parameter" do
|
98
|
+
expect {
|
99
|
+
TestClass.class_eval do
|
100
|
+
validation_scope :if => 1 do
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
}.to raise_error
|
28
105
|
end
|
106
|
+
|
107
|
+
after { Object.send(:remove_const, :TestClass) }
|
29
108
|
end
|
30
109
|
|
31
|
-
describe "
|
110
|
+
describe ValidationScopes, "with AM" do
|
32
111
|
|
33
112
|
context "validates_with" do
|
34
113
|
before do
|
35
114
|
User = Class.new(TestUser) do
|
36
115
|
validates_presence_of :address
|
37
|
-
validation_scope :if => :step_2? do
|
38
|
-
validates_presence_of :name
|
116
|
+
validation_scope :if => :step_2? do |v|
|
117
|
+
v.validates_presence_of :name
|
39
118
|
end
|
40
|
-
validation_scope :if => Proc.new { |u| u.step == 3 } do
|
41
|
-
|
119
|
+
validation_scope :if => Proc.new { |u| u.step == 3 } do |v|
|
120
|
+
v.validation_scope :if => Proc.new { |u| !u.height.nil? && u.height > 6 } do |h|
|
121
|
+
h.validates_presence_of :weight
|
122
|
+
end
|
123
|
+
v.validates_inclusion_of :eye_colour, :in => ["blue", "brown"], :if => Proc.new { |u| !u.age.nil? && u.age > 20 }
|
42
124
|
end
|
43
125
|
end
|
44
126
|
@user = User.new
|
@@ -47,76 +129,43 @@ describe ".validation_scope" do
|
|
47
129
|
end
|
48
130
|
|
49
131
|
it "should only validate name when on step 2" do
|
50
|
-
@user.
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
132
|
+
@user.tap do |u|
|
133
|
+
u.name = nil
|
134
|
+
u.should be_valid
|
135
|
+
u.step = 2
|
136
|
+
u.should be_invalid
|
137
|
+
u.name = "Steve"
|
138
|
+
u.should be_valid
|
139
|
+
end
|
56
140
|
end
|
57
141
|
|
58
142
|
it "only validates eye colour when on step 3 and age is above 20" do
|
59
|
-
@user.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
it "calls validates_with with merged scope options" do
|
72
|
-
presence_validator = User.validators_on(:name).first
|
73
|
-
presence_validator.options.should eq({:if => :step_2?})
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
context "validate method" do
|
78
|
-
before do
|
79
|
-
User = Class.new(TestUser) do
|
80
|
-
validation_scope :unless => :step_2? do
|
81
|
-
validate do
|
82
|
-
errors.add(:weight, "Must be greater than 0") unless !@weight.nil? && @weight > 0
|
83
|
-
end
|
84
|
-
end
|
143
|
+
@user.tap do |u|
|
144
|
+
u.eye_colour = "red"
|
145
|
+
u.should be_valid
|
146
|
+
u.age = 20
|
147
|
+
u.step = 3
|
148
|
+
u.should be_valid
|
149
|
+
u.age = 21
|
150
|
+
u.valid?
|
151
|
+
u.should be_invalid
|
152
|
+
u.eye_colour = "blue"
|
153
|
+
u.should be_valid
|
85
154
|
end
|
86
155
|
end
|
87
156
|
|
88
|
-
it "
|
89
|
-
user
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
user.should be_valid
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
context "custom validators" do
|
102
|
-
before do
|
103
|
-
User = Class.new(TestUser) do
|
104
|
-
validation_scope :unless => :step_2?, :some_config_var => 5 do
|
105
|
-
validates_with TestValidator, {:attributes => [:name], :some_config_var => 6}
|
106
|
-
end
|
157
|
+
it "allows nesting of scopes" do
|
158
|
+
@user.tap do |u|
|
159
|
+
u.weight = nil
|
160
|
+
u.should be_valid
|
161
|
+
u.height = 7
|
162
|
+
u.should be_valid
|
163
|
+
u.step = 3
|
164
|
+
u.should be_invalid
|
165
|
+
u.errors[:weight].should be
|
107
166
|
end
|
108
|
-
@validator = User.validators_on(:name).first
|
109
|
-
end
|
110
|
-
|
111
|
-
it "passes the options in" do
|
112
|
-
@validator.options[:unless].should eq(:step_2?)
|
113
|
-
@validator.options[:some_config_var].should be
|
114
|
-
end
|
115
|
-
|
116
|
-
it "turns the duplicate options into an array" do
|
117
|
-
@validator.options[:some_config_var].should eq([6, 5])
|
118
167
|
end
|
119
168
|
end
|
120
169
|
|
121
|
-
after { Object.send(:remove_const, :User)
|
170
|
+
after { Object.send(:remove_const, :User) }
|
122
171
|
end
|
data/validation-scopes.gemspec
CHANGED
metadata
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: validation-scopes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 3
|
9
|
-
version: 0.0.3
|
4
|
+
prerelease:
|
5
|
+
version: 0.4.0
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- Steve Hodgkiss
|
@@ -14,7 +10,7 @@ autorequire:
|
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
12
|
|
17
|
-
date: 2011-
|
13
|
+
date: 2011-03-03 00:00:00 +00:00
|
18
14
|
default_executable:
|
19
15
|
dependencies:
|
20
16
|
- !ruby/object:Gem::Dependency
|
@@ -25,8 +21,6 @@ dependencies:
|
|
25
21
|
requirements:
|
26
22
|
- - ">="
|
27
23
|
- !ruby/object:Gem::Version
|
28
|
-
segments:
|
29
|
-
- 0
|
30
24
|
version: "0"
|
31
25
|
type: :runtime
|
32
26
|
version_requirements: *id001
|
@@ -67,21 +61,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
61
|
requirements:
|
68
62
|
- - ">="
|
69
63
|
- !ruby/object:Gem::Version
|
70
|
-
segments:
|
71
|
-
- 0
|
72
64
|
version: "0"
|
73
65
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
66
|
none: false
|
75
67
|
requirements:
|
76
68
|
- - ">="
|
77
69
|
- !ruby/object:Gem::Version
|
78
|
-
segments:
|
79
|
-
- 0
|
80
70
|
version: "0"
|
81
71
|
requirements: []
|
82
72
|
|
83
73
|
rubyforge_project: validation-scopes
|
84
|
-
rubygems_version: 1.
|
74
|
+
rubygems_version: 1.5.2
|
85
75
|
signing_key:
|
86
76
|
specification_version: 3
|
87
77
|
summary: Scope ActiveModel validations
|