validates_serialized 0.0.1.pre2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ module ValidatesSerialized
2
+ VERSION = "0.0.1.pre2"
3
+ 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
@@ -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