bodhi-slam 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,191 @@
1
+ module Bodhi
2
+ class Type
3
+ include Bodhi::Validations
4
+
5
+ BODHI_SYSTEM_ATTRIBUTES = [:sys_created_at, :sys_version, :sys_modified_at, :sys_modified_by,
6
+ :sys_namespace, :sys_created_by, :sys_type_version, :sys_id]
7
+ BODHI_TYPE_ATTRIBUTES = [:name, :namespace, :package, :embedded, :properties]
8
+
9
+ attr_accessor *BODHI_TYPE_ATTRIBUTES
10
+ attr_reader *BODHI_SYSTEM_ATTRIBUTES
11
+ attr_reader :validations
12
+
13
+ validates :name, required: true, is_not_blank: true
14
+ validates :namespace, required: true
15
+ validates :properties, required: true
16
+
17
+ def initialize(params={})
18
+ params.symbolize_keys!
19
+
20
+ BODHI_TYPE_ATTRIBUTES.each do |attribute|
21
+ send("#{attribute}=", params[attribute])
22
+ end
23
+
24
+ @validations = {}
25
+ if properties
26
+ properties.symbolize_keys!
27
+ properties.each_pair do |attr_name, attr_properties|
28
+ attr_properties.symbolize_keys!
29
+ @validations[attr_name] = []
30
+ attr_properties.each_pair do |option, value|
31
+ underscored_name = option.to_s.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase.to_sym
32
+ unless [:system, :trim, :ref, :unique, :default, :is_current_user].include? underscored_name
33
+ klass = Bodhi::Validator.constantize(underscored_name)
34
+ if option == :type && value == "Enumerated"
35
+ if attr_properties[:ref].nil?
36
+ raise RuntimeError.new("No reference property found! Cannot build enumeration validator for #{name}.#{attr_name}")
37
+ end
38
+
39
+ @validations[attr_name] << klass.new(value, attr_properties[:ref])
40
+ else
41
+ @validations[attr_name] << klass.new(value)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def self.find_all(context)
50
+ raise context.errors unless context.valid?
51
+
52
+ result = context.connection.get do |request|
53
+ request.url "/#{context.namespace}/types"
54
+ request.headers[context.credentials_header] = context.credentials
55
+ end
56
+
57
+ if result.status != 200
58
+ errors = JSON.parse result.body
59
+ errors.each{|error| error['status'] = result.status } if errors.is_a? Array
60
+ errors["status"] = result.status if errors.is_a? Hash
61
+ raise errors.to_s
62
+ end
63
+
64
+ JSON.parse(result.body).collect{ |type| Bodhi::Type.new(type) }
65
+ end
66
+
67
+ def self.create_class_with(type)
68
+ unless type.is_a? Bodhi::Type
69
+ raise ArgumentError.new("Expected #{type.class} to be a Bodhi::Type")
70
+ end
71
+
72
+ klass = Object.const_set(type.name, Class.new {
73
+ include Bodhi::Resource
74
+ attr_accessor *type.properties.keys
75
+ })
76
+
77
+ type.validations.each_pair do |attribute, validations|
78
+ attr_options = Hash.new
79
+ validations.each{ |validation| attr_options.merge!(validation.to_options) }
80
+ klass.validates(attribute, attr_options)
81
+ end
82
+
83
+ klass
84
+ end
85
+
86
+ def self.create_factory_with(type)
87
+ unless type.is_a? Bodhi::Type
88
+ raise ArgumentError.new("Expected #{type.class} to be a Bodhi::Type")
89
+ end
90
+
91
+ FactoryGirl.define do
92
+ factory type.name.to_sym do
93
+ type.properties.each_pair do |attribute, options|
94
+ unless options[:system]
95
+
96
+ case options[:type]
97
+ when "GeoJSON"
98
+ if options[:multi].nil?
99
+ send(attribute) { {type: "Point", coordinates: [10,20]} }
100
+ else
101
+ send(attribute) { [*0..5].sample.times.collect{ {type: "Point", coordinates: [10,20]} } }
102
+ end
103
+
104
+ when "Boolean"
105
+ if options[:multi].nil?
106
+ send(attribute) { [true, false].sample }
107
+ else
108
+ send(attribute) { [*0..5].sample.times.collect{ [true, false].sample } }
109
+ end
110
+
111
+ when "Enumerated"
112
+ reference = options[:ref].split('.')
113
+ name = reference[0]
114
+ field = reference[1]
115
+
116
+ if options[:multi].nil?
117
+ if field.nil?
118
+ send(attribute) { Bodhi::Enumeration.cache[name.to_sym].values.sample }
119
+ else
120
+ Bodhi::Enumeration.cache[name.to_sym].values.map!{ |value| value.symbolize_keys! }
121
+ send(attribute) { Bodhi::Enumeration.cache[name.to_sym].values.sample[field.to_sym] }
122
+ end
123
+ else
124
+ if field.nil?
125
+ send(attribute) { [*0..5].sample.times.collect{ Bodhi::Enumeration.cache[name.to_sym].values.sample } }
126
+ else
127
+ send(attribute) { [*0..5].sample.times.collect{ Bodhi::Enumeration.cache[name.to_sym].values.sample[field.to_sym] } }
128
+ end
129
+ end
130
+
131
+ when "Object"
132
+ if options[:multi].nil?
133
+ send(attribute) { {SecureRandom.hex => SecureRandom.hex} }
134
+ else
135
+ send(attribute) { [*0..5].sample.times.collect{ {SecureRandom.hex => SecureRandom.hex} } }
136
+ end
137
+
138
+ when "String"
139
+ if options[:multi].nil?
140
+ send(attribute) { SecureRandom.hex }
141
+ else
142
+ send(attribute) { [*0..5].sample.times.collect{ SecureRandom.hex } }
143
+ end
144
+
145
+ when "DateTime"
146
+ if options[:multi].nil?
147
+ send(attribute) { Time.at(rand * Time.now.to_i).iso8601 }
148
+ else
149
+ send(attribute) { [*0..5].sample.times.collect{ Time.at(rand * Time.now.to_i).iso8601 } }
150
+ end
151
+
152
+ when "Integer"
153
+ min = -10000
154
+ max = 10000
155
+ if options[:min]
156
+ min = options[:min]
157
+ end
158
+
159
+ if options[:max]
160
+ max = options[:max]
161
+ end
162
+
163
+ if options[:multi].nil?
164
+ send(attribute) { rand(min..max) }
165
+ else
166
+ send(attribute) { [*0..5].sample.times.collect{ rand(min..max) } }
167
+ end
168
+
169
+ when "Real"
170
+ if options[:multi].nil?
171
+ send(attribute) { SecureRandom.random_number*[-1,1,1,1].sample*[10,100,1000,10000].sample }
172
+ else
173
+ send(attribute) { [*0..5].sample.times.collect{ SecureRandom.random_number*[-1,1,1,1].sample*[10,100,1000,10000].sample } }
174
+ end
175
+
176
+ else # Its an embedded type
177
+ if options[:multi].nil?
178
+ send(attribute) { FactoryGirl.build(options[:type]) }
179
+ else
180
+ send(attribute) { [*0..5].sample.times.collect{ FactoryGirl.build(options[:type]) } }
181
+ end
182
+ end
183
+
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,141 @@
1
+ module Bodhi
2
+ module Validations
3
+
4
+ module ClassMethods
5
+
6
+ # Returns a Hash of all validations present for the class
7
+ #
8
+ # class User
9
+ # include Bodhi::Validations
10
+ # attr_accessor :name, :tags
11
+ #
12
+ # validates :tags, requried: true, multi: true
13
+ # validates :name, required: true
14
+ #
15
+ # User.validations
16
+ # # => {
17
+ # # tags: [
18
+ # # #<RequiredValidator:0x007fbff403e808 @options={}>,
19
+ # # #<MultiValidator:0x007fbff403e808 @options={}>
20
+ # # ],
21
+ # # name: [
22
+ # # #<RequiredValidator:0x007fbff403e808 @options={}>
23
+ # # ]
24
+ # # }
25
+ def validators; @validators; end
26
+
27
+ # Creates a new validation on the given +attribute+ using the supplied +options+
28
+ #
29
+ # class User
30
+ # include Bodhi::Validations
31
+ # attr_accessor :name, :address, :tags
32
+ #
33
+ # validates :name, type: "String", required: true
34
+ # validates :address, type: "PostalAddress", required: true
35
+ # validates :tags, type: "String", multi: true
36
+ def validates(attribute, options)
37
+ unless attribute.is_a? Symbol
38
+ raise ArgumentError.new("Invalid :attribute argument. Expected #{attribute.class} to be a Symbol")
39
+ end
40
+
41
+ unless options.is_a? Hash
42
+ raise ArgumentError.new("Invalid :options argument. Expected #{options.class} to be a Hash")
43
+ end
44
+
45
+ if options.keys.empty?
46
+ raise ArgumentError.new("Invalid :options argument. Options can not be empty")
47
+ end
48
+
49
+ @validators[attribute] = []
50
+ options.each_pair do |key, value|
51
+ unless [:ref].include?(key)
52
+ if key == :type && value == "Enumerated"
53
+ @validators[attribute] << Bodhi::Validator.constantize(key).new(value, options[:ref])
54
+ else
55
+ @validators[attribute] << Bodhi::Validator.constantize(key).new(value)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ module InstanceMethods
63
+
64
+ # Returns a +Bodhi::Errors+ object that holds all information about attribute error messages.
65
+ #
66
+ # class User
67
+ # include Bodhi::Validations
68
+ #
69
+ # attr_accessor :name
70
+ # validates :name, required: true
71
+ # end
72
+ #
73
+ # user = User.new
74
+ # user.valid? # => false
75
+ # user.errors # => #<Bodhi::Errors:0x007fbff403e808 @messages={name:["is required"]}>
76
+ def errors
77
+ @errors ||= Bodhi::Errors.new
78
+ end
79
+
80
+ # Runs all class validations on object and adds any errors to the +Bodhi::Errors+ object
81
+ #
82
+ # class User
83
+ # include Bodhi::Validations
84
+ #
85
+ # attr_accessor :name
86
+ # validates :name, required: true
87
+ # end
88
+ #
89
+ # user = User.new
90
+ # user.validate! # => nil
91
+ # user.errors.full_messages # => ["name is required"]
92
+ #
93
+ # user.name = "Bob"
94
+ # user.validate! # => nil
95
+ # user.errors.full_messages # => []
96
+ def validate!
97
+ errors.clear
98
+ self.class.validators.each_pair do |attribute, array|
99
+ value = self.send(attribute)
100
+ array.each do |validator|
101
+ validator.validate(self, attribute, value)
102
+ end
103
+ end
104
+ end
105
+
106
+ # Runs all validations and returns +true+ if no errors are present otherwise +false+.
107
+ #
108
+ # class User
109
+ # include Bodhi::Validations
110
+ #
111
+ # attr_accessor :name
112
+ # validates :name, required: true
113
+ # end
114
+ #
115
+ # user = User.new
116
+ # user.valid? # => false
117
+ # user.errors.full_messages # => ["name is required"]
118
+ #
119
+ # user.name = "Bob"
120
+ # user.valid? # => true
121
+ # user.errors.full_messages # => []
122
+ def valid?
123
+ validate!
124
+ !errors.messages.any?
125
+ end
126
+
127
+ # Runs all validations and returns +false+ if no errors are present otherwise +true+.
128
+ def invalid?
129
+ !valid?
130
+ end
131
+ end
132
+
133
+ def self.included(base)
134
+ base.extend(ClassMethods)
135
+ base.include(InstanceMethods)
136
+ base.instance_variable_set(:@validators, Hash.new)
137
+ end
138
+ end
139
+ end
140
+
141
+ require File.dirname(__FILE__) + "/validators.rb"
@@ -0,0 +1,60 @@
1
+ module Bodhi
2
+ class Validator
3
+
4
+ # Override this method in subclasses with validation logic, adding errors
5
+ # to the records +errors+ array where necessary.
6
+ def validate(record, attribute, value)
7
+ raise NotImplementedError, "Subclasses must implement a validate(record, attribute, value) method."
8
+ end
9
+
10
+ # Calls +underscore+ on the validation and returns it's class name as a symbol.
11
+ # Namespaces and the trailing "_validation" text will be trimmed
12
+ #
13
+ # BaseValidation.to_sym # => :base
14
+ # StringValidation.to_sym # => :string
15
+ # NotBlankValidation.to_sym # => :not_blank
16
+ def to_sym
17
+ underscore.
18
+ gsub("bodhi/", "").
19
+ gsub("_validator", "").
20
+ to_sym
21
+ end
22
+
23
+ # Returns the validation's class name in snake_case.
24
+ #
25
+ # BaseValidation.underscore # => "bodhi/base_validation"
26
+ # StringValidation.underscore # => "bodhi/string_validation"
27
+ # NotBlankValidation.underscore # => "bodhi/not_blank_validation"
28
+ def underscore
29
+ self.class.name.gsub(/::/, '/').
30
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
31
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
32
+ tr("-", "_").
33
+ downcase
34
+ end
35
+
36
+ # Returns the validation as an options Hash.
37
+ # The options hash is suitable to be used in the Bodhi::Validations.valdiates method
38
+ #
39
+ # StringValidation.to_options # => { string: true }
40
+ # EmbeddedValidation.to_options # => { embedded: "ClassName" }
41
+ # EnumeratedValidation.to_options # => { enumerated: "Country.name" }
42
+ def to_options
43
+ raise NotImplementedError, "Subclasses must implement a to_options method."
44
+ end
45
+
46
+ # Returns the validator class with the given +name+
47
+ # Raises NameError if no validator class is found
48
+ #
49
+ # Bodhi::Validator.constantize("type") # => Bodhi::TypeValidator
50
+ # Bodhi::Validator.constantize("multi") # => Bodhi::MutliValidator
51
+ # Bodhi::Validator.constantize("required") # => Bodhi::RequriedValidator
52
+ def self.constantize(name)
53
+ camelized_name = name.to_s.split('_').collect(&:capitalize).join
54
+ full_name = "Bodhi::#{camelized_name}Validator"
55
+ Object.const_get(full_name)
56
+ end
57
+ end
58
+ end
59
+
60
+ Dir[File.dirname(__FILE__) + "/validators/*.rb"].each { |file| require file }
@@ -0,0 +1,24 @@
1
+ module Bodhi
2
+ class IsNotBlankValidator < Validator
3
+
4
+ def initialize(value); end
5
+
6
+ def validate(record, attribute, value)
7
+ unless value.nil?
8
+
9
+ if value.is_a?(Array)
10
+ unless value.empty?
11
+ record.errors.add(attribute, "must not contain blank Strings") unless value.delete_if{ |v| !v.empty? }.empty?
12
+ end
13
+ else
14
+ record.errors.add(attribute, "can not be blank") if value.empty?
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ def to_options
21
+ {self.to_sym => true}
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module Bodhi
2
+ class IsEmailValidator < Validator
3
+
4
+ def initialize(value); end
5
+
6
+ def validate(record, attribute, value)
7
+ unless value.nil?
8
+
9
+ if value.is_a?(Array)
10
+ unless value.empty?
11
+ record.errors.add(attribute, "must only contain valid email addresses") unless value.delete_if{ |v| v =~ /.+@.+\..+/i }.empty?
12
+ end
13
+ else
14
+ record.errors.add(attribute, "must be a valid email address") unless value =~ /.+@.+\..+/i
15
+ end
16
+
17
+ end
18
+ end
19
+
20
+ def to_options
21
+ {self.to_sym => true}
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,27 @@
1
+ module Bodhi
2
+ class MatchesValidator < Validator
3
+ attr_reader :value
4
+
5
+ def initialize(value)
6
+ @value = value
7
+ end
8
+
9
+ def validate(record, attribute, value)
10
+ unless value.nil?
11
+
12
+ if value.is_a?(Array)
13
+ unless value.empty?
14
+ record.errors.add(attribute, "must only contain values matching #{@value}") unless value.delete_if{ |v| v.match(@value) }.empty?
15
+ end
16
+ else
17
+ record.errors.add(attribute, "must match #{@value}") unless value.match(@value)
18
+ end
19
+
20
+ end
21
+ end
22
+
23
+ def to_options
24
+ {self.to_sym => @value}
25
+ end
26
+ end
27
+ end