bodhi-slam 0.8.6 → 0.8.7
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 +3 -3
- data/lib/bodhi-slam/factory.rb +80 -20
- data/lib/bodhi-slam/properties.rb +127 -81
- data/lib/bodhi-slam/resource.rb +296 -140
- data/lib/bodhi-slam/simulator.rb +1 -5
- data/lib/bodhi-slam/support.rb +13 -3
- data/lib/bodhi-slam/types.rb +139 -15
- data/lib/bodhi-slam/validations.rb +74 -78
- metadata +16 -2
data/lib/bodhi-slam/simulator.rb
CHANGED
@@ -11,14 +11,10 @@ module Bodhi
|
|
11
11
|
include Bodhi::Properties
|
12
12
|
include Bodhi::Validations
|
13
13
|
|
14
|
-
# Initial conditions
|
15
14
|
property :starts_at, type: DateTime
|
16
15
|
property :iterations, type: Integer
|
17
16
|
property :time_units, type: String
|
18
17
|
property :time_scale, type: Integer, default: 1
|
19
|
-
|
20
|
-
# Dynamic attributes
|
21
|
-
# Updated every iteration
|
22
18
|
property :current_frame, type: SimulationFrame
|
23
19
|
|
24
20
|
# Model validations
|
@@ -91,4 +87,4 @@ module Bodhi
|
|
91
87
|
end
|
92
88
|
end
|
93
89
|
|
94
|
-
Dir[File.dirname(__FILE__) + "/simulation/*.rb"].each { |file| require file }
|
90
|
+
Dir[File.dirname(__FILE__) + "/simulation/*.rb"].each { |file| require file }
|
data/lib/bodhi-slam/support.rb
CHANGED
@@ -62,9 +62,19 @@ module Bodhi
|
|
62
62
|
if Object.const_defined?(options[:type].to_s) && Object.const_get(options[:type].to_s).ancestors.include?(Bodhi::Properties)
|
63
63
|
klass = Object.const_get(options[:type].to_s)
|
64
64
|
if options[:multi] == true
|
65
|
-
value.map
|
65
|
+
value.map do |item|
|
66
|
+
if item.respond_to?(:attributes)
|
67
|
+
klass.new(item.attributes)
|
68
|
+
else
|
69
|
+
klass.new(item)
|
70
|
+
end
|
71
|
+
end
|
66
72
|
else
|
67
|
-
|
73
|
+
if value.respond_to?(:attributes)
|
74
|
+
klass.new(value.attributes)
|
75
|
+
else
|
76
|
+
klass.new(value)
|
77
|
+
end
|
68
78
|
end
|
69
79
|
else
|
70
80
|
value
|
@@ -72,4 +82,4 @@ module Bodhi
|
|
72
82
|
end
|
73
83
|
end
|
74
84
|
end
|
75
|
-
end
|
85
|
+
end
|
data/lib/bodhi-slam/types.rb
CHANGED
@@ -1,9 +1,13 @@
|
|
1
1
|
module Bodhi
|
2
|
+
# Interface for interacting with the BodhiType resource.
|
2
3
|
class Type
|
3
4
|
include Bodhi::Factories
|
4
5
|
include Bodhi::Properties
|
5
6
|
include Bodhi::Validations
|
6
7
|
|
8
|
+
# The API context that binds this {Bodhi::Type} instance to the HotSchedules IoT Platform
|
9
|
+
# @note This is required for the all instance methods to work correctly
|
10
|
+
# @return [Bodhi::Context] the API context linked to this object
|
7
11
|
attr_accessor :bodhi_context
|
8
12
|
|
9
13
|
property :properties, type: "Object"
|
@@ -21,7 +25,6 @@ module Bodhi
|
|
21
25
|
property :embedded, type: "Boolean"
|
22
26
|
property :metadata, type: "Boolean"
|
23
27
|
property :encapsulated, type: "Boolean"
|
24
|
-
|
25
28
|
property :documentation, type: "Link"
|
26
29
|
|
27
30
|
validates :name, required: true, is_not_blank: true
|
@@ -35,12 +38,27 @@ module Bodhi
|
|
35
38
|
generates :embedded, type: "Boolean"
|
36
39
|
generates :version, type: "String"
|
37
40
|
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
41
|
-
#
|
42
|
-
#
|
41
|
+
# POST a new +Bodhi::Type+ to the HotSchedules IoT Platform
|
42
|
+
#
|
43
|
+
# Equivalent CURL command:
|
44
|
+
# curl -u username:password -X POST -H "Content-Type: application/json" \
|
45
|
+
# https://{server}/{namespace}/types \
|
46
|
+
# -d '{type properties}'
|
47
|
+
#
|
48
|
+
# @raise [RuntimeError] if the {#bodhi_context} attribute is nil
|
49
|
+
# @raise [Bodhi::ContextErrors] if the {#bodhi_context} attribute is not valid
|
50
|
+
# @raise [Bodhi::ApiErrors] if the response status is NOT +201+
|
51
|
+
# @return [nil]
|
52
|
+
# @example
|
53
|
+
# type = Bodhi::Type.new(bodhi_context: context, name: "MyType", properties: { name: { type: "String" }, age: { type: "Integer" } })
|
54
|
+
# type.save!
|
43
55
|
def save!
|
56
|
+
if bodhi_context.nil?
|
57
|
+
raise RuntimeError.new("Missing attribute #bodhi_context. Unable to send HTTP request")
|
58
|
+
elsif bodhi_context.invalid?
|
59
|
+
raise Bodhi::ContextErrors.new(context.errors.messages), context.errors.to_a.to_s
|
60
|
+
end
|
61
|
+
|
44
62
|
result = bodhi_context.connection.post do |request|
|
45
63
|
request.url "/#{bodhi_context.namespace}/types"
|
46
64
|
request.headers['Content-Type'] = 'application/json'
|
@@ -55,9 +73,38 @@ module Bodhi
|
|
55
73
|
if result.headers['location']
|
56
74
|
@sys_id = result.headers['location'].match(/types\/(?<name>[a-zA-Z0-9]+)/)[:name]
|
57
75
|
end
|
76
|
+
|
77
|
+
return nil
|
58
78
|
end
|
59
79
|
|
80
|
+
# DELETE a +Bodhi::Type+ from the HotSchedules IoT Platform.
|
81
|
+
#
|
82
|
+
# Equivalent CURL command:
|
83
|
+
# curl -u username:password -X DELETE https://{server}/{namespace}/types/{type.name}
|
84
|
+
#
|
85
|
+
# @raise [RuntimeError] if the {#bodhi_context} attribute is nil
|
86
|
+
# @raise [Bodhi::ContextErrors] if the {#bodhi_context} attribute is not valid
|
87
|
+
# @raise [Bodhi::ApiErrors] if the HTTP response status is NOT 204
|
88
|
+
# @return [nil]
|
89
|
+
# @example
|
90
|
+
# type = Bodhi::Type.new(
|
91
|
+
# bodhi_context: context,
|
92
|
+
# name: "MyType",
|
93
|
+
# properties: {
|
94
|
+
# name: { type: "String" },
|
95
|
+
# age: { type: "Integer" }
|
96
|
+
# }
|
97
|
+
# )
|
98
|
+
#
|
99
|
+
# type.save!
|
100
|
+
# type.delete!
|
60
101
|
def delete!
|
102
|
+
if bodhi_context.nil?
|
103
|
+
raise RuntimeError.new("Missing attribute #bodhi_context. Unable to send HTTP request")
|
104
|
+
elsif bodhi_context.invalid?
|
105
|
+
raise Bodhi::ContextErrors.new(context.errors.messages), context.errors.to_a.to_s
|
106
|
+
end
|
107
|
+
|
61
108
|
result = bodhi_context.connection.delete do |request|
|
62
109
|
request.url "/#{bodhi_context.namespace}/types/#{name}"
|
63
110
|
request.headers[bodhi_context.credentials_header] = bodhi_context.credentials
|
@@ -66,9 +113,37 @@ module Bodhi
|
|
66
113
|
if result.status != 204
|
67
114
|
raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
|
68
115
|
end
|
116
|
+
|
117
|
+
return nil
|
69
118
|
end
|
70
119
|
|
120
|
+
# PATCH a +Bodhi::Type+ with an Array of patch operations
|
121
|
+
#
|
122
|
+
# Equivalent CURL command:
|
123
|
+
# curl -u username:password -X PATCH -H "Content-Type: application/json" \
|
124
|
+
# https://{server}/{namespace}/types/{type.name} \
|
125
|
+
# -d '[{operation1}, {operation2}, ...]'
|
126
|
+
#
|
127
|
+
# @note This method does not update the calling object! Only the record on the API will be changed
|
128
|
+
# @todo After patch is successful, update the object with the changes.
|
129
|
+
# @param params [Array<Hash>] An array of hashes with the keys: +op+, +path+, & +value+
|
130
|
+
# @raise [RuntimeError] if the {#bodhi_context} attribute is nil
|
131
|
+
# @raise [Bodhi::ContextErrors] if the {#bodhi_context} attribute is not valid
|
132
|
+
# @raise [Bodhi::ApiErrors] if the response status is NOT +204+
|
133
|
+
# @return [nil]
|
134
|
+
# @example
|
135
|
+
# type.patch!([
|
136
|
+
# {op: "add", path: "/properties/birthday", value: { type: "DateTime" }},
|
137
|
+
# {op: "add", path: "/indexes/-", value: { keys: ["birthday"] }},
|
138
|
+
# {op: "remove", path: "/properties/age"}
|
139
|
+
# ])
|
71
140
|
def patch!(params)
|
141
|
+
if bodhi_context.nil?
|
142
|
+
raise RuntimeError.new("Missing attribute #bodhi_context. Unable to send HTTP request")
|
143
|
+
elsif bodhi_context.invalid?
|
144
|
+
raise Bodhi::ContextErrors.new(context.errors.messages), context.errors.to_a.to_s
|
145
|
+
end
|
146
|
+
|
72
147
|
result = bodhi_context.connection.patch do |request|
|
73
148
|
request.url "/#{bodhi_context.namespace}/types/#{name}"
|
74
149
|
request.headers['Content-Type'] = 'application/json'
|
@@ -79,9 +154,29 @@ module Bodhi
|
|
79
154
|
if result.status != 204
|
80
155
|
raise Bodhi::ApiErrors.new(body: result.body, status: result.status), "status: #{result.status}, body: #{result.body}"
|
81
156
|
end
|
157
|
+
|
158
|
+
return nil
|
82
159
|
end
|
83
160
|
|
161
|
+
# PUT a +Bodhi::Type+ using the properties in the given +params+
|
162
|
+
#
|
163
|
+
# @todo Break this method apart into #update! (raise error) and #update (return Boolean) methods
|
164
|
+
# @param params [Bodhi::Type, Hash, JSON String] the properties & values to update the type with
|
165
|
+
# @raise [RuntimeError] if the {#bodhi_context} attribute is nil
|
166
|
+
# @raise [Bodhi::ContextErrors] if the {#bodhi_context} attribute is not valid
|
167
|
+
# @raise [Bodhi::ApiErrors] if the HTTP response status is NOT 204
|
168
|
+
# @return [Boolean]
|
169
|
+
# @example
|
170
|
+
# type = Bodhi::Type.new(bodhi_context: context, name: "MyType", properties: { name: { type: "String" }, age: { type: "Integer" } })
|
171
|
+
# type.save!
|
172
|
+
# type.update!(name: "MyType", properties: { name: { type: "String" }, age: { type: "Integer" } }, indexes: [{ keys: ["age"] }])
|
84
173
|
def update!(params)
|
174
|
+
if bodhi_context.nil?
|
175
|
+
raise RuntimeError.new("Missing attribute #bodhi_context. Unable to send HTTP request")
|
176
|
+
elsif bodhi_context.invalid?
|
177
|
+
raise Bodhi::ContextErrors.new(context.errors.messages), context.errors.to_a.to_s
|
178
|
+
end
|
179
|
+
|
85
180
|
update_attributes(params)
|
86
181
|
|
87
182
|
if invalid?
|
@@ -103,9 +198,14 @@ module Bodhi
|
|
103
198
|
end
|
104
199
|
alias :update :update!
|
105
200
|
|
106
|
-
# Queries the Bodhi API for the given +type_name+ and
|
107
|
-
#
|
108
|
-
#
|
201
|
+
# Queries the Bodhi API for the given +type_name+ and returns a single +Bodhi::Type+ or raises an error.
|
202
|
+
#
|
203
|
+
# @param context [Bodhi::Context]
|
204
|
+
# @param type_name [String]
|
205
|
+
# @raise [Bodhi::ContextErrors] if the provided Bodhi::Context is invalid
|
206
|
+
# @raise [Bodhi::ApiErrors] if the HTTP response status is NOT 200
|
207
|
+
# @return [Bodhi::Type]
|
208
|
+
# @example
|
109
209
|
# context = BodhiContext.new(valid_params)
|
110
210
|
# type = Bodhi::Type.find(context, "MyTypeName")
|
111
211
|
# type # => #<Bodhi::Type:0x007fbff403e808 @name="MyTypeName">
|
@@ -129,8 +229,14 @@ module Bodhi
|
|
129
229
|
end
|
130
230
|
|
131
231
|
# Queries the Bodhi API for all types within the given +context+ and
|
132
|
-
# returns an array of Bodhi::Type objects
|
133
|
-
#
|
232
|
+
# returns an array of +Bodhi::Type+ objects
|
233
|
+
#
|
234
|
+
# @note This method will query ALL type records within the context and is not limited to the default 100 record limit for queries. YE BE WARNED!
|
235
|
+
# @param context [Bodhi::Context]
|
236
|
+
# @return [Array<Bodhi::Type>] all Bodhi::Type records within the given context
|
237
|
+
# @raise [Bodhi::ContextErrors] if the provided Bodhi::Context is invalid
|
238
|
+
# @raise [Bodhi::ApiErrors] if the HTTP response status is NOT 200
|
239
|
+
# @example
|
134
240
|
# context = BodhiContext.new(valid_params)
|
135
241
|
# types = Bodhi::Type.find_all(context)
|
136
242
|
# types # => [#<Bodhi::Type:0x007fbff403e808 @name="MyType">, #<Bodhi::Type:0x007fbff403e808 @name="MyType2">, ...]
|
@@ -164,15 +270,33 @@ module Bodhi
|
|
164
270
|
end
|
165
271
|
end
|
166
272
|
|
273
|
+
# Search for +Bodhi::Type+ records using MongoDB query operators.
|
274
|
+
#
|
275
|
+
# @note This method will NOT return more than 100 records at a time!
|
276
|
+
# @param query [Hash, JSON String] The MongoDB query to use for the search
|
277
|
+
# @return [Bodhi::Query<Bodhi::Type>] A query object for +Bodhi::Types+ using the given +query+
|
278
|
+
# @example
|
279
|
+
# query_obj = Bodhi::Type.where(name: "MyType")
|
280
|
+
# query_obj.from(context).all #=> [#<Bodhi::Type:0x007fbff403e808 @name="MyType">]
|
281
|
+
#
|
282
|
+
# json = '{"name":{ "$in": ["MyType", "MyType2"] }}'
|
283
|
+
# query_obj = Bodhi::Type.where(json)
|
284
|
+
# query_obj.from(context).all #=> [#<Bodhi::Type:0x007fbff403e808 @name="MyType">, #<Bodhi::Type:0x007fbff403e808 @name="MyType2">]
|
167
285
|
def self.where(query)
|
168
286
|
query_obj = Bodhi::Query.new(Bodhi::Type, "types")
|
169
287
|
query_obj.where(query)
|
170
288
|
query_obj
|
171
289
|
end
|
172
290
|
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
291
|
+
# Defines a new Ruby class using the given +Bodhi::Type+
|
292
|
+
# and includes the {Bodhi::Resource} and ActiveModel::Model modules
|
293
|
+
#
|
294
|
+
# @todo Break apart creating classes and setting classes to a constant
|
295
|
+
# @note This method uses +Object.const_set+ to create new Classes. Old definitions will be overwritten!
|
296
|
+
# @param type [Bodhi::Type]
|
297
|
+
# @return [Class] the new {Bodhi::Resource}
|
298
|
+
# @raise [ArgumentError] if the +type+ param is not a +Bodhi::Type+
|
299
|
+
# @example
|
176
300
|
# type = Bodhi::Type.new({name: "TestType", properties: { foo:{ type:"String" }}})
|
177
301
|
# klass = Bodhi::Type.create_class_with(type)
|
178
302
|
# klass # => #<Class:0x007fbff403e808 @name="TestType">
|
@@ -215,4 +339,4 @@ module Bodhi
|
|
215
339
|
end
|
216
340
|
end
|
217
341
|
|
218
|
-
Dir[File.dirname(__FILE__) + "/types/*.rb"].each { |file| require file }
|
342
|
+
Dir[File.dirname(__FILE__) + "/types/*.rb"].each { |file| require file }
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Bodhi
|
2
2
|
module Validations
|
3
|
-
|
3
|
+
|
4
4
|
module ClassMethods
|
5
|
-
|
5
|
+
|
6
6
|
# Returns a Hash of all validations present for the class
|
7
7
|
#
|
8
8
|
# class User
|
@@ -22,7 +22,7 @@ module Bodhi
|
|
22
22
|
# ]
|
23
23
|
# }
|
24
24
|
def validators; @validators; end
|
25
|
-
|
25
|
+
|
26
26
|
# Creates a new validation on the given +attribute+ using the supplied +options+
|
27
27
|
#
|
28
28
|
# class User
|
@@ -36,23 +36,23 @@ module Bodhi
|
|
36
36
|
unless attribute.is_a? Symbol
|
37
37
|
raise ArgumentError.new("Invalid :attribute argument. Expected #{attribute.class} to be a Symbol")
|
38
38
|
end
|
39
|
-
|
39
|
+
|
40
40
|
unless options.is_a? Hash
|
41
41
|
raise ArgumentError.new("Invalid :options argument. Expected #{options.class} to be a Hash")
|
42
42
|
end
|
43
|
-
|
43
|
+
|
44
44
|
if options.keys.empty?
|
45
45
|
raise ArgumentError.new("Invalid :options argument. Options can not be empty")
|
46
46
|
end
|
47
47
|
|
48
|
-
options = options.reduce({}) do |memo, (k, v)|
|
48
|
+
options = options.reduce({}) do |memo, (k, v)|
|
49
49
|
memo.merge({ k.to_sym => v})
|
50
50
|
end
|
51
51
|
|
52
52
|
@validators[attribute] = []
|
53
53
|
options.each_pair do |key, value|
|
54
54
|
key = key.to_s.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase.to_sym
|
55
|
-
|
55
|
+
|
56
56
|
unless [:ref].include?(key)
|
57
57
|
if key == :type && value == "Enumerated"
|
58
58
|
@validators[attribute] << Bodhi::Validator.constantize(key).new(value, options[:ref])
|
@@ -63,84 +63,80 @@ module Bodhi
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
array.each do |validator|
|
106
|
-
validator.validate(self, attribute, value)
|
107
|
-
end
|
66
|
+
|
67
|
+
# Returns a +Bodhi::Errors+ object that holds all information about attribute error messages.
|
68
|
+
#
|
69
|
+
# class User
|
70
|
+
# include Bodhi::Validations
|
71
|
+
#
|
72
|
+
# attr_accessor :name
|
73
|
+
# validates :name, required: true
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# user = User.new
|
77
|
+
# user.valid? # => false
|
78
|
+
# user.errors # => #<Bodhi::Errors:0x007fbff403e808 @messages={name:["is required"]}>
|
79
|
+
def errors
|
80
|
+
@errors ||= Bodhi::Errors.new
|
81
|
+
end
|
82
|
+
|
83
|
+
# Runs all class validations on object and adds any errors to the +Bodhi::Errors+ object
|
84
|
+
#
|
85
|
+
# class User
|
86
|
+
# include Bodhi::Validations
|
87
|
+
#
|
88
|
+
# attr_accessor :name
|
89
|
+
# validates :name, required: true
|
90
|
+
# end
|
91
|
+
#
|
92
|
+
# user = User.new
|
93
|
+
# user.validate! # => nil
|
94
|
+
# user.errors.full_messages # => ["name is required"]
|
95
|
+
#
|
96
|
+
# user.name = "Bob"
|
97
|
+
# user.validate! # => nil
|
98
|
+
# user.errors.full_messages # => []
|
99
|
+
def validate!
|
100
|
+
errors.clear
|
101
|
+
self.class.validators.each_pair do |attribute, array|
|
102
|
+
value = self.send(attribute)
|
103
|
+
array.each do |validator|
|
104
|
+
validator.validate(self, attribute, value)
|
108
105
|
end
|
109
106
|
end
|
110
|
-
|
111
|
-
# Runs all validations and returns +true+ if no errors are present otherwise +false+.
|
112
|
-
#
|
113
|
-
# class User
|
114
|
-
# include Bodhi::Validations
|
115
|
-
#
|
116
|
-
# attr_accessor :name
|
117
|
-
# validates :name, required: true
|
118
|
-
# end
|
119
|
-
#
|
120
|
-
# user = User.new
|
121
|
-
# user.valid? # => false
|
122
|
-
# user.errors.full_messages # => ["name is required"]
|
123
|
-
#
|
124
|
-
# user.name = "Bob"
|
125
|
-
# user.valid? # => true
|
126
|
-
# user.errors.full_messages # => []
|
127
|
-
def valid?
|
128
|
-
validate!
|
129
|
-
!errors.messages.any?
|
130
|
-
end
|
131
|
-
|
132
|
-
# Runs all validations and returns +false+ if no errors are present otherwise +true+.
|
133
|
-
def invalid?
|
134
|
-
!valid?
|
135
|
-
end
|
136
107
|
end
|
137
|
-
|
108
|
+
|
109
|
+
# Runs all validations and returns +true+ if no errors are present otherwise +false+.
|
110
|
+
#
|
111
|
+
# class User
|
112
|
+
# include Bodhi::Validations
|
113
|
+
#
|
114
|
+
# attr_accessor :name
|
115
|
+
# validates :name, required: true
|
116
|
+
# end
|
117
|
+
#
|
118
|
+
# user = User.new
|
119
|
+
# user.valid? # => false
|
120
|
+
# user.errors.full_messages # => ["name is required"]
|
121
|
+
#
|
122
|
+
# user.name = "Bob"
|
123
|
+
# user.valid? # => true
|
124
|
+
# user.errors.full_messages # => []
|
125
|
+
def valid?
|
126
|
+
validate!
|
127
|
+
!errors.messages.any?
|
128
|
+
end
|
129
|
+
|
130
|
+
# Runs all validations and returns +false+ if no errors are present otherwise +true+.
|
131
|
+
def invalid?
|
132
|
+
!valid?
|
133
|
+
end
|
134
|
+
|
138
135
|
def self.included(base)
|
139
136
|
base.extend(ClassMethods)
|
140
|
-
base.include(InstanceMethods)
|
141
137
|
base.instance_variable_set(:@validators, Hash.new)
|
142
138
|
end
|
143
139
|
end
|
144
140
|
end
|
145
141
|
|
146
|
-
require File.dirname(__FILE__) + "/validators.rb"
|
142
|
+
require File.dirname(__FILE__) + "/validators.rb"
|