validates_serialized 0.0.1.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +46 -0
- data/README.md +151 -0
- data/lib/validates_serialized/each_validator.rb +20 -0
- data/lib/validates_serialized/validateable_array.rb +9 -0
- data/lib/validates_serialized/validateable_hash.rb +15 -0
- data/lib/validates_serialized/validateable_object.rb +14 -0
- data/lib/validates_serialized/validators/array_validator.rb +78 -0
- data/lib/validates_serialized/validators/hash_block_validator.rb +59 -0
- data/lib/validates_serialized/validators/hash_validator.rb +75 -0
- data/lib/validates_serialized/validators/object_block_validator.rb +58 -0
- data/lib/validates_serialized/validators/serialized_validator.rb +73 -0
- data/lib/validates_serialized/version.rb +3 -0
- data/lib/validates_serialized.rb +10 -0
- data/notes.md +5 -0
- data/spec/acceptance_spec.rb +95 -0
- data/spec/each_validator_spec.rb +47 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/validateable_array_spec.rb +15 -0
- data/spec/validateable_hash_spec.rb +54 -0
- data/spec/validateable_object_spec.rb +72 -0
- data/spec/validators/array_validator_spec.rb +182 -0
- data/spec/validators/hash_block_validator_spec.rb +98 -0
- data/spec/validators/hash_validator_spec.rb +183 -0
- data/spec/validators/object_block_validator_spec.rb +110 -0
- data/validates_serialized.gemspec +26 -0
- metadata +154 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Validations
|
3
|
+
class ObjectBlockValidator < BlockValidator #:nodoc:
|
4
|
+
def initialize(options, &block)
|
5
|
+
@block = block
|
6
|
+
@options = options
|
7
|
+
super
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def validate_each(record, attribute, value)
|
12
|
+
error_hash = get_serialized_object_errors(value)
|
13
|
+
add_errors_to_record(record, attribute, error_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_serialized_object_errors(value)
|
17
|
+
serialized_object = ValidateableObject.new(value)
|
18
|
+
serialized_object.class_eval &@block
|
19
|
+
serialized_object.valid?
|
20
|
+
serialized_object.errors.messages
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_errors_to_record(record, attribute, error_hash)
|
24
|
+
error_hash.each_pair do |key, array|
|
25
|
+
text = array.join(", ")
|
26
|
+
message = "#{key} #{text}"
|
27
|
+
if exception = options[:strict]
|
28
|
+
exception = ActiveModel::StrictValidationFailed if exception == true
|
29
|
+
raise exception, message
|
30
|
+
end
|
31
|
+
record.errors.add(attribute, message)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_message_from_error_hash(error_hash)
|
36
|
+
message = nil
|
37
|
+
error_hash.each_pair do |key, array|
|
38
|
+
message = array.join(", ")
|
39
|
+
end
|
40
|
+
message
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
# Helper to accept arguments in the style of the +validates+ class method
|
46
|
+
def validates_serialized(*attr_names, &block)
|
47
|
+
raise ArgumentError, "You need to supply at least one attribute" if attr_names.empty?
|
48
|
+
validates_with ObjectBlockValidator, _merge_attributes(attr_names), &block
|
49
|
+
end
|
50
|
+
|
51
|
+
def validates_serialized!(*attr_names, &block)
|
52
|
+
options = attr_names.extract_options!
|
53
|
+
options[:strict] = true
|
54
|
+
validates_serialized(*(attr_names << options), &block)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Validations
|
3
|
+
# Special case of EachValidator that exists to allow other EachValidators to validate
|
4
|
+
# individual properties of a serialized object
|
5
|
+
class SerializedValidator < ::ActiveModel::EachValidator #:nodoc:
|
6
|
+
def initialize(*args, &block)
|
7
|
+
#TODO: Need a method for extracting serialized_attributes
|
8
|
+
@serialized_attributes = nil
|
9
|
+
options = args.extract_options!
|
10
|
+
@validators = []
|
11
|
+
args.first.each do |klass|
|
12
|
+
validator = klass.new(options.dup, &block)
|
13
|
+
@validators << validator
|
14
|
+
end
|
15
|
+
super(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate(record)
|
19
|
+
attributes.each do |attribute|
|
20
|
+
serialized = record.read_attribute_for_validation(attribute)
|
21
|
+
type_check!(serialized)
|
22
|
+
validate_serialized(record, attribute, serialized)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def validate_each(record, attribute, value)
|
27
|
+
@validators.each do |validator|
|
28
|
+
validator.validate_each(record, attribute, value)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
def validate_serialized(record, attribute, serialized)
|
34
|
+
serialized.each do |value|
|
35
|
+
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
|
36
|
+
validate_each(record, attribute, value)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def type_check!(value)
|
41
|
+
raise TypeError, "#{value} is not an Object" unless array.is_a?(Object)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
def validates_serialized_with(*args, &block)
|
47
|
+
options = args.extract_options!
|
48
|
+
options[:class] = self
|
49
|
+
serialized_validator_class = args.shift
|
50
|
+
validator = serialized_validator_class.new(args, options, &block)
|
51
|
+
|
52
|
+
if validator.respond_to?(:attributes) && !validator.attributes.empty?
|
53
|
+
validator.attributes.each do |attribute|
|
54
|
+
_validators[attribute.to_sym] << validator
|
55
|
+
end
|
56
|
+
else
|
57
|
+
_validators[nil] << validator
|
58
|
+
end
|
59
|
+
|
60
|
+
validate(validator, options)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def validates_serialized_with!(*args, &block)
|
65
|
+
options = args.extract_options!
|
66
|
+
serialized_validator_class = args.shift
|
67
|
+
args.each do |klass|
|
68
|
+
validator = serialized_validator_class.new(args, options, &block)
|
69
|
+
validator.validate(self)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'validates_serialized/version'
|
2
|
+
require 'validates_serialized/validateable_array'
|
3
|
+
require 'validates_serialized/validateable_hash'
|
4
|
+
require 'validates_serialized/validateable_object'
|
5
|
+
require 'validates_serialized/each_validator'
|
6
|
+
require 'validates_serialized/validators/serialized_validator'
|
7
|
+
require 'validates_serialized/validators/array_validator'
|
8
|
+
require 'validates_serialized/validators/hash_validator'
|
9
|
+
require 'validates_serialized/validators/hash_block_validator'
|
10
|
+
require 'validates_serialized/validators/object_block_validator'
|
data/notes.md
ADDED
@@ -0,0 +1,5 @@
|
|
1
|
+
1) The 'validates' method iterates over each of the validations and passes along the parsed information to 'validates_with'
|
2
|
+
|
3
|
+
2) Validates_array should mimic the 'validates' method in this regard, but call 'validates_array_values_with' (which will do what 'validates_with' does, but by iterating over each value)
|
4
|
+
|
5
|
+
TODO: Look up/copy 'def validates_with' to understand that function and duplicate its functionality.
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class Author
|
4
|
+
def initialize(h={})
|
5
|
+
h.each {|k,v| send("#{k}=",v)}
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
@name ||= nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def name=(val)
|
13
|
+
@name = val
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Blog
|
18
|
+
include ActiveModel::Validations
|
19
|
+
|
20
|
+
def initialize(h={})
|
21
|
+
h.each {|k,v| send("#{k}=",v)}
|
22
|
+
end
|
23
|
+
|
24
|
+
def author
|
25
|
+
@author
|
26
|
+
end
|
27
|
+
|
28
|
+
def author=(val)
|
29
|
+
@author = val
|
30
|
+
end
|
31
|
+
|
32
|
+
def ratings
|
33
|
+
@ratings
|
34
|
+
end
|
35
|
+
|
36
|
+
def ratings=(val)
|
37
|
+
@ratings = val
|
38
|
+
end
|
39
|
+
|
40
|
+
def comments
|
41
|
+
@comments
|
42
|
+
end
|
43
|
+
|
44
|
+
def comments=(val)
|
45
|
+
@comments = val
|
46
|
+
end
|
47
|
+
|
48
|
+
validates_serialized :author do
|
49
|
+
validates :name, presence: true
|
50
|
+
end
|
51
|
+
validates_array_values :ratings, inclusion: { in: [1, 2, 3] }
|
52
|
+
validates_hash_keys :comments, allow_blank: true do
|
53
|
+
validates :admin, presence: true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe ValidatesSerialized do
|
58
|
+
it "is valid with all params" do
|
59
|
+
model = Blog.new(ratings: [1, 3, 1], author: Author.new(name: "Tom"), comments: { admin: "This is great!" })
|
60
|
+
model.should be_valid
|
61
|
+
end
|
62
|
+
|
63
|
+
it "is valid without comments" do
|
64
|
+
model = Blog.new(ratings: [1, 3, 1], author: Author.new(name: "Tom"))
|
65
|
+
model.should be_valid
|
66
|
+
end
|
67
|
+
|
68
|
+
it "raises error without Serialized author" do
|
69
|
+
model = Blog.new(ratings: [1, 3, 1], comments: { admin: "This is great!" })
|
70
|
+
expect { model.valid? }.to raise_error
|
71
|
+
end
|
72
|
+
|
73
|
+
it "is invalid without author name" do
|
74
|
+
model = Blog.new(ratings: [1, 3, 1], author: Author.new, comments: { admin: "This is great!" })
|
75
|
+
model.should_not be_valid
|
76
|
+
model.errors[:author].should eq(["name can't be blank"])
|
77
|
+
end
|
78
|
+
|
79
|
+
it "is invalid without comment admin key" do
|
80
|
+
model = Blog.new(ratings: [1, 3, 1], author: Author.new(name: "Tom"), comments: { other: "This is great!" })
|
81
|
+
model.should_not be_valid
|
82
|
+
model.errors[:comments].should eq(["admin can't be blank"])
|
83
|
+
end
|
84
|
+
|
85
|
+
it "raises error without ratings" do
|
86
|
+
model = Blog.new(ratings: nil, author: Author.new(name: "Tom"), comments: { admin: "This is great!" })
|
87
|
+
expect { model.valid? }.to raise_error
|
88
|
+
end
|
89
|
+
|
90
|
+
it "is invalid with invalid ratings value" do
|
91
|
+
model = Blog.new(ratings: [1, 8], author: Author.new(name: "Tom"), comments: { admin: "This is great!" })
|
92
|
+
model.should_not be_valid
|
93
|
+
model.errors[:ratings].should eq(["is not included in the list"])
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
class MyValidator < ::ActiveModel::EachValidator
|
5
|
+
def validate_each(record, attribute, value)
|
6
|
+
if invalid_format?(value)
|
7
|
+
record.errors.add(attribute, "is an invalid email")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def invalid_format?(email)
|
12
|
+
email !~ /.*\@gmail\.com/
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class MyTestClass < OpenStruct
|
17
|
+
include ActiveModel::Validations
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ActiveModel::EachValidator do
|
21
|
+
describe "#validate_each_in_array" do
|
22
|
+
it "does not add errors for all valid values" do
|
23
|
+
record = MyTestClass.new(my_attr: ["tom@gmail.com"])
|
24
|
+
MyValidator.new(attributes: [:my_attr]).validate_each_in_array(record)
|
25
|
+
record.errors.should be_empty
|
26
|
+
end
|
27
|
+
|
28
|
+
it "adds errors for single invalid value" do
|
29
|
+
record = MyTestClass.new(my_attr: ["tom"])
|
30
|
+
MyValidator.new(attributes: [:my_attr]).validate_each_in_array(record)
|
31
|
+
record.errors[:"my_attr[0]"].should eq(["is an invalid email"])
|
32
|
+
end
|
33
|
+
|
34
|
+
it "adds errors for multiple invalid value" do
|
35
|
+
record = MyTestClass.new(my_attr: ["tom", "rob@gmail.com", "steve@email.com"])
|
36
|
+
MyValidator.new(attributes: [:my_attr]).validate_each_in_array(record)
|
37
|
+
record.errors[:"my_attr[0]"].should eq(["is an invalid email"])
|
38
|
+
record.errors[:"my_attr[1]"].should eq([])
|
39
|
+
record.errors[:"my_attr[2]"].should eq(["is an invalid email"])
|
40
|
+
end
|
41
|
+
|
42
|
+
it "raises TypeError for non array" do
|
43
|
+
record = MyTestClass.new(my_attr: {email: "tom"})
|
44
|
+
expect { MyValidator.new(attributes: [:my_attr]).validate_each_in_array(record) }.to raise_error(TypeError, '{:email=>"tom"} is not an array')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'validates_serialized'
|
4
|
+
|
5
|
+
spec = Gem::Specification.find_by_name("validates_serialized")
|
6
|
+
gem_root = spec.gem_dir
|
7
|
+
Dir[("#{gem_root}/spec/support/**/*.rb")].each {|f| require f}
|
8
|
+
I18n.config.enforce_available_locales = true
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.mock_with :rspec
|
12
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ValidateableArray do
|
4
|
+
let!(:array) { Array.new }
|
5
|
+
|
6
|
+
subject { described_class.new(array) }
|
7
|
+
|
8
|
+
it "responds to valid?" do
|
9
|
+
subject.should be_valid
|
10
|
+
end
|
11
|
+
|
12
|
+
it "responds to .errors" do
|
13
|
+
subject.errors.should be_empty
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ValidateableHash do
|
4
|
+
let!(:hash) { { key_1: "boy", key_2: "girl" } }
|
5
|
+
|
6
|
+
subject { described_class.new(hash) }
|
7
|
+
|
8
|
+
it "responds to valid?" do
|
9
|
+
subject.should be_valid
|
10
|
+
end
|
11
|
+
|
12
|
+
it "responds to .errors" do
|
13
|
+
subject.errors.should be_empty
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "attributes" do
|
17
|
+
it "defines key_1 attribute" do
|
18
|
+
subject.key_1.should eq("boy")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "defines key_2 attribute" do
|
22
|
+
subject.key_2.should eq("girl")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "returns nil for non-existent key_3" do
|
26
|
+
subject.key_3.should be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "validation block" do
|
31
|
+
it "validates when passes validation methods" do
|
32
|
+
subject.class_eval do
|
33
|
+
validates :key_1, presence: true
|
34
|
+
end
|
35
|
+
subject.should be_valid
|
36
|
+
end
|
37
|
+
|
38
|
+
it "handles errors when invalid" do
|
39
|
+
subject.class_eval do
|
40
|
+
validates :key_1, inclusion: { in: [ 'a', 'b', 'c' ] }
|
41
|
+
end
|
42
|
+
subject.should_not be_valid
|
43
|
+
subject.errors[:key_1].should eq(["is not included in the list"])
|
44
|
+
end
|
45
|
+
|
46
|
+
it "is invalid when validating non-existent property" do
|
47
|
+
subject.class_eval do
|
48
|
+
validates :other_key, inclusion: { in: [ 'a', 'b', 'c' ] }
|
49
|
+
end
|
50
|
+
subject.should_not be_valid
|
51
|
+
subject.errors[:other_key].should eq(["is not included in the list"])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ValidateableObject do
|
4
|
+
class TestPerson
|
5
|
+
def initialize(h={})
|
6
|
+
h.each {|k,v| send("#{k}=",v)}
|
7
|
+
end
|
8
|
+
|
9
|
+
def name
|
10
|
+
@name ||= nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def name=(val)
|
14
|
+
@name = val
|
15
|
+
end
|
16
|
+
|
17
|
+
def age
|
18
|
+
@age ||= nil
|
19
|
+
end
|
20
|
+
|
21
|
+
def age=(val)
|
22
|
+
@age = val
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
let!(:object) { TestPerson.new(age: 26, name: "Thomas") }
|
27
|
+
|
28
|
+
subject { described_class.new(object) }
|
29
|
+
|
30
|
+
it "responds to valid?" do
|
31
|
+
subject.should be_valid
|
32
|
+
end
|
33
|
+
|
34
|
+
it "responds to .errors" do
|
35
|
+
subject.errors.should be_empty
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "delegating attributes" do
|
39
|
+
it "delegates age attribute" do
|
40
|
+
subject.age.should eq(26)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "delegates name attribute" do
|
44
|
+
subject.name.should eq("Thomas")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "validation block" do
|
49
|
+
it "validates when passes validation methods" do
|
50
|
+
subject.class_eval do
|
51
|
+
validates :name, presence: true
|
52
|
+
validates :age, presence: true
|
53
|
+
end
|
54
|
+
subject.should be_valid
|
55
|
+
end
|
56
|
+
|
57
|
+
it "handles errors when invalid" do
|
58
|
+
subject.class_eval do
|
59
|
+
validates :name, inclusion: { in: [ 'a', 'b', 'c' ] }
|
60
|
+
end
|
61
|
+
subject.should_not be_valid
|
62
|
+
subject.errors[:name].should eq(["is not included in the list"])
|
63
|
+
end
|
64
|
+
|
65
|
+
it "raises error when validating non-existent property" do
|
66
|
+
subject.class_eval do
|
67
|
+
validates :other_property, inclusion: { in: [ 'a', 'b', 'c' ] }
|
68
|
+
end
|
69
|
+
expect { subject.valid? }.to raise_error(NoMethodError)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|