Class: Pod4::NebulousInterface

Inherits:
Interface show all
Defined in:
lib/pod4/nebulous_interface.rb

Overview

An interface to talk to a Nebulous Target.

Each interface can only speak with one target, designated with #set_target.# The developer must also set a unique ID key using #set_id_fld.

The primary challenge here is to map the CRUDL methods (which interfaces contract to implement) to nebulous verbs. The programmer uses #set_verb for this purpose: the first parameter indicates the CRUDL method, the next is the verb name, and the rest are hash keys.

In the case of the #create and #update methods, the list of keys controls which parts of the incoming hash end up in the verb parameters, and in what order. For #update, the list must include the ID key that you gave to #set_id_fld.

Parameters for the #list method similarly constrain how its selection parameter is translated to a nebulous verb parameter string.

Parameters for #read and #delete can be whatever you like, but since the only value passed to read is the ID, the only symbol there should be the same as the one in #set_id_fld.

class CustomerInterface < SwingShift::NebulousInterface
  set_target 'accord'
  set_id_fld :id
  set_verb :read,   'customerread',   :id, '100'
  set_verb :list,   'customerlist',   :name
  set_verb :create, 'customerupdate', 'create', :name, :price
  set_verb :update, 'customerupdate', 'update', :name, :id, :price

  def update(id, name, price)
    super( id, name: name, price: price)
  end
end

In this example both the create and update methods point to the same nebulous verb. Note that only keys which are symbols are translated to the corresponding values in the record or selection hash; anything else is passed literally in the Nebulous parameter string.

When you subclass NebulousInterfce, you may want to override some or all of the CRUDL methods so that your callers can pass specific parameters rather than a hash; the above example demonstrates this.

We assume that the response to the #create message returns the ID as the parameter part of the success verb. If that’s not true, then you will have to override #create and sort this out yourself.

Note that all values are returned as strings; there is no typecasting. This is a given limitation for Nebulous as a whole.

Calls to create, update and delete avoid (for obvious reasons) Nebulous’ Redis cache if present. read and list use it. The interface methods take an extra options hash to control this, which, of course, Pod4::Model does not know about. If you want to enable a non-cached read in your model, it will need a method something like this:

def read_no_cache
  r = interface.read(@model_id, caching: false)

  if r.empty?
    add_alert(:error, "Record ID '#@model_id' not found on the data source")
  else
    map_to_model(r)
    run_validation(:read)
    @model_status = :okay if @model_status == :empty
end

Defined Under Namespace

Classes: Verb

Constant Summary

Constants inherited from Interface

Interface::ACTIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Metaxing

#define_class_method, #metaclass

Constructor Details

#initialize(requestObj = nil) ⇒ NebulousInterface

In normal operation, takes no parameters.

For testing purposes you may pass something here. Whatever it is you pass, it must respond to a ‘send` method, take the same parameters as NebulousStomp::Request.new (that is, a target and a message) and return something that behaves like a NebulousStomp::Request. This method will be called instead of creating a NebulousStomp::Request directly.



177
178
179
180
181
182
183
184
# File 'lib/pod4/nebulous_interface.rb', line 177

def initialize(requestObj=nil)
  @request_object  = requestObj # might as well be a reference 
  @response        = nil
  @response_status = nil
  @id_fld          = self.class.id_fld

  self.class.validate_params
end

Instance Attribute Details

#id_fldObject (readonly)

Returns the value of attribute id_fld.



74
75
76
# File 'lib/pod4/nebulous_interface.rb', line 74

def id_fld
  @id_fld
end

#responseObject (readonly)

The NebulousStomp Message object holding the response from the last message sent, or, nil.



77
78
79
# File 'lib/pod4/nebulous_interface.rb', line 77

def response
  @response
end

#response_statusObject (readonly)

The status of the response from the last message:

  • nil - we didn’t send a request yet

  • :off - Nebulous is turned off, so nothing happened

  • :timeout we sent the message but timed out waiting for a response

  • :verberror - we got an error verb in response

  • :verbsuccess - we got a success verb in response

  • :response - we got some response that doesn’t follow The Protocol

NB: if we got an exception sending the message, we raised it on the caller, so there is no status for that.



89
90
91
# File 'lib/pod4/nebulous_interface.rb', line 89

def response_status
  @response_status
end

Class Method Details

.id_fldObject

Raises:



139
140
141
# File 'lib/pod4/nebulous_interface.rb', line 139

def id_fld
  raise Pod4Error, "You need to use set_id_fld"
end

.set_id_fld(idFld) ⇒ Object

Set the name of the ID parameter (needs to be in the CRUD verbs param list)



135
136
137
# File 'lib/pod4/nebulous_interface.rb', line 135

def set_id_fld(idFld)
  define_class_method(:id_fld) {idFld}
end

.set_target(target) ⇒ Object

Set the name of the Nebulous target in the interface definition

a reference to the interface object.



124
125
126
# File 'lib/pod4/nebulous_interface.rb', line 124

def set_target(target)
  define_class_method(:target) {target.to_s}
end

.set_verb(action, verb, *paramKeys) ⇒ Object

Set a verb.

  • action - must be one of CRUDL

  • verb - the name of the verb

  • parameters - array of symbols to order the hash passed to create, etc

Raises:

  • (ArgumentError)


107
108
109
110
111
112
113
114
# File 'lib/pod4/nebulous_interface.rb', line 107

def set_verb(action, verb, *paramKeys)
  raise ArgumentError, "Bad action" unless Interface::ACTIONS.include? action

  v = verbs.dup
  v[action] = Verb.new( verb, paramKeys.flatten )

  define_class_method(:verbs) {v}
end

.targetObject

Raises:



128
129
130
# File 'lib/pod4/nebulous_interface.rb', line 128

def target
  raise Pod4Error, "You need to use set_target on your interface"
end

.validate_paramsObject

Make sure all of the above is consistent

Raises:



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/pod4/nebulous_interface.rb', line 147

def validate_params
  raise Pod4Error, "You need to use set_verb" if verbs == {}

  i|create read update delete|.each do |action|
    raise Pod4Error, "set_verb #{action} is missing a parameter list" \
      if verbs[action] && !verbs[action].params == []

  end

  i|read update delete|.each do |action|
    raise Pod4Error, "#{action} verb doesn't have an #{id_fld} key" \
      if verbs[action] && !verbs[action].params.include?(id_fld)

  end

end

.verbsObject



116
# File 'lib/pod4/nebulous_interface.rb', line 116

def verbs; {}; end

Instance Method Details

#clearing_cacheObject

Bonus method: chain this method before a CRUDL method to clear the cache for that parameter string:

@interface.clearing_cache.read(14)

Note that there is no guarantee that the request that clears the cache is actually the one you chain after (if multiple model instances are running against the same interface instance) but for the instance that calls ‘clearing_cache`, this is not important.



295
296
297
298
# File 'lib/pod4/nebulous_interface.rb', line 295

def clearing_cache
  @clear_cache = true
  self
end

#create(record) ⇒ Object

Pass a parameter string or an array as the record. returns the ID. We assume that the response to the create message returns the ID as the parameter part of the success verb. If that’s not true, then you will have to override #create and sort this out yourself.



221
222
223
224
225
226
227
228
229
# File 'lib/pod4/nebulous_interface.rb', line 221

def create(record)
  raise ArgumentError, 'create takes a Hash or an Octothorpe' unless hashy?(record)

  send_message( verb_for(:create), param_string(:create, record), false )
  @response.params

rescue => e
  handle_error(e)
end

#delete(id) ⇒ Object

Given an ID, delete the record. Return self.

The actual parameters passed to nebulous depend on how you #set_verb

Raises:

  • (ArgumentError)


274
275
276
277
278
279
280
281
282
# File 'lib/pod4/nebulous_interface.rb', line 274

def delete(id)
  raise ArgumentError, 'You must pass an ID to delete' unless id

  send_message( verb_for(:delete), 
                param_string(:delete, nil, id), 
                false )

  self
end

#list(selection = nil, opts = {}) ⇒ Object

Pass a parameter string or array (which will be taken as the literal Nebulous parameter) or a Hash or Octothorpe (which will be interpreted as per your list of keys set in add_verb :list).

Returns an array of Octothorpes, or an empty array if the responder could not make any records out of our message.

Note that the ‘opts` hash is not part of the protocol supported by Pod4::Model. If you want to make use of it, you will have to write your own method for that. Supported keys:

  • caching: true if you want to use redis caching (defaults to true)



200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/pod4/nebulous_interface.rb', line 200

def list(selection=nil, opts={})
  sel = 
    case selection
      when Array, Hash, Octothorpe then param_string(:list, selection)
      else selection
    end

  caching = opts[:caching].nil? ? true : !!opts[:caching]
  send_message( verb_for(:list), sel, caching )
  @response.body.is_a?(Array) ? @response.body.map{|e| Octothorpe.new e} : []

rescue => e
  handle_error(e)
end

#read(id, opts = {}) ⇒ Object

Given the id, return an Octothorpe of the record.

The actual parameters passed to nebulous depend on how you #set_verb

Note that the ‘opts` hash is not part of the protocol supported by Pod4::Model. If you want to make use of it, you will have to write your own method for that. Supported keys:

  • caching: true if you want to use redis caching (defaults to true)

Raises:

  • (ArgumentError)


242
243
244
245
246
247
248
249
250
251
# File 'lib/pod4/nebulous_interface.rb', line 242

def read(id, opts={})
  raise ArgumentError, 'You must pass an ID to read' unless id

  caching = opts[:caching].nil? ? true : !!opts[:caching]
  send_message( verb_for(:read), 
                param_string(:read, nil, id), 
                caching )

  Octothorpe.new( @response.body.is_a?(Hash) ? @response.body : {} )
end

#send_message(verb, paramStr, with_cache = true) ⇒ Object

Bonus method: send an arbitrary Nebulous message to the target and return the response object.

We don’t trap errors here - see #handle_error - but we raise extra ones if we think things look fishy.



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
# File 'lib/pod4/nebulous_interface.rb', line 307

def send_message(verb, paramStr, with_cache=true)
  unless NebulousStomp.on? 
    @response_status = :off
    raise Pod4::DatabaseError, "Nebulous is turned off!"
  end

  Pod4.logger.debug(__FILE__) do
    "Sending v:#{verb} p:#{paramStr} c?: #{with_cache}"
  end

  @response = send_message_helper(verb, paramStr, with_cache)

  raise Pod4::DatabaseError, "Null response" if @response.nil?

  @response_status = 
    case @response.verb
      when 'error'   then :verberror
      when 'success' then :verbsuccess
      else                :response
    end

  raise Pod4::WeakError, "Nebulous returned an error verb: #{@response.description}" \
    if @response_status == :verberror

  self

rescue => err
  handle_error(err)
end

#update(id, record) ⇒ Object

Given an id an a record (Octothorpe or Hash), update the record. Returns self.

Raises:

  • (ArgumentError)


257
258
259
260
261
262
263
264
265
266
# File 'lib/pod4/nebulous_interface.rb', line 257

def update(id, record)
  raise ArgumentError, 'You must pass an ID to update' unless id
  raise ArgumentError, 'update record takes a Hash or an Octothorpe' unless hashy?(record)

  send_message( verb_for(:update), 
                param_string(:update, record, id), 
                false )

  self
end