hatch 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZmM2NTJmMWJlZWRmMGRmNTliMmUwMmFkYjhiYTcyM2I2ZDkwY2Y5Mg==
5
+ data.tar.gz: !binary |-
6
+ ZDhmMDliNGQ3Zjk2MzRlNzk5YTRjMDI4Y2E4ZTI3Yjg1YWZlNjhmOQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YTUwMzA0MjVmOGJjZjc0OTRkM2FhNDRlNDM3MGFkMTM0YWY4ZTc5ZGYyMzlm
10
+ NDc2ZDFhNjBlOWM1ODAwYTA3NTBmZjQzYjljNmI5OTkxYmEwMjRmZTVhMDcz
11
+ ZWZmYTc3YzRmM2ZhODA2OGRmZDU5MjUxNTRjYjdhMTZjMGYyODE=
12
+ data.tar.gz: !binary |-
13
+ ZTNlNTgwNjVjMGU0ZGU3NGIzNWMwNGNlMzlhZWEyYzJhN2EyNzZhNjQxNzE4
14
+ ZTY3NjI3ZjAxNzU2MDQ4MDgwOTE0MmIzM2M4YjU4YmIxMGU4ZDAyNzQyOGJk
15
+ ZGI1MmE0OGUzMGUxMTFjZWNjNzY1ZTUwMTgzODA2ZDZmMzgyM2I=
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ task :test do
2
+ Dir["test/*_test.rb"].each { |file| load file }
3
+ end
4
+
5
+ task :default => :test
6
+
data/hatch.gemspec ADDED
@@ -0,0 +1,18 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'hatch'
3
+ s.version = '0.0.4'
4
+ s.date = Time.now.strftime('%Y-%m-%d')
5
+ s.summary = 'Keep valid objects only'
6
+ s.description = "An address without a street? A person without a name? You don't need no invalid objects!"
7
+ s.authors = ['Lucas Tolchinsky']
8
+ s.email = ['lucas.tolchinsky@gmail.com']
9
+ s.homepage = 'http://github.com/tonchis/hatch'
10
+ s.license = 'MIT'
11
+
12
+ s.files = Dir[
13
+ '*.gemspec',
14
+ 'Rakefile',
15
+ 'test/*.*',
16
+ 'lib/*.rb'
17
+ ]
18
+ end
data/lib/hatch.rb ADDED
@@ -0,0 +1,134 @@
1
+ module Hatch
2
+ def self.included(klass)
3
+ klass.extend(ClassMethods)
4
+ end
5
+
6
+ def errors
7
+ []
8
+ end
9
+
10
+ def valid?
11
+ true
12
+ end
13
+
14
+ module ClassMethods
15
+ @@validations = {}
16
+ @@attributes = {}
17
+
18
+ def self.extended(klass)
19
+ klass_symbol = klass.to_s.to_sym
20
+ @@validations[klass_symbol] = {}
21
+ @@attributes[klass_symbol] = []
22
+
23
+ klass.class_eval <<-EOS
24
+ class Invalid#{klass}
25
+ include InvalidInstanceMethods
26
+ end
27
+ EOS
28
+ end
29
+
30
+ def attributes(*args)
31
+ @@attributes[self.to_s.to_sym] = args
32
+ end
33
+
34
+ def certify(attribute, error, &block)
35
+ @@validations[self.to_s.to_sym][attribute] = {error: error, validation: block}
36
+ end
37
+
38
+ def hatch(args = {})
39
+ validated_attributes = []
40
+ klass_symbol = self.to_s.to_sym
41
+ @@attributes[klass_symbol].each do |attribute|
42
+ validation = @@validations[klass_symbol][attribute]
43
+ validated_attributes << Validator.validate(attribute,
44
+ args[attribute],
45
+ validation[:error],
46
+ &validation[:validation])
47
+ end
48
+
49
+ build(validated_attributes)
50
+ end
51
+
52
+ module InvalidInstanceMethods
53
+ attr_reader :errors
54
+
55
+ def initialize(*validated_attributes)
56
+ @validated_attributes = validated_attributes
57
+ select_errors
58
+ respond_to_instance_methods
59
+ end
60
+
61
+ def valid?
62
+ false
63
+ end
64
+
65
+ private
66
+
67
+ def respond_to_instance_methods
68
+ extended_klass = Kernel.const_get(self.class.to_s.split("Invalid").last)
69
+ address_instance_methods = extended_klass.instance_methods(false)
70
+
71
+ attributes_with_reader = @validated_attributes.select do |validated_attribute|
72
+ address_instance_methods.include?(validated_attribute.attr)
73
+ end
74
+
75
+ attributes_with_reader.each do |attribute|
76
+ self.class.send :define_method, attribute.attr, -> {attribute.value}
77
+ end
78
+ end
79
+
80
+ def select_errors
81
+ @errors = @validated_attributes.select do |validated_attribute|
82
+ validated_attribute.invalid?
83
+ end.map(&:error)
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def build(validated_attributes)
90
+ validation = -> do
91
+ valid = true
92
+ validated_attributes.each do |validated_attribute|
93
+ valid = valid && validated_attribute.valid?
94
+ end
95
+
96
+ valid
97
+ end
98
+
99
+ if validation.call
100
+ set_instance_variables(new, *validated_attributes)
101
+ else
102
+ const_get("Invalid#{self}").new(*validated_attributes)
103
+ end
104
+ end
105
+
106
+ def set_instance_variables(instance, *args)
107
+ @@attributes[instance.class.to_s.to_sym].each_with_index do |attribute, index|
108
+ instance.instance_variable_set("@#{attribute}", args[index].value)
109
+ end
110
+ instance
111
+ end
112
+ end
113
+
114
+ class Validator
115
+ attr_reader :attr, :value, :error
116
+
117
+ def self.validate(attr, value, error, &block)
118
+ new(attr, value, error, yield(value))
119
+ end
120
+
121
+ def initialize(attr, value, error, valid)
122
+ @attr, @value, @error, @valid = attr, value, error, valid
123
+ end
124
+
125
+ def valid?
126
+ @valid
127
+ end
128
+
129
+ def invalid?
130
+ !@valid
131
+ end
132
+ end
133
+ end
134
+
data/test/helper.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'test/unit'
2
+ require 'pry'
3
+ require_relative '../lib/hatch'
4
+
5
+ class Address
6
+ attr_reader :city, :street, :number
7
+
8
+ include Hatch
9
+ attributes :city, :street, :number
10
+
11
+ certify(:street, "Address must have a street") do |street|
12
+ !street.nil? && !street.empty?
13
+ end
14
+
15
+ certify(:number, "Address must have a positive number") do |number|
16
+ !number.nil? && number > 0
17
+ end
18
+
19
+ certify(:city, "Address must have a city") do |city|
20
+ !city.nil? && !city.empty?
21
+ end
22
+ end
23
+
@@ -0,0 +1,18 @@
1
+ require_relative 'helper'
2
+
3
+ class PolymorphismTest < Test::Unit::TestCase
4
+ def test_polymorphism
5
+ address = Address.hatch(street: "Fake St", number: 1234, city: "Buenos Aires")
6
+ assert address.is_a?(Address)
7
+ assert_equal address.street, "Fake St"
8
+ assert_equal address.number, 1234
9
+ assert_equal address.city, "Buenos Aires"
10
+
11
+ address = Address.hatch(street: "Fake St", number: -1, city: "Buenos Aires")
12
+ assert address.is_a?(Address::InvalidAddress)
13
+ assert_equal address.street, "Fake St"
14
+ assert_equal address.number, -1
15
+ assert_equal address.city, "Buenos Aires"
16
+ end
17
+ end
18
+
@@ -0,0 +1,34 @@
1
+ require_relative 'helper'
2
+
3
+ class ValidTest < Test::Unit::TestCase
4
+ def test_valid
5
+ address = Address.hatch(street: "Fake St", city: "Buenos Aires", number: 1234)
6
+ assert address.is_a?(Address)
7
+ assert_equal address.instance_variable_get("@street"), "Fake St"
8
+ assert_equal address.instance_variable_get("@number"), 1234
9
+ assert_equal address.instance_variable_get("@city"), "Buenos Aires"
10
+
11
+ assert address.respond_to?(:errors)
12
+ assert address.errors.empty?
13
+ assert address.respond_to?(:valid?)
14
+ assert address.valid?
15
+ end
16
+
17
+ def test_invalid
18
+ address = Address.hatch(city: "Buenos Aires", street: "", number: 1234)
19
+ assert address.is_a?(Address::InvalidAddress)
20
+ assert address.errors.include?("Address must have a street")
21
+ assert !address.errors.include?("Address must have a positive number")
22
+ assert !address.errors.include?("Address must have a city")
23
+
24
+ address = Address.hatch(street: "", number: -4)
25
+ assert address.is_a?(Address::InvalidAddress)
26
+ assert address.errors.include?("Address must have a street")
27
+ assert address.errors.include?("Address must have a positive number")
28
+ assert address.errors.include?("Address must have a city")
29
+
30
+ assert address.respond_to?(:valid?)
31
+ assert !address.valid?
32
+ end
33
+ end
34
+
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hatch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Tolchinsky
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-04-19 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: An address without a street? A person without a name? You don't need
14
+ no invalid objects!
15
+ email:
16
+ - lucas.tolchinsky@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - hatch.gemspec
22
+ - Rakefile
23
+ - test/helper.rb
24
+ - test/polymorphism_test.rb
25
+ - test/validation_test.rb
26
+ - lib/hatch.rb
27
+ homepage: http://github.com/tonchis/hatch
28
+ licenses:
29
+ - MIT
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ! '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 2.0.0
48
+ signing_key:
49
+ specification_version: 4
50
+ summary: Keep valid objects only
51
+ test_files: []