Class: TinkitBaseNode

Inherits:
Object
  • Object
show all
Defined in:
lib/tinkit_base_node.rb

Constant Summary collapse

@@log =

Set Logger

TinkitLog.set("TinkitBaseNode", :warn)

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(init_params = {}) ⇒ TinkitBaseNode

Normal instantiation can take two forms that differ only in the source for the initial parameters. The constructor could be called by the user and passed only user data, or the constructor could be called by a class collection method and the initial parameters would come from a datastore. In the latter case, some of the parameters will include information about the datastore (model metadata).



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/tinkit_base_node.rb', line 345

def initialize(init_params = {})
  #setting the class accessor to also be an instance accessor
  #for convenience and hopefully doesn't create confusion
  @my_GlueEnv = self.class.myGlueEnv
  @@log.debug {"initializing with: #{init_params.inspect}"} if @@log.debug?
  raise "init_params cannot be nil" unless init_params
  @saved_to_model = nil #TODO rename to sychronized_to_model
  #make sure keys are symbols
  init_params = HashKeys.str_to_sym(init_params)
  @_user_data, @_model_metadata = filter_user_from_model_data(init_params)
  
  @@log.debug {"data filtered into user data: #{@_user_data}"} if @@log.debug?
  @@log.debug {"data filtered into model metadata: #{@_model_metadata}"} if @@log.debug?
  
  instance_data_validations(@_user_data)
  node_key = get__user_data_id(@_user_data)
  
  moab_file_mgr = @my_GlueEnv._files_mgr_class.new(@my_GlueEnv, node_key)
  @_files_mgr = FilesMgr.new(moab_file_mgr)
  @_model_metadata = (@_model_metadata, node_key)
  
  @@log.debug {"Updated model metadata: #{@_model_metadata.inspect}"} if @@log.debug?
  
  init_params.each do |attr_name, attr_value|
    __set_userdata_key(attr_name.to_sym, attr_value)
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(meth_sym, *args, &block) ⇒ Object



610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/tinkit_base_node.rb', line 610

def method_missing(meth_sym, *args, &block)
  meth_str = meth_sym.to_s
  raise_method_missing(meth_sym, args) unless @_user_data
  @@log.debug { "User Data (methods generated from these keys): #{@_user_data.inspect}"} if @@log.debug?
  return_value = "method_not_found_here"
  @_user_data.keys.each do |existing_methods_base|
    meth_regex_str ="^#{existing_methods_base}_"
    meth_regex = Regexp.new(meth_regex_str)
    if meth_str.match(meth_regex)
      return_value = @_user_data[existing_methods_base]
      break
    end
  end
  
  if return_value == "method_not_found_here"
    raise_method_missing(meth_sym, *args)
  else
    puts "Warning: Method #{meth_sym.inspect} not defined for all fields\
      returning value of the field in those cases"
    return return_value
  end
end

Class Attribute Details

.data_strucObject

uppercased to highlight its supporting the class



163
164
165
# File 'lib/tinkit_base_node.rb', line 163

def data_struc
  @data_struc
end

.metadata_keysObject

uppercased to highlight its supporting the class



163
164
165
# File 'lib/tinkit_base_node.rb', line 163

def 
  @metadata_keys
end

.myGlueEnvObject

uppercased to highlight its supporting the class



163
164
165
# File 'lib/tinkit_base_node.rb', line 163

def myGlueEnv
  @myGlueEnv
end

Instance Attribute Details

#_files_mgrObject

Instance Accessors



169
170
171
# File 'lib/tinkit_base_node.rb', line 169

def _files_mgr
  @_files_mgr
end

#_model_metadataObject

Instance Accessors



169
170
171
# File 'lib/tinkit_base_node.rb', line 169

def 
  @_model_metadata
end

#_user_dataObject

Instance Accessors



169
170
171
# File 'lib/tinkit_base_node.rb', line 169

def _user_data
  @_user_data
end

#attached_filesObject

Instance Accessors



169
170
171
# File 'lib/tinkit_base_node.rb', line 169

def attached_files
  @attached_files
end

#my_GlueEnvObject

Instance Accessors



169
170
171
# File 'lib/tinkit_base_node.rb', line 169

def my_GlueEnv
  @my_GlueEnv
end

Class Method Details

.__create_from_other_node(other_node) ⇒ Object

Create the document in the BUFS node format from an existing node.



318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/tinkit_base_node.rb', line 318

def self.__create_from_other_node(other_node)
  #TODO:Figure out data structure imports
  #Idea, for duplicates, this node takes precedence
  #for new data structures, other node operations (if they exist) are used
  #Not implemented yet, though
  #TODO: add to spec
  #TODO: what about node id collisions? currently ignoring it
  #and letting the persistence model work it out
  this_node = self.new(other_node._user_data)
  this_node.__save
  this_node.__import_attachments(other_node.__export_attachments) if other_node.attached_files
end

.all(data_structure_changes = {}) ⇒ Object

TODO: Add the very cool feature to spec (creating new fields on the fly) TODO: Document the feature too!!



228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/tinkit_base_node.rb', line 228

def self.all(data_structure_changes = {})
  #add_keys = data_structure_changes[:add]
  #remove_keys = data_structure_changes[:remove]
  #TODO: test for proper format

  raw_nodes = @myGlueEnv.raw_all
  

  raw_nodes.map! do |base_data| 
    combined_data = self.modify_data_structures(base_data, data_structure_changes)
    self.new(combined_data)
  end
  raw_nodes
end

.all_native_recordsObject

Collection Methods This returns all records, but does not create an instance of this class for each record. Each record is provided in its native form.



222
223
224
# File 'lib/tinkit_base_node.rb', line 222

def self.all_native_records
  @myGlueEnv.query_all
end

.attachment_base_idObject

Returns the id that will be appended to the document ID to uniquely identify attachment documents associated with the main document TODO: NOT COMPLETELY ABSTRACTED YET



334
335
336
# File 'lib/tinkit_base_node.rb', line 334

def self.attachment_base_id
  @myGlueEnv.attachment_base_id 
end

.call_new_view(view_name, match_key) ⇒ Object

Not implemented on all persistence layers yet (just couchrest and filesystem)



251
252
253
254
255
256
257
258
259
260
# File 'lib/tinkit_base_node.rb', line 251

def self.call_new_view(view_name, match_key)
  results = if @myGlueEnv.respond_to? :call_view
    @myGlueEnv.call_view(view_name, 
                                   @myGlueEnv.moab_data,
                                   @myGlueEnv.namespace_key,
                                   @myGlueEnv.user_datastore_location,
                                   match_key)
  end
  results
end

.call_view(param, match_keys, data_structure_changes = {}) ⇒ Object

Not implemented on all persistence layers yet (just couchrest and filesystem) may be deprecated



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/tinkit_base_node.rb', line 277

def self.call_view(param, match_keys, data_structure_changes = {})
  view_method_name = "by_#{param}".to_sym #using CouchDB style for now
  records = if @myGlueEnv.views.respond_to? view_method_name
    @myGlueEnv.views.__send__(view_method_name,
                                @myGlueEnv.moab_data,
                                @myGlueEnv.user_datastore_location, 
                                match_keys)
  else
    #TODO: Think of a more elegant way to handle an unknown view
    raise "Unknown design view #{view_method_name} called for: #{param}"
  end
  
  nodes = []
  records.map do |base_data|
    if base_data
      combined_data = self.modify_data_structures(base_data, data_structure_changes)
      nodes << self.new(combined_data)
    end
  end
  return nodes
end

.destroy_allObject

This destroys all nodes in the model this is more efficient than calling destroy on instances of this class as it avoids instantiating only to destroy it



312
313
314
315
# File 'lib/tinkit_base_node.rb', line 312

def self.destroy_all
  all_records = self.all_native_records
  @myGlueEnv.destroy_bulk(all_records)
end

.find_nodes_where(key, relation, this_value) ⇒ Object



262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/tinkit_base_node.rb', line 262

def self.find_nodes_where(key, relation, this_value)
  records = @myGlueEnv.find_nodes_where(key, relation, this_value)
  nodes = []
  records.map do |base_data|
    if base_data
      #combined_data = self.modify_data_structures(base_data, data_structure_changes)
      #nodes << self.new(combined_data)
      nodes << self.new(base_data)
    end
  end
  return nodes
end

.get(id) ⇒ Object



299
300
301
302
303
304
305
306
# File 'lib/tinkit_base_node.rb', line 299

def self.get(id)
  data = @myGlueEnv.get(id)
  rtn = if data
    self.new(data)
  else
    nil
  end
end

.modify_data_structures(base_data, changes) ⇒ Object



243
244
245
246
247
248
# File 'lib/tinkit_base_node.rb', line 243

def self.modify_data_structures(base_data, changes)
  add_keys_values = changes[:add]||{}
  remove_keys = changes[:remove]||[]  #note its an array
  removed_data = base_data.delete_if {|k,v| remove_keys.include?(k)}
  added_data = add_keys_values.merge(removed_data) #so that add doesn't overwrite existing keys
end

.set_environment(persist_env, data_model_bindings) ⇒ Object

Class Methods Setting up the Class Environment - The class environment holds all model-specific implementation details (not used when created by factory?)



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/tinkit_base_node.rb', line 188

def self.set_environment(persist_env, data_model_bindings)
  class_name = persist_env[:name]
  model_name = class_name
  model_env = persist_env[:env]
  #key_fields = data_model_bindings[:key_fields]
  #initial_views_data = data_model_bindings[:data_ops_set]
  
  #dynamically determine what's needed
  glue_file_name = "#{model_name}_glue_env"
  #moab_file_name = "moab_#{model_name}_env"
  
  #dynamic require (maybe just keep this static?)
  require Tinkit.glue glue_file_name
  #require Tinkit.moabs moab_file_name
  
  glue_lc_name = "#{model_name}_env"
  glue_const_name = Camel.ize(glue_lc_name)
  glueModule = Object.const_get(glue_const_name)
  glueClass = glueModule::GlueEnv
  
  #orig
  #@myGlueEnv = glueClass.new(persist_env, data_model_bindings)
  #/orig
  #new
  persistent_model_glue_obj = glueClass.new(persist_env, data_model_bindings)
  @myGlueEnv = persistent_model_glue_obj #GlueEnv.new(persistent_model_glue_obj)

  @metadata_keys = @myGlueEnv. 
end

Instance Method Details

#__destroy_nodeObject

Deletes the object



481
482
483
# File 'lib/tinkit_base_node.rb', line 481

def __destroy_node
  @my_GlueEnv.destroy_node(self.)
end

#__export_attachment(attachment_name) ⇒ Object



465
466
467
468
469
# File 'lib/tinkit_base_node.rb', line 465

def __export_attachment(attachment_name)
  md = (attachment_name)
  data = get_raw_data(attachment_name)
  export = {:metadata => md, :raw_data => data}
end

#__get_attachment_metadata(attachment_name) ⇒ Object



513
514
515
516
517
# File 'lib/tinkit_base_node.rb', line 513

def (attachment_name)
  all_md = 
  index_name = TkEscape.escape(attachment_name)
  all_md[index_name.to_sym]
end

#__get_attachments_metadataObject



504
505
506
507
508
509
510
511
# File 'lib/tinkit_base_node.rb', line 504

def 
  md = @_files_mgr.(self)
  md = HashKeys.str_to_sym(md)
  md.each do |fbn, fmd|
    md[fbn] = HashKeys.str_to_sym(fmd)
  end
  md
end

#__import_attachment(attach_name, att_xfer_format) ⇒ Object



471
472
473
474
475
476
477
478
# File 'lib/tinkit_base_node.rb', line 471

def __import_attachment(attach_name, att_xfer_format)
  #transfer format is the format of the export method
  content_type = att_xfer_format[:metadata][:content_type]
  file_modified_at = att_xfer_format[:metadata][:file_modified]
  raw_data = att_xfer_format[:raw_data]
  #raise "Attachment provided no data to import" unless raw_data
  add_raw_data(attach_name, content_type, raw_data, file_modified_at)
end

#__method_wrapper(param, unbound_op) ⇒ Object

TODO: Method Wrapper is not sufficiently tested The method operations are completely decoupled from the object that they are bound to. This creates a problem when operations act on themselves (for example adding x to the current value requires the adder to determine the current value of x). To get around this self-referential problem while maintaining the decoupling this wrapper is used. Essentially it takes the unbound two parameter (this, other) and binds the current value to (this). This allows a more natural form of calling these operations. In other words description_add(new_string) can be used, rather than description_add(current_string, new_string).



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
# File 'lib/tinkit_base_node.rb', line 411

def __method_wrapper(param, unbound_op)
  @@log.debug {"__method_wrapper with #{param.inspect}, #{unbound_op.inspect}"} if @@log.debug?
  #What I want is to call obj.param_op(other)   example: obj.links_add(new_link)
  #which would then add new_link to obj.links
  #however, the predefined operation (add in the example) has no way of knowing
  #about links, so the predefined operation takes two parameters (this, other)
  #and this method wraps the obj.links so that the links_add method doesn't have to
  #include itself as a paramter to the predefined operation
  #lambda {|other| @node_data_hash[param] = unbound_op.call(@node_data_hash[param], other)}
  lambda {|other| old_this = self.__send__("#{param}".to_sym) #original value
                  #we're going to compare the new value to the old later
                  if old_this
                    this = old_this.dup 
                  else
                    this = old_this
                  end
                  rtn_data = unbound_op.call(this, other)
                  new_this = rtn_data[:update_this]
                  self.__send__("#{param}=".to_sym, new_this)
                  it_changed = true
                  it_changed = false if (old_this == new_this) || !(rtn_data.has_key?(:update_this))
                  not_in_model = !@saved_to_model
                  self.__save if (not_in_model || it_changed)#unless (@saved_to_model && save) #don't save if the value hasn't changed
                  rtn = rtn_data[:return_value] || rtn_data[:update_this]
                  rtn
         }
end

#__saveObject

Save the object to the CouchDB database



448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
# File 'lib/tinkit_base_node.rb', line 448

def __save
  save_data_validations(self._user_data)
  node_key = @my_GlueEnv.node_key
  node_id = self.[node_key]
  @@log.debug {"User Data to save: #{self._user_data}"} if @@log.debug?
  model_data = 
  #raise model_data.inspect
  @@log.debug { "saving (including injected model data): #{model_data.inspect}"} if @@log.debug?
  res = @my_GlueEnv.save(model_data)
  version_key = @my_GlueEnv.version_key
  #TODO: Make consistent with rev keys
  rev_data = {version_key => res['rev']}
  update_self(rev_data)
  return self
end

#__set_userdata_key(attr_var, attr_value) ⇒ Object

This will take a key-value pair and create an instance variable (actually it’s a method)using key as the method name, and sets the return value to the value associated with that key changes to the key’s value are reflected in subsequent method calls, and the value can be updated by using method_name = some value. Additionally, any custom operations that have been defined for that key name will be loaded in and assigned methods in the form methodname_operation



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/tinkit_base_node.rb', line 380

def __set_userdata_key(attr_var, attr_value)
  ops = self.class.data_struc.field_op_defs #data_ops #|| NodeElementOperations.ops
  #@@log.debug {"Ops Def: #{ops.inspect}"} if @@log.debug?
  #ops = NodeElementOperations::Ops
  #incorporates predefined methods
  #@@log.debug {"Setting method #{attr_var.inspect}, #{ops[attr_var].inspect}"} if @@log.debug?
  add_op_method(attr_var, ops[attr_var]) if (ops && ops[attr_var])
  unless self.class..include? attr_var.to_sym
    @_user_data[attr_var] = attr_value
  else
    raise "Metadata Keys: #{self.class..inspect} 
     Key match: #{attr_var.to_sym.inspect} UserData: #{@_user_data.inspect}"
  end
  #manually setting instance variable (rather than using instance_variable_set),
  # so @node_data_hash can be updated
  #dynamic method acting like an instance variable getter
  self.class.__send__(:define_method, "#{attr_var}".to_sym,
                              lambda {@_user_data[attr_var]} )
  #dynamic method acting like an instance variable setter
  self.class.__send__(:define_method, "#{attr_var}=".to_sym,
                              lambda {|new_val| @_user_data[attr_var] = new_val} )
end

#__unset_userdata_key(param) ⇒ Object



439
440
441
442
# File 'lib/tinkit_base_node.rb', line 439

def __unset_userdata_key(param)
  self.class.__send__(:remove_method, param.to_sym)
  @_user_data.delete(param)
end

#add_parent_categories(new_cats) ⇒ Object

Deprecated Methods———————— Adds parent categories, it can accept a single category or an array of categories aliased for backwards compatibility, this method is dynamically defined and generated



524
525
526
527
# File 'lib/tinkit_base_node.rb', line 524

def add_parent_categories(new_cats)
  raise "Warning:: add_parent_categories is being deprecated, use <param_name>_add instead ex: parent_categories_add(cats_to_add) "
  parent_categories_add(new_cats)
end

#add_raw_data(attach_name, content_type, raw_data, file_modified_at = nil) ⇒ Object

Get attachment content. Note that the data is read in as a complete block, this may be something that needs optimized. TODO: add_raw_data parameters to a hash?



541
542
543
544
545
546
547
548
549
550
551
# File 'lib/tinkit_base_node.rb', line 541

def add_raw_data(attach_name, content_type, raw_data, file_modified_at = nil)
  attached_basenames = @_files_mgr.add_raw_data(self, attach_name, content_type, raw_data, file_modified_at = nil)
  if self.attached_files
    self.attached_files += attached_basenames
    self.attached_files.uniq!  #removing duplicates is ok because these names are keys to the underlying attached file data (dupes would point to the same data)
  else
    self.__set_userdata_key(:attached_files, attached_basenames)
  end

  self.__save
end

#files_add(file_datas) ⇒ Object



553
554
555
556
557
558
559
560
561
562
563
564
565
# File 'lib/tinkit_base_node.rb', line 553

def files_add(file_datas)
  file_datas = [file_datas].flatten
  #TODO keep original names, and have model abstract character issues
  #TODO escaping is spread all over, do it in one place
  attached_basenames = @_files_mgr.add_files(self, file_datas)
  if self.attached_files
    self.attached_files += attached_basenames
    self.attached_files.uniq!  #removing duplicates is ok because these names are keys to the underlying attached file data (dupes would point to the same data)
  else
    self.__set_userdata_key(:attached_files, attached_basenames)
  end
  self.__save
end

#files_remove_allObject



574
575
576
577
578
# File 'lib/tinkit_base_node.rb', line 574

def files_remove_all
  @_files_mgr.subtract_files(self, :all)
  self.attached_files = nil
  self.__save
end

#files_subtract(file_basenames) ⇒ Object



567
568
569
570
571
572
# File 'lib/tinkit_base_node.rb', line 567

def files_subtract(file_basenames)
  file_basenames = [file_basenames].flatten
  @_files_mgr.subtract_files(self, file_basenames)
  self.attached_files -= file_basenames
  self.__save
end

#get_file_data(attachment_name) ⇒ Object

def attachment_url(attachment_name)

current_node_doc = self.class.get(self['_id'])
att_doc_id = current_node_doc['attachment_doc_id']
current_node_attachment_doc = self.class.user_attachClass.get(att_doc_id)
current_node_attachment_doc.attachment_url(attachment_name)

end



593
594
595
596
597
598
599
# File 'lib/tinkit_base_node.rb', line 593

def get_file_data(attachment_name)
  @_files_mgr.get_file_data(self, attachment_name)
  #current_node_doc = self.class.get(self['_id'])
  #att_doc_id = current_node_doc['attachment_doc_id']
  #current_node_attachment_doc = self.class.user_attachClass.get(att_doc_id)
  #current_node_attachment_doc.read_attachment(attachment_name)
end

#get_raw_data(attachment_name) ⇒ Object



580
581
582
# File 'lib/tinkit_base_node.rb', line 580

def get_raw_data(attachment_name)
  @_files_mgr.get_raw_data(self, attachment_name)
end

#raise_method_missing(meth_sym, *args) ⇒ Object

Raises:

  • (NoMethodError)


601
602
603
604
605
606
607
608
# File 'lib/tinkit_base_node.rb', line 601

def raise_method_missing(meth_sym, *args)
  raise NoMethodError, <<-ERRORINFO
      base class: TinkitBaseNode
      actual class: #{self.class}
      method: #{meth_sym.inspect}
      args: #{args.inspect}
    ERRORINFO
end

#remove_parent_categories(cats_to_remove) ⇒ Object

Can accept a single category or an array of categories aliased for backwards compatiblity the method is dynamically defined and generated



531
532
533
534
# File 'lib/tinkit_base_node.rb', line 531

def remove_parent_categories(cats_to_remove)
  raise "Warning:: remove_parent_categories is being deprecated, use <param_name>_subtract instead ex: parent_categories_subtract(cats_to_remove)"
  parent_categories_subtract(cats_to_remove)
end