class CouchrestEnv::GlueEnv

EnvName = :couchrest_env name for couchrest environments

Constants

AttachClassBaseName
CouchMetadataKeys
DesignDocBaseName
PersistLayerKey

NamespaceKey = :bufs_namespace

QueryAllStr
VersionKey

used to identify metadata for models (should be consistent across models) ModelKey = :_id

Attributes

_files_mgr_class[RW]
attachClass[RW]
attachment_base_id[RW]
db[RW]
design_doc[RW]
metadata_keys[RW]
moab_data[RW]
model_key[RW]
model_save_params[RW]
namespace_key[RW]
node_key[RW]
persist_layer_key[RW]
query_all[RW]
required_instance_keys[RW]
required_save_keys[RW]
user_datastore_location[RW]
user_id[RW]
version_key[RW]
views[RW]

Public Class Methods

new(persist_env, data_model_bindings) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 194
def initialize(persist_env, data_model_bindings)
  couchrest_env = persist_env[:env]
  couch_db_host = couchrest_env[:host]
  db_name_path = couchrest_env[:path]
  @user_id = couchrest_env[:user_id]
  @model_name = persist_env[:name]
  
  #data_model_bindings from NodeElementOperations
  key_fields = data_model_bindings[:key_fields] 
  initial_views_data = data_model_bindings[:views] || []
  
  #FIXME: Major BUG!! when setting multiple environments in that this may cross-contaminate across users
  #if those users share the same db.  Testing up to date has been users on different dbs, so not an issue to date
  #also, one solution might be to force users to their own db? (what about sharing though?)
  #The problem is that there is one "query_all" per database, and it gets set to the last user class
  #that sets it.  [Is this still a bug? 12/16/10]
  #@user_id = db_user_id
  #user_attach_class_name = "UserAttach#{db_user_id}"
  #the rescue is so that testing works
  #begin
  #  attachClass = UserNode.const_get(user_attach_class_name)
  #rescue NameError
  #  puts "Warning:: Multiuser support for attachments not enabled. Using generic Attachment Class"
  #  attachClass = CouchrestAttachment
  #end
  couch_db_location = set_db_location(couch_db_host, db_name_path)
  @db = CouchRest.database!(couch_db_location)
  @model_save_params = {:db => @db}
  @persist_layer_key = PersistLayerKey
  
  #@collection_namespace = CouchrestEnv.set_collection_namespace(db_name_path, @user_id)
  #@user_datastore_location = CouchRestEnv.set_user_datastore_location(@db, @user_id)
  @user_datastore_location = set_namespace(db_name_path, @user_id)
  @design_doc = set_couch_design(@db, @user_id)#, @collection_namespace)
  @node_key = key_fields[:primary_key]     
  #
  @define_query_all = QueryAllStr #CouchRestEnv.query_for_all_collection_records
  
  @required_instance_keys = key_fields[:required_keys] #DataStructureModels::RequiredInstanceKeys
  @required_save_keys = key_fields[:required_keys] #DataStructureModels::Tinkit::RequiredSaveKeys
  @model_key = @node_key #ModelKey #CouchRestEnv::ModelKey
  @version_key = VersionKey #CouchRestEnv::VersionKey
  @namespace_key = @model_name #NamespaceKey #CouchRestEnv::NamespaceKey
  #TODO: Need to investigate whether to keep model_key = node_key in metadata
  @metadata_keys = [@persist_layer_key, @version_key, @namespace_key] + CouchMetadataKeys #CouchRestEnv.set_db_metadata_keys #(@collection_namespace)
 
  @views = CouchRestViews
  @views.set_view_all(@db, @design_doc, @model_name, @user_datastore_location)
  
  @views.set_my_cat_view(@db, @design_doc, @user_datastore_location)
  
  
  
  #set new view
  initial_views_data.each do |view_name, view_data|
    set_view_value_match(@db, @design_doc, @namespace_key, @user_datastore_location, view_data[:field])
  end
  
  #@views.set_new_views(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
  
  attach_class_name = "#{AttachClassBaseName}#{@user_id}"
  @attachClass = set_attach_class(@db.root, attach_class_name) 
  @moab_data = {:db => @db, 
                          :design_doc => @design_doc, 
                          :attachClass => @attachClass}
  #TODO: Have to do the above, but want to do the below
  #@attachClass = set_attach_class(@db.root, attach_class_name)
  @_files_mgr_class = CouchrestInterface::FilesMgr
end

Public Instance Methods

db_destroy(model_metadata) click to toggle source

TODO: update glue models so that it's id and rev that are explicitly needed which begs the question whether rev is supported or not

# File lib/glue_envs/couchrest_glue_env.rb, line 462
def db_destroy(model_metadata)
  id_to_delete = model_metadata[@persist_layer_key] ||  generate_pk_data(model_metadata[@node_key])
  @@log.info { "Destroy in DB, Key: #{id_to_delete.inspect} in #{model_metadata.inspect}" } if @@log.info?
  
  rev_to_delete = model_metadata[@version_key] || @db.get(id_to_delete)['_rev']  
  doc_to_delete = {'_id' =>  id_to_delete, '_rev' => rev_to_delete }
 
  @@log.debug { "Attempting to Delete: #{doc_to_delete.inspect}" } if @@log.debug?
  begin
    @db.delete_doc(doc_to_delete)
  rescue ArgumentError => e
    puts "Rescued Error: #{e} while trying to destroy #{model_metadata[@model_key]} node"
    #Code here was deleting the latest version, but rather than wait for an error, it was proactively checked
    #so this block may not be needed any more
    #node = node.class.get(model_metadata[@model_key]) #(model_metadata['_id'])
    #db_destroy(model_metadata)
  end
end
destroy_bulk(user_records_to_delete) click to toggle source

TODO: Investigate if Couchrest bulk actions or design views will assist here fixed to delete orphaned attachments, but this negates much of the advantage of using this method in the first place or perhaps using a close to the metal design view based on the class name?? (this may be better)

# File lib/glue_envs/couchrest_glue_env.rb, line 511
def destroy_bulk(user_records_to_delete)
  #TODO: Investigate why mutiple ids may be returned for the same record
  #Answer Database Corruption
  user_records_to_delete.uniq!
  #puts "List of all records: #{user_records_to_delete.map{|r| r['_id']}.inspect}"

  user_records_to_delete.each do |user_rec|
    begin
      db_key = user_rec[@persist_layer_key] || generate_pk_data(user_rec[@model_key])
      att_doc_id = attachClass.uniq_att_doc_id(db_key)
      r = @db.get(db_key )
      @db.delete_doc(r)
      begin
        att_doc = @db.get(att_doc_id)
      rescue
        att_doc = nil
      end
      @db.delete_doc(att_doc) if att_doc
    rescue RestClient::RequestFailed
      @@log.warn{ "Warning:: Failed to delete document?" } if @@log.warn?
    end
  end
  nil #TODO ok to return nil if all docs destroyed? also, not verifying
end
destroy_node(model_metadata) click to toggle source

Not tested in factory tests (but is in couchrest tests)

# File lib/glue_envs/couchrest_glue_env.rb, line 450
def destroy_node(model_metadata)
  #att_doc = node.my_GlueEnv.attachClass.get(node.attachment_doc_id) if node.respond_to?(:attachment_doc_id)
  attachClass = @moab_data[:attachClass]
  att_doc_id = attachClass.uniq_att_doc_id(model_metadata[@persist_layer_key])
  att_doc = attachClass.get(att_doc_id) if att_doc_id
  #raise "Destroying Attachment #{att_doc.inspect} derived from #{@model_metadata.inspect} "
  att_doc.destroy if att_doc
  db_destroy(model_metadata)
end
find_contains(key, this_value) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 363
def find_contains(key, this_value)
  #TODO: Make a view for this rather than doing it in ruby
  results =[]
  query_all.each do |record|
    test_val = record[key]
    results << record  if find_contains_type_helper(test_val, this_value)
  end
  @@log.debug {"Found contains results: #{results.inspect}"} if @@log.debug?
  results 
end
find_contains_type_helper(stored_data, this_value) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 374
def find_contains_type_helper(stored_data, this_value)
  resp = nil
  #stored_data = jparse(stored_dataj)
  if stored_data.respond_to?(:"include?")
    resp = (stored_data.include?(this_value))
  else
    resp = (stored_data == this_value)
  end
  return resp
end
find_equals(key, this_value) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 353
def find_equals(key, this_value)
  results =[]
  query_all.each do |record|
    test_val = record[key]
    results << record  if test_val == this_value
  end
  @@log.debug {"Found equals results: #{results.inspect}"} if @@log.debug?
  results 
end
find_nodes_where(key, relation, this_value) click to toggle source

current relations supported:

- :equals (data in the key field matches this_value)
- :contains (this_value is contained in the key field data (same as equals for non-enumerable types )
# File lib/glue_envs/couchrest_glue_env.rb, line 339
def find_nodes_where(key, relation, this_value)
  
  res = case relation
    when :equals
      find_equals(key, this_value)
    when :contains
      find_contains(key, this_value)
    else
      raise "Couldn't determine relationship between stored data and lookup data"
  end #case
  @@log.info {"Found #{res.size} nodes where #{key} #{relation} #{this_value}"} if @@log.info?
  return res    
end
generate_model_key(namespace, node_key_value) click to toggle source

I hope this can be replaced by the generate_pk_data, but need to make sure FIXME: Actually it has to be

# File lib/glue_envs/couchrest_glue_env.rb, line 489
def generate_model_key(namespace, node_key_value)
  #TODO: Make sure namespace is portable across model migrations (must be diff database)
  #"#{namespace}::#{node_key}"   # <== original if the below ends up breaking stuff
  #"#{node_key}"
  generate_pk_data(node_key_value)
end
generate_pk_data(record_id) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 496
def generate_pk_data(record_id)
  #url_friendly_class_name = self.class.name.gsub('::','-')
  "#{user_id}:#{self.class.name}:#{record_id}"
end
get(id) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 423
def get(id)
  #id can be the model id or the persist layer id
  pk_data = if id.include? self.class.name
    id
  else
    generate_pk_data(id)
  end
  #maybe put in some validations to ensure its from the proper collection namespace?
  #pk_data = id #generate_pk_data(id)
  #Major TODO: Deconflict module CouchrestView and CouchRestViews
  namespace_key = CouchRestViews::ClassNamespaceKey
  #options, use native couchdb _id or buidl a view for the model key
  #currently opting for using _id, but this requires rebuilding the primary key data
  #which is not an ideal solution
  rtn = begin
    node = @db.get(pk_data)
    node = HashKeys.str_to_sym(node)
    node.delete(PersistLayerKey)
    node.delete(namespace_key)
    node
  rescue RestClient::ResourceNotFound => e
    nil
  end
  rtn
end
raw_all() click to toggle source

some models have additional processing required, but not this one

# File lib/glue_envs/couchrest_glue_env.rb, line 502
def raw_all
  query_all
end
save(user_data) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 386
def save(user_data)
  #I thinkthis was why I originally created the namespace concept but reconciliation is now required
  pk_data = generate_pk_data(user_data[@model_key])
  record_namespace = "#{@user_datastore_location}_#{@model_name}"
  #Major TODO: Deconflict module CouchrestView and CouchRestViews
  namespace_key = CouchRestViews::ClassNamespaceKey
  pl_metadata = {PersistLayerKey => pk_data,  namespace_key => record_namespace}
  new_data = user_data.dup.merge(pl_metadata)
  db = @model_save_params[:db]
  
  raise "No database found to save data" unless db
  raise "No CouchDB Key (#{PersistLayerKey}) found in data: #{new_data.inspect}" unless new_data[PersistLayerKey]
  raise "No [#{@model_key}] key found in model data: #{new_data.inspect}" unless new_data[@model_key]
  
  model_data = HashKeys.sym_to_str(new_data) 
  #db.save_doc(model_data)
  begin
    #TODO: Genericize this
    res = db.save_doc(model_data)
  rescue RestClient::RequestFailed => e
    #TODO Update specs to test for this
    if e.http_code == 409
      doc_str = "Document Conflict in the Database." 
      @@log.warn { doc_str } if @@log.warn?
      existing_doc = db.get(model_data['_id'])
      rev = existing_doc['_rev']
      data_with_rev = model_data.merge({'_rev' => rev})
      res = db.save_doc(data_with_rev)
    else
            raise "Request Failed -- Response: #{res.inspect} Error:#{e}"\
            "\nAdditonal Data: model params: #{model_save_params.inspect}"\
            "\n                model data: #{model_data.inspect}"\
            "\n                all data: #{new_data.inspect}"
    end
  end
end
set_attach_class(db_root_location, attach_class_name) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 314
  def set_attach_class(db_root_location, attach_class_name)
  dyn_attach_class_def = "class #{attach_class_name} < CouchrestAttachment
    use_database CouchRest.database!(\"#{db_root_location}/\")
 
    def self.namespace
      CouchRest.database!(\"http://#{db_root_location}/\")
    end
  end"
  
  self.class.class_eval(dyn_attach_class_def)
  self.class.const_get(attach_class_name)
end
set_couch_design(db, user_id) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 299
def set_couch_design(db, user_id) #, view_name)
    design_doc = CouchRest::Design.new
    design_doc.name = "#{DesignDocBaseName}_#{user_id}_Design"
    #example of a map function that can be passed as a parameter if desired (currently not needed)
    #map_function = "function(doc) {\n  if(doc['#{@@collection_namespace}']) {\n   emit(doc['_id'], 1);\n  }\n}"
    #design_doc.view_by collection_namespace.to_sym #, {:map => map_function }
    design_doc.database = db
    begin
      design_doc = db.get(design_doc['_id'])
    rescue RestClient::ResourceNotFound
      design_doc.save
    end
    design_doc
  end
set_db_location(couch_db_host, db_name_path) click to toggle source

TODO Need to fix some naming issues before bringing this method over into the glue environment def set_attach_class(db_root_location, attach_class_name)

dyn_attach_class_def = "class #{attach_class_name} < CouchrestAttachment
  use_database CouchRest.database!(\"http://#{db_root_location}/\")

  def self.namespace
    CouchRest.database!(\"http://#{db_root_location}/\")
  end
end"

self.class.class_eval(dyn_attach_class_def)
self.class.const_get(attach_class_name)

end

# File lib/glue_envs/couchrest_glue_env.rb, line 278
def set_db_location(couch_db_host, db_name_path)
    couch_db_host.chop if couch_db_host =~ /\/$/ #removes any trailing slash
    db_name_path = "/#{db_name_path}" unless db_name_path =~ /^\// #check for le
    couch_db_location = "#{couch_db_host}#{db_name_path}"
end
set_namespace(db_name_path, db_user_id) click to toggle source

TODO: MAJOR Refactoring may have broken compatibility with already persisted data, need to figure out tool to migrate persisted data when changes occur TODO: MAJOR Namespace should not be bound to the underlying model it should be bound to user data only

# File lib/glue_envs/couchrest_glue_env.rb, line 287
def set_namespace(db_name_path, db_user_id)
    lose_leading_slash = db_name_path.split("/")
    lose_leading_slash.shift
    db_name = lose_leading_slash.join("")
    #namespace = "#{db_name}_#{db_user_id}"
    namespace = "#{db_user_id}"
end
set_user_datastore_location(db, db_user_id) click to toggle source
# File lib/glue_envs/couchrest_glue_env.rb, line 295
def set_user_datastore_location(db, db_user_id)
    "#{db.to_s}::#{db_user_id}"
end