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 CHANGED
@@ -3,5 +3,6 @@ source "http://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  group :test do
6
+ gem "activemodel"
6
7
  gem "rspec"
7
8
  end
@@ -1,17 +1,17 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- validation-scopes (0.0.1)
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.4)
11
- activesupport (= 3.0.4)
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.4)
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 group validations together that share the same conditions. It depends on ActiveModel. Example:
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
- validates_presence_of :body
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| i.step == 3 } do
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
- # In this case InclusionValidator would get an array containing both Procs in the :if attribute
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
 
@@ -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
- module ClassMethods
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 validation_scope(options, &block)
10
- @_in_validation_scope = true
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 validates_with(*args, &block)
19
- if @_in_validation_scope
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
- def validate(*args, &block)
27
- if @_in_validation_scope & !@_handled_by_with
28
- merge_args(args)
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
- super(*args, &block)
31
- end
32
-
33
- protected
34
-
35
- def merge_args(args)
36
- if args.empty?
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
- options[key] = Array.wrap(options[key])
46
- options[key] << @_validation_scope_options[key]
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
- args << options
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
- class TestValidator < ActiveModel::EachValidator
19
- attr_accessor :options
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
- def initialize(options)
22
- @options = options
23
- super(options)
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
- def validate(record)
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 ".validation_scope" do
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
- validates_inclusion_of :eye_colour, :in => ["blue", "brown"], :if => Proc.new { |u| !u.age.nil? && u.age > 20 }
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.name = nil
51
- @user.should be_valid
52
- @user.step = 2
53
- @user.should be_invalid
54
- @user.name = "Steve"
55
- @user.should be_valid
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.eye_colour = nil
60
- @user.should be_valid
61
- @user.age = 20
62
- @user.step = 3
63
- @user.eye_colour = "red"
64
- @user.should be_valid
65
- @user.age = 21
66
- @user.should be_invalid
67
- @user.eye_colour = "blue"
68
- @user.should be_valid
69
- end
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 "should only validate weight on step 2" do
89
- user = User.new
90
- user.weight = 0
91
- user.should be_invalid
92
- user.weight = 1
93
- user.should be_valid
94
-
95
- user.step = 2
96
- user.weight = 0
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) if defined?(User) }
170
+ after { Object.send(:remove_const, :User) }
122
171
  end
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "validation-scopes"
6
- s.version = "0.0.3"
6
+ s.version = "0.4.0"
7
7
  s.platform = Gem::Platform::RUBY
8
8
  s.authors = ["Steve Hodgkiss"]
9
9
  s.email = ["steve@hodgkiss.me.uk"]
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: false
5
- segments:
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-02-14 00:00:00 +00:00
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.3.7
74
+ rubygems_version: 1.5.2
85
75
  signing_key:
86
76
  specification_version: 3
87
77
  summary: Scope ActiveModel validations