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.
@@ -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