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).



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

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



631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
# File 'lib/tinkit_base_node.rb', line 631

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



166
167
168
# File 'lib/tinkit_base_node.rb', line 166

def data_struc
  @data_struc
end

.metadata_keysObject

uppercased to highlight its supporting the class



166
167
168
# File 'lib/tinkit_base_node.rb', line 166

def 
  @metadata_keys
end

.myGlueEnvObject

uppercased to highlight its supporting the class



166
167
168
# File 'lib/tinkit_base_node.rb', line 166

def myGlueEnv
  @myGlueEnv
end

Instance Attribute Details

#_files_mgrObject

Instance Accessors



172
173
174
# File 'lib/tinkit_base_node.rb', line 172

def _files_mgr
  @_files_mgr
end

#_model_metadataObject

Instance Accessors



172
173
174
# File 'lib/tinkit_base_node.rb', line 172

def 
  @_model_metadata
end

#_user_dataObject

Instance Accessors



172
173
174
# File 'lib/tinkit_base_node.rb', line 172

def _user_data
  @_user_data
end

#attached_filesObject

Instance Accessors



172
173
174
# File 'lib/tinkit_base_node.rb', line 172

def attached_files
  @attached_files
end

#my_GlueEnvObject

Instance Accessors



172
173
174
# File 'lib/tinkit_base_node.rb', line 172

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.



321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/tinkit_base_node.rb', line 321

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!!



231
232
233
234
235
236
237
238
239
240
241
242
243
244
# File 'lib/tinkit_base_node.rb', line 231

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.



225
226
227
# File 'lib/tinkit_base_node.rb', line 225

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



337
338
339
# File 'lib/tinkit_base_node.rb', line 337

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)



254
255
256
257
258
259
260
261
262
263
# File 'lib/tinkit_base_node.rb', line 254

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



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

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



315
316
317
318
# File 'lib/tinkit_base_node.rb', line 315

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

.find_nodes_where(key, relation, this_value) ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/tinkit_base_node.rb', line 265

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



302
303
304
305
306
307
308
309
# File 'lib/tinkit_base_node.rb', line 302

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



246
247
248
249
250
251
# File 'lib/tinkit_base_node.rb', line 246

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?)



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
217
218
219
# File 'lib/tinkit_base_node.rb', line 191

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

#[](field) ⇒ Object

these add hash like syntax to node setters and getters get



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

def [](field)
  self.__send__(field.to_sym)
end

#[]=(field, data) ⇒ Object

set



536
537
538
539
540
# File 'lib/tinkit_base_node.rb', line 536

def []=(field, data)
  assignment_field = "_add" #for Node Element Operations
  assignment_method = "#{field}#{assignment_field}".to_sym
  self.__send__(assignment_method, data)
end

#__destroy_nodeObject

Deletes the object



484
485
486
# File 'lib/tinkit_base_node.rb', line 484

def __destroy_node
  @my_GlueEnv.destroy_node(self.)
end

#__export_attachment(attachment_name) ⇒ Object



468
469
470
471
472
# File 'lib/tinkit_base_node.rb', line 468

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



516
517
518
519
520
# File 'lib/tinkit_base_node.rb', line 516

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

#__get_attachments_metadataObject



507
508
509
510
511
512
513
514
# File 'lib/tinkit_base_node.rb', line 507

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



474
475
476
477
478
479
480
481
# File 'lib/tinkit_base_node.rb', line 474

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).



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

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



451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
# File 'lib/tinkit_base_node.rb', line 451

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



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

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

#__to_xml(root_name = nil) ⇒ Object

TODO: Add to specs outputs the node data as an xml snippet



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

def __to_xml(root_name = nil)
  root_name = root_name || 'tinkit_node'
  XmlSimple.xml_out(@_user_data, 'AttrPrefix' => true, 'RootName' => root_name)
end

#__unset_userdata_key(param) ⇒ Object



442
443
444
445
# File 'lib/tinkit_base_node.rb', line 442

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



544
545
546
547
# File 'lib/tinkit_base_node.rb', line 544

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?



561
562
563
564
565
566
567
568
569
570
571
# File 'lib/tinkit_base_node.rb', line 561

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



573
574
575
576
577
578
579
580
581
582
583
584
585
# File 'lib/tinkit_base_node.rb', line 573

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



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

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

#files_subtract(file_basenames) ⇒ Object



587
588
589
590
591
592
# File 'lib/tinkit_base_node.rb', line 587

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



613
614
615
616
617
618
619
# File 'lib/tinkit_base_node.rb', line 613

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



600
601
602
# File 'lib/tinkit_base_node.rb', line 600

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

#raise_method_missing(meth_sym, *args) ⇒ Object

Raises:

  • (NoMethodError)


621
622
623
624
625
626
627
628
629
# File 'lib/tinkit_base_node.rb', line 621

def raise_method_missing(meth_sym, *args)
  raise NoMethodError, <<-ERRORINFO
      base class: TinkitBaseNode
      actual class: #{self.class}
      method: #{meth_sym.inspect}
      args: #{args.inspect}
      user_data: #{@_user_data.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



551
552
553
554
# File 'lib/tinkit_base_node.rb', line 551

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