validates_serialized 0.0.1.pre2
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 +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
|