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.
@@ -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 }
@@ -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{|item| klass.new(item) }
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
- klass.new(value)
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
@@ -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
- # Saves the resource to the Bodhi Cloud. Raises ArgumentError if record could not be saved.
39
- #
40
- # obj = Resouce.new
41
- # obj.save!
42
- # obj.persisted? # => true
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
- # returns a Bodhi::Type
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
- # Dynamically defines a new Ruby class for the given +type+
174
- # Class validations, factory, and helper methods will also be added
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
- module InstanceMethods
68
-
69
- # Returns a +Bodhi::Errors+ object that holds all information about attribute error messages.
70
- #
71
- # class User
72
- # include Bodhi::Validations
73
- #
74
- # attr_accessor :name
75
- # validates :name, required: true
76
- # end
77
- #
78
- # user = User.new
79
- # user.valid? # => false
80
- # user.errors # => #<Bodhi::Errors:0x007fbff403e808 @messages={name:["is required"]}>
81
- def errors
82
- @errors ||= Bodhi::Errors.new
83
- end
84
-
85
- # Runs all class validations on object and adds any errors to the +Bodhi::Errors+ object
86
- #
87
- # class User
88
- # include Bodhi::Validations
89
- #
90
- # attr_accessor :name
91
- # validates :name, required: true
92
- # end
93
- #
94
- # user = User.new
95
- # user.validate! # => nil
96
- # user.errors.full_messages # => ["name is required"]
97
- #
98
- # user.name = "Bob"
99
- # user.validate! # => nil
100
- # user.errors.full_messages # => []
101
- def validate!
102
- errors.clear
103
- self.class.validators.each_pair do |attribute, array|
104
- value = self.send(attribute)
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"