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.
- checksums.yaml +4 -4
- data/lib/bodhi-slam.rb +13 -109
- data/lib/bodhi-slam/context.rb +37 -51
- data/lib/bodhi-slam/enumerations.rb +35 -0
- data/lib/bodhi-slam/errors.rb +81 -4
- data/lib/bodhi-slam/resource.rb +201 -64
- data/lib/bodhi-slam/types.rb +191 -0
- data/lib/bodhi-slam/validations.rb +141 -0
- data/lib/bodhi-slam/validators.rb +60 -0
- data/lib/bodhi-slam/validators/blank.rb +24 -0
- data/lib/bodhi-slam/validators/email.rb +24 -0
- data/lib/bodhi-slam/validators/matches.rb +27 -0
- data/lib/bodhi-slam/validators/max.rb +27 -0
- data/lib/bodhi-slam/validators/min.rb +27 -0
- data/lib/bodhi-slam/validators/multi.rb +14 -0
- data/lib/bodhi-slam/validators/not_empty.rb +20 -0
- data/lib/bodhi-slam/validators/required.rb +14 -0
- data/lib/bodhi-slam/validators/type.rb +123 -0
- data/lib/bodhi-slam/validators/url.rb +24 -0
- metadata +61 -5
@@ -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
|