tinkit 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. data/LICENSE +176 -0
  2. data/README +11 -0
  3. data/Rakefile +75 -0
  4. data/lib/glue_envs/couchrest/couchrest_attachment_handler.rb +260 -0
  5. data/lib/glue_envs/couchrest/couchrest_files_mgr.rb +198 -0
  6. data/lib/glue_envs/couchrest_glue_env.rb +536 -0
  7. data/lib/glue_envs/files_mgr_base.rb +51 -0
  8. data/lib/glue_envs/filesystem/filesystem_files_mgr.rb +187 -0
  9. data/lib/glue_envs/filesystem_glue_env.rb +395 -0
  10. data/lib/glue_envs/mysql/mysql_files_mgr.rb +175 -0
  11. data/lib/glue_envs/mysql_glue_env.rb +428 -0
  12. data/lib/glue_envs/sdb_s3/sdb_s3_files_mgr.rb +314 -0
  13. data/lib/glue_envs/sdb_s3_glue_env.rb +248 -0
  14. data/lib/helpers/camel.rb +21 -0
  15. data/lib/helpers/filesystem_helpers.rb +27 -0
  16. data/lib/helpers/hash_helpers.rb +74 -0
  17. data/lib/helpers/log_helper.rb +34 -0
  18. data/lib/helpers/mime_types_new.rb +126 -0
  19. data/lib/helpers/old_more_open_struct.rb +28 -0
  20. data/lib/helpers/require_helper.rb +45 -0
  21. data/lib/helpers/tk_escape.rb +17 -0
  22. data/lib/midas/bufs_data_structure.rb +84 -0
  23. data/lib/midas/node_element_operations.rb +264 -0
  24. data/lib/tinkit.rb +38 -0
  25. data/lib/tinkit_base_node.rb +733 -0
  26. data/lib/tinkit_node_factory.rb +47 -0
  27. data/spec/couchrest_files_mgr_spec.rb +551 -0
  28. data/spec/couchrest_glue_spec.rb +246 -0
  29. data/spec/filesystem_files_mgr_spec.rb +236 -0
  30. data/spec/filesystem_glue_spec.rb +243 -0
  31. data/spec/filesystem_helpers_spec.rb +42 -0
  32. data/spec/helpers/bufs_node_builder.rb +17 -0
  33. data/spec/helpers/bufs_sample_dataset.rb +160 -0
  34. data/spec/helpers/bufs_test_environments.rb +81 -0
  35. data/spec/helpers/tmp_view_cleaner.rb +15 -0
  36. data/spec/lib_helpers/tk_escape_spec.rb +45 -0
  37. data/spec/mysql_files_mgr_spec.rb +250 -0
  38. data/spec/mysql_glue_spec.rb +214 -0
  39. data/spec/node_element_operations_spec.rb +392 -0
  40. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec1.rb +82 -0
  41. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec2.rb +68 -0
  42. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec3.rb +80 -0
  43. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec4.rb +110 -0
  44. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec5.rb +84 -0
  45. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec6.rb +83 -0
  46. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec7.rb +101 -0
  47. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec8.rb +92 -0
  48. data/spec/sdb_s3_files_mgr_spec/sdb_s3_files_mgr_spec_all.rb +266 -0
  49. data/spec/sdb_s3_glue_spec.rb +230 -0
  50. data/spec/tinkit_node_factory_spec.rb +1108 -0
  51. metadata +114 -0
@@ -0,0 +1,198 @@
1
+ #require helper for cleaner require statements
2
+ #require File.join(File.dirname(__FILE__), '../../helpers/require_helper')
3
+
4
+ #Tinkit directory organization defined in lib/helpers/require_helper
5
+ require Tinkit.glue 'couchrest/couchrest_attachment_handler'
6
+ require Tinkit.helpers 'hash_helpers'
7
+
8
+ module CouchrestInterface
9
+ class FilesMgr
10
+
11
+ attr_accessor :attachment_doc_class
12
+
13
+ #not tested in factory tests, tested in couchrest though
14
+ def self.get_att_doc(node)
15
+ node_id = node._model_metadata[:_id]
16
+ attachment_doc_id = node.my_GlueEnv.moab_data[:attachClass].uniq_att_doc_id(node_id)
17
+ att_doc = node.my_GlueEnv.moab_data[:db].get(attachment_doc_id)
18
+ if att_doc
19
+ return att_doc
20
+ else
21
+ return nil
22
+ end
23
+ end
24
+
25
+ def initialize(node_env, node_key_value)
26
+ #for bufs node_key is the value of :my_category
27
+ #although it is not used in this class, it is required to
28
+ #maintain consitency with tinkit_base_node
29
+ #TODO: Actually the goal is for moab's to have no dependency on tinkit_base_node
30
+ #so maybe the glue environment should have a files interface to tinkit_base_node??
31
+ #@attachment_doc_class = node_env.attachClass #old
32
+ #TODO: just pass moab_data??
33
+ @attachment_doc_class = node_env.moab_data[:attachClass]
34
+ end
35
+
36
+ def add(node, file_datas)
37
+ bia_class = @attachment_doc_class
38
+ attachment_package = {}
39
+ file_datas = [file_datas].flatten
40
+ file_datas.each do |file_data|
41
+ #get file data
42
+ src_filename = file_data[:src_filename]
43
+ src_basename = TkEscape.escape(File.basename(src_filename))
44
+ raise "File data must include the source filename when adding a file to the model" unless src_filename
45
+ model_basename = file_data[:model_basename] || src_basename
46
+ model_basename.gsub!('+', ' ') #plus signs are problematic
47
+ #TODO: Consider creating TkEscape.unescape method
48
+ model_basename = CGI.unescape(model_basename)
49
+ content_type = file_data[:content_type] || MimeNew.for_ofc_x(model_basename)
50
+ modified_time = file_data[:modified_time] || File.mtime(src_filename).to_s
51
+ #create attachment class data structure
52
+ file_metadata = {}
53
+ file_metadata['content_type'] = content_type
54
+ file_metadata['file_modified'] = modified_time
55
+ #read in file
56
+ #TODO: reading the file in this way is memory intensive for large files, chunking it up would be better
57
+ file_data = File.open(src_filename, "rb") {|f| f.read}
58
+ attachment_package[model_basename] = {'data' => file_data, 'md' => file_metadata}
59
+ end
60
+ #attachment package has now been created
61
+ #create the attachment record
62
+ #The attachment handler (bia_class) will deal with creating vs updating
63
+ user_id = node.my_GlueEnv.user_id
64
+ node_id = node._model_metadata[:_id]
65
+ #TODO: There is probably a cleaner way to do add attachments, but low on the priority list
66
+ record = bia_class.add_attachment_package(node_id, bia_class, attachment_package)
67
+
68
+ #get the basenames we just stored
69
+ stored_basenames = record['_attachments'].keys
70
+ if node.respond_to? :attachment_doc_id
71
+ #make sure the objects attachment id matches the persistence layer's record id
72
+ if node.attachment_doc_id && (node.attachment_doc_id != record['_id'] )
73
+ raise "Attachment ID mismatch, current id: #{node.attachment_doc_id} new id: #{record['_id']}"
74
+ #if the attachment id doesn't exist, create it
75
+ elsif node.attachment_doc_id.nil?
76
+ node.attachment_doc_id = record['_id'] #TODO is it nil after all attachs are deleted?
77
+ else
78
+ #we will reach here when everything is fine but we don't need to do anything
79
+ end
80
+ else #it's a new attachment and the attachment id has not been set, so we create and set it
81
+ node.__set_userdata_key(:attachment_doc_id, record['_id'] )
82
+ end
83
+ stored_basenames
84
+ end
85
+
86
+ def add_raw_data(node, attach_name, content_type, raw_data, file_modified_at = nil)
87
+ bia_class = node.my_GlueEnv.moab_data[:attachClass]
88
+ file_metadata = {}
89
+ if file_modified_at
90
+ file_metadata['file_modified'] = file_modified_at
91
+ else
92
+ file_metadata['file_modified'] = Time.now.to_s
93
+ end
94
+ file_metadata['content_type'] = content_type
95
+ attachment_package = {}
96
+ unesc_attach_name = TkEscape.unescape(attach_name)
97
+ attachment_package[unesc_attach_name] = {'data' => raw_data, 'md' => file_metadata}
98
+ node_id = node._model_metadata[:_id]
99
+ record = bia_class.add_attachment_package(node_id, bia_class, attachment_package)
100
+ if node.respond_to? :attachment_doc_id
101
+ if node.attachment_doc_id && (node.attachment_doc_id != record['_id'] )
102
+ raise "Attachment ID mismatch, current id: #{node.attachment_doc_id} new id: #{record['_id']}"
103
+ elsif node.attachment_doc_id.nil?
104
+ node.attachment_doc_id = record['_id'] #TODO How is it nil?
105
+ end
106
+ else
107
+ node.__set_userdata_key(:attachment_doc_id, record['_id'] )
108
+ end
109
+ [attach_name]
110
+ end
111
+
112
+ #to conform with files_mgr
113
+ def subtract(node, file_basenames)
114
+ subtract_files(node, file_basenames)
115
+ end
116
+
117
+ #TODO Document the :all shortcut somewhere
118
+ def subtract_files(node, model_basenames)
119
+ bia_class = node.my_GlueEnv.moab_data[:attachClass]
120
+ if model_basenames == :all
121
+ subtract_all(node, bia_class)
122
+ else
123
+ subtract_some(node, model_basenames, bia_class)
124
+ end
125
+ end
126
+
127
+ def get_raw_data(node, model_basename)
128
+ bia_class = node.my_GlueEnv.moab_data[:attachClass]
129
+ node_id = node._model_metadata[:_id]
130
+ bia_doc_id = bia_class.uniq_att_doc_id(node_id)
131
+ bia_doc = bia_class.get(bia_doc_id)
132
+ return nil unless bia_doc
133
+ begin
134
+ rtn = bia_doc.fetch_attachment(model_basename)
135
+ rescue RestClient::ResourceNotFound
136
+ return nil
137
+ end
138
+ rtn
139
+ end
140
+
141
+ def get_attachments_metadata(node)
142
+ bia_class = node.my_GlueEnv.moab_data[:attachClass]
143
+ node_id = node._model_metadata[:_id]
144
+ bia_doc_id = bia_class.uniq_att_doc_id(node_id)
145
+ bia_doc = bia_class.get(bia_doc_id)
146
+ atts = bia_doc.get_attachments
147
+ md_symified = {}
148
+ atts.each do |k,v|
149
+ v_symified = HashKeys.str_to_sym(v)
150
+ md_symified[k] = v_symified
151
+ end
152
+ md_symified
153
+ end
154
+
155
+ def list(node)
156
+ bia_class = node.my_GlueEnv.moab_data[:attachClass]
157
+ node_id = node._model_metadata[:_id]
158
+ bia_doc_id = bia_class.uniq_att_doc_id(node_id)
159
+ bia_doc = bia_class.get(bia_doc_id)
160
+ return nil unless bia_doc
161
+ bia_class.get_attachments(bia_doc).keys
162
+ end
163
+
164
+ #TODO: make private
165
+ def subtract_some(node, model_basenames, bia_class)
166
+ bia_class = node.my_GlueEnv.moab_data[:attachClass]
167
+ node_id = node._model_metadata[:_id]
168
+ bia_doc_id = bia_class.uniq_att_doc_id(node_id)
169
+ if bia_doc_id
170
+ bia_doc = bia_class.get(bia_doc_id)
171
+ raise "No attachment handler found for node: #{node.inspect}" unless bia_doc
172
+ bia_doc.remove_attachment(model_basenames)
173
+ rem_atts = bia_doc.get_attachments
174
+ subtract_all(node, bia_class) if rem_atts.empty?
175
+ end
176
+ end
177
+
178
+ #TODO: Validate that we want to remove the files container
179
+ #TODO: does attachment_doc_id exist any more?
180
+ def subtract_all(node, bia_class)
181
+ #delete the attachment record
182
+ doc_db = node.my_GlueEnv.moab_data[:db]
183
+
184
+ bia_class = node.my_GlueEnv.moab_data[:attachClass]
185
+ node_id = node._model_metadata[:_id]
186
+ bia_doc_id = bia_class.uniq_att_doc_id(node_id)
187
+ if bia_doc_id
188
+ attach_doc = bia_class.get(bia_doc_id)
189
+ doc_db.delete_doc(attach_doc)
190
+ #node.__unset_userdata_key(:attachment_doc_id)
191
+ #node.__save
192
+ else
193
+ puts "Warning: Attempted to delete attachments when none existed"
194
+ end
195
+ node
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,536 @@
1
+ #Tinkit directory structure defined in lib/helpers/require_helpers
2
+ require Tinkit.midas 'bufs_data_structure'
3
+ require Tinkit.glue '/couchrest/couchrest_files_mgr'
4
+ require Tinkit.helpers 'log_helper'
5
+ require Tinkit.helpers 'hash_helpers'
6
+
7
+ module CouchRestViews
8
+ #FIXME MAJOR BUG
9
+ #Views should be an instance to a user class
10
+ #not a module, otherwise, last one set gets all the goodies
11
+
12
+ #Set Logger
13
+ @@log = TinkitLog.set(self.name, :warn)
14
+
15
+ #Constants (pulling out magic text embedded in program)
16
+ #Changing these will break compatibility with earlier records
17
+ ClassViewAllName = "all" #view name stored in the couch db design doc
18
+ ClassNamespaceKey = "all_this_class" #couch db record that the bufs node class name is stored
19
+
20
+
21
+ def self.set_view(db, design_doc, view_name, opts={})
22
+ #raise view_name if view_name == :parent_categories
23
+ #TODO: Add options for custom maps, etc
24
+ #creating view in design_doc
25
+ #puts "setting design_doc #{design_doc['_id']} with view: #{view_name.inspect} with map:\n #{opts.inspect}"
26
+ design_doc.view_by view_name.to_sym, opts
27
+ db_view_name = "by_#{view_name}"
28
+ views = design_doc['views'] || {}
29
+ view_keys = views.keys || []
30
+ unless view_keys.include? db_view_name
31
+ design_doc['_rev'] = nil
32
+ end
33
+ begin
34
+ view_rev_in_db = db.get(design_doc['_id'])['_rev']
35
+ #TODO: See if this can be simplified, I had forgotten the underscore for rev and added a bunch of other stuff
36
+ #I also think I'm saving when it's not needed because I can't figure out how to detect if the saved view matches the
37
+ #current view I want to run yet
38
+ design_doc_uptodate = (design_doc['_rev'] == view_rev_in_db) &&
39
+ (design_doc['views'].keys.include? db_view_name)
40
+ design_doc['_rev'] = view_rev_in_db #unless design_doc_uptodate
41
+ res = design_doc.save #unless design_doc_uptodate
42
+ @@log.debug { "Save Design Doc Response: #{res.inspect}"} if @@log.debug?
43
+ res
44
+ rescue RestClient::RequestFailed
45
+ if @@log.warn?
46
+ @@log.warn { "Warning: Request Failed, assuming because the design doc was already saved?"}
47
+ end
48
+ if @@log.info?
49
+ @@log.info { "Design doc_id: #{design_doc['_id'].inspect}"}
50
+ @@log.info { "doc_rev: #{design_doc['_rev'].inspect}" }
51
+ @@log.info { "db_rev: #{view_rev_in_db}" }
52
+ @@log.info {"Code thinks doc is up to date? #{design_doc_uptodate.inspect}" }
53
+ end
54
+ end
55
+ end
56
+
57
+
58
+ def self.set_view_all(db, design_doc, model_name, datastore_location)
59
+ view_name = "#{ClassViewAllName}_#{model_name}"
60
+ namespace_id = ClassNamespaceKey
61
+ record_namespace = "#{datastore_location}_#{model_name}"
62
+ map_str = "function(doc) {
63
+ if (doc['#{namespace_id}'] == '#{record_namespace}') {
64
+ emit(doc['_id'], doc);
65
+ }
66
+ }"
67
+ map_fn = { :map => map_str }
68
+ self.set_view(db, design_doc, view_name, map_fn)
69
+ end
70
+
71
+ #Set static views.
72
+ #=begin
73
+ def self.set_my_cat_view(db, design_doc, user_datastore_location)
74
+ map_str = "function(doc) {
75
+ if (doc.#{ClassNamespaceKey} =='#{user_datastore_location}' && doc.my_category ){
76
+ emit(doc.my_category, doc);
77
+ }
78
+ }"
79
+ map_fn = { :map => map_str }
80
+ #TODO: Tied to datastructure
81
+ self.set_view(db, design_doc, :my_category, map_fn)
82
+ end
83
+ #=end
84
+ #TODO: Tied to datastructure
85
+ def self.by_my_category(moab_data, user_datastore_location, match_key)
86
+ db = moab_data[:db]
87
+ design_doc = moab_data[:design_doc]
88
+ map_str = "function(doc) {
89
+ if (doc.bufs_namespace =='#{user_datastore_location}' && doc.my_category ){
90
+ emit(doc.my_category, doc);
91
+ }
92
+ }"
93
+ map_fn = { :map => map_str }
94
+ self.set_view(db, design_doc, :my_category, map_fn)
95
+ raw_res = design_doc.view :by_my_category, :key => match_key
96
+ rows = raw_res["rows"]
97
+ records = rows.map{|r| r["value"]}
98
+ end
99
+
100
+ #TODO: Tied to datastructure
101
+ def self.by_parent_categories(moab_data, user_datastore_location, match_keys)
102
+ db = moab_data[:db]
103
+ design_doc = moab_data[:design_doc]
104
+ map_str = "function(doc) {
105
+ if (doc.bufs_namespace == '#{user_datastore_location}' && doc.parent_categories) {
106
+ emit(doc.parent_categories, doc);
107
+ };
108
+ };"
109
+ # }"
110
+ map_fn = { :map => map_str }
111
+
112
+ self.set_view(db, design_doc, :parent_categories, map_fn)
113
+ raw_res = design_doc.view :by_parent_categories
114
+ rows = raw_res["rows"]
115
+ records = rows.map{|r| r["value"] if r["value"]["parent_categories"].include? match_keys}
116
+ end
117
+ end
118
+ module CouchrestViews
119
+ DefineViews = {
120
+ :value_match => nil,
121
+ :included_match => nil,
122
+ :key_of_included_match => nil
123
+ }
124
+
125
+ def view_map(namespace_label, datastore_location, field_name)
126
+ "function(doc) {
127
+ if (doc.#{namespace_label} =='#{user_datastore_location}' && doc.#{field_name} ){
128
+ emit(doc.#{field_name}, doc);
129
+ }
130
+ }"
131
+ end
132
+
133
+ def set_view_value_match(db, design_doc, namespace_key, user_datastore_location, field_name)
134
+ map_function = { :map => view_map(namespace_key, user_datastore_location, field_name) }
135
+ CouchRestViews.set_view(db, design_doc, field_name, map_function)
136
+ end
137
+
138
+
139
+ def call_view(field_name, moab_data, namespace_key, user_datastore_location, match_key, view_name = nil)
140
+ db = moab_data[:db]
141
+ design_doc = moab_data[:design_doc]
142
+ set_view_value_match(db, design_doc, namespace_key, user_datastore_location, field_name)
143
+ view_name = view_name || "by_#{field_name}"
144
+ raw_results = design_doc.view view_name, :key => match_key
145
+ rows = raw_results["rows"]
146
+ records = rows.map{|r| r["value"]}
147
+ end
148
+
149
+ end
150
+
151
+ module CouchrestEnv
152
+ #EnvName = :couchrest_env #name for couchrest environments
153
+
154
+ class GlueEnv
155
+ #Set Logger
156
+ @@log = TinkitLog.set(self.name, :warn)
157
+
158
+ include CouchRest::Mixins::Views::ClassMethods
159
+ include CouchrestViews
160
+
161
+ #used to identify metadata for models (should be consistent across models)
162
+ #ModelKey = :_id
163
+ VersionKey = :_rev
164
+ #NamespaceKey = :bufs_namespace
165
+ PersistLayerKey = :_id #required by CouchDb to be unique in DB
166
+
167
+ CouchMetadataKeys = [:_pos, :_deleted_conflicts, :couchrest, :all_this_class] #possibly more, these keys are ignored
168
+ QueryAllStr = "by_all_bufs".to_sym
169
+ AttachClassBaseName = "MoabAttachmentHandler"
170
+ DesignDocBaseName = "CouchRestEnv" #used to be module name
171
+
172
+
173
+ attr_accessor :user_id,
174
+ :user_datastore_location,
175
+ :metadata_keys,
176
+ :required_instance_keys,
177
+ :required_save_keys,
178
+ :node_key,
179
+ :model_key,
180
+ :version_key,
181
+ :namespace_key,
182
+ :_files_mgr_class,
183
+ :views,
184
+ :model_save_params,
185
+ :moab_data,
186
+ :persist_layer_key,
187
+ #accessors specific to this persitence model
188
+ :db,
189
+ :design_doc,
190
+ :query_all,
191
+ :attachment_base_id,
192
+ :attachClass
193
+
194
+ def initialize(persist_env, data_model_bindings)
195
+ couchrest_env = persist_env[:env]
196
+ couch_db_host = couchrest_env[:host]
197
+ db_name_path = couchrest_env[:path]
198
+ @user_id = couchrest_env[:user_id]
199
+ @model_name = persist_env[:name]
200
+
201
+ #data_model_bindings from NodeElementOperations
202
+ key_fields = data_model_bindings[:key_fields]
203
+ initial_views_data = data_model_bindings[:views] || []
204
+
205
+ #FIXME: Major BUG!! when setting multiple environments in that this may cross-contaminate across users
206
+ #if those users share the same db. Testing up to date has been users on different dbs, so not an issue to date
207
+ #also, one solution might be to force users to their own db? (what about sharing though?)
208
+ #The problem is that there is one "query_all" per database, and it gets set to the last user class
209
+ #that sets it. [Is this still a bug? 12/16/10]
210
+ #@user_id = db_user_id
211
+ #user_attach_class_name = "UserAttach#{db_user_id}"
212
+ #the rescue is so that testing works
213
+ #begin
214
+ # attachClass = UserNode.const_get(user_attach_class_name)
215
+ #rescue NameError
216
+ # puts "Warning:: Multiuser support for attachments not enabled. Using generic Attachment Class"
217
+ # attachClass = CouchrestAttachment
218
+ #end
219
+ couch_db_location = set_db_location(couch_db_host, db_name_path)
220
+ @db = CouchRest.database!(couch_db_location)
221
+ @model_save_params = {:db => @db}
222
+ @persist_layer_key = PersistLayerKey
223
+
224
+ #@collection_namespace = CouchrestEnv.set_collection_namespace(db_name_path, @user_id)
225
+ #@user_datastore_location = CouchRestEnv.set_user_datastore_location(@db, @user_id)
226
+ @user_datastore_location = set_namespace(db_name_path, @user_id)
227
+ @design_doc = set_couch_design(@db, @user_id)#, @collection_namespace)
228
+ @node_key = key_fields[:primary_key]
229
+ #
230
+ @define_query_all = QueryAllStr #CouchRestEnv.query_for_all_collection_records
231
+
232
+ @required_instance_keys = key_fields[:required_keys] #DataStructureModels::RequiredInstanceKeys
233
+ @required_save_keys = key_fields[:required_keys] #DataStructureModels::Tinkit::RequiredSaveKeys
234
+ @model_key = @node_key #ModelKey #CouchRestEnv::ModelKey
235
+ @version_key = VersionKey #CouchRestEnv::VersionKey
236
+ @namespace_key = @model_name #NamespaceKey #CouchRestEnv::NamespaceKey
237
+ #TODO: Need to investigate whether to keep model_key = node_key in metadata
238
+ @metadata_keys = [@persist_layer_key, @version_key, @namespace_key] + CouchMetadataKeys #CouchRestEnv.set_db_metadata_keys #(@collection_namespace)
239
+
240
+ @views = CouchRestViews
241
+ @views.set_view_all(@db, @design_doc, @model_name, @user_datastore_location)
242
+
243
+ @views.set_my_cat_view(@db, @design_doc, @user_datastore_location)
244
+
245
+
246
+
247
+ #set new view
248
+ initial_views_data.each do |view_name, view_data|
249
+ set_view_value_match(@db, @design_doc, @namespace_key, @user_datastore_location, view_data[:field])
250
+ end
251
+
252
+ #@views.set_new_views(xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
253
+
254
+ attach_class_name = "#{AttachClassBaseName}#{@user_id}"
255
+ @attachClass = set_attach_class(@db.root, attach_class_name)
256
+ @moab_data = {:db => @db,
257
+ :design_doc => @design_doc,
258
+ :attachClass => @attachClass}
259
+ #TODO: Have to do the above, but want to do the below
260
+ #@attachClass = set_attach_class(@db.root, attach_class_name)
261
+ @_files_mgr_class = CouchrestInterface::FilesMgr
262
+ end
263
+
264
+ #TODO Need to fix some naming issues before bringing this method over into the glue environment
265
+ #def set_attach_class(db_root_location, attach_class_name)
266
+ # dyn_attach_class_def = "class #{attach_class_name} < CouchrestAttachment
267
+ # use_database CouchRest.database!(\"http://#{db_root_location}/\")
268
+ #
269
+ # def self.namespace
270
+ # CouchRest.database!(\"http://#{db_root_location}/\")
271
+ # end
272
+ # end"
273
+ #
274
+ # self.class.class_eval(dyn_attach_class_def)
275
+ # self.class.const_get(attach_class_name)
276
+ #end
277
+
278
+ def set_db_location(couch_db_host, db_name_path)
279
+ couch_db_host.chop if couch_db_host =~ /\/$/ #removes any trailing slash
280
+ db_name_path = "/#{db_name_path}" unless db_name_path =~ /^\// #check for le
281
+ couch_db_location = "#{couch_db_host}#{db_name_path}"
282
+ end
283
+
284
+ #TODO: MAJOR Refactoring may have broken compatibility with already persisted data, need to
285
+ #figure out tool to migrate persisted data when changes occur
286
+ #TODO: MAJOR Namespace should not be bound to the underlying model it should be bound to user data only
287
+ def set_namespace(db_name_path, db_user_id)
288
+ lose_leading_slash = db_name_path.split("/")
289
+ lose_leading_slash.shift
290
+ db_name = lose_leading_slash.join("")
291
+ #namespace = "#{db_name}_#{db_user_id}"
292
+ namespace = "#{db_user_id}"
293
+ end
294
+
295
+ def set_user_datastore_location(db, db_user_id)
296
+ "#{db.to_s}::#{db_user_id}"
297
+ end
298
+
299
+ def set_couch_design(db, user_id) #, view_name)
300
+ design_doc = CouchRest::Design.new
301
+ design_doc.name = "#{DesignDocBaseName}_#{user_id}_Design"
302
+ #example of a map function that can be passed as a parameter if desired (currently not needed)
303
+ #map_function = "function(doc) {\n if(doc['#{@@collection_namespace}']) {\n emit(doc['_id'], 1);\n }\n}"
304
+ #design_doc.view_by collection_namespace.to_sym #, {:map => map_function }
305
+ design_doc.database = db
306
+ begin
307
+ design_doc = db.get(design_doc['_id'])
308
+ rescue RestClient::ResourceNotFound
309
+ design_doc.save
310
+ end
311
+ design_doc
312
+ end
313
+
314
+ def set_attach_class(db_root_location, attach_class_name)
315
+ dyn_attach_class_def = "class #{attach_class_name} < CouchrestAttachment
316
+ use_database CouchRest.database!(\"http://#{db_root_location}/\")
317
+
318
+ def self.namespace
319
+ CouchRest.database!(\"http://#{db_root_location}/\")
320
+ end
321
+ end"
322
+
323
+ self.class.class_eval(dyn_attach_class_def)
324
+ self.class.const_get(attach_class_name)
325
+ end
326
+
327
+ def query_all #TODO move to ViewsMgr and change the confusing accessor/method clash
328
+ #breaks everything -> self.set_view(@db, @design_doc, @collection_namespace)
329
+ view_name = "by_#{CouchRestViews::ClassViewAllName}_#{@model_name}"
330
+ #raise view_name
331
+ raw_res = @design_doc.view view_name #@define_query_all
332
+ raw_data = raw_res["rows"]
333
+ data = raw_data.map {|rec| HashKeys.str_to_sym( rec['value'] ) }
334
+ end
335
+
336
+ #current relations supported:
337
+ # - :equals (data in the key field matches this_value)
338
+ # - :contains (this_value is contained in the key field data (same as equals for non-enumerable types )
339
+ def find_nodes_where(key, relation, this_value)
340
+
341
+ res = case relation
342
+ when :equals
343
+ find_equals(key, this_value)
344
+ when :contains
345
+ find_contains(key, this_value)
346
+ else
347
+ raise "Couldn't determine relationship between stored data and lookup data"
348
+ end #case
349
+ @@log.info {"Found #{res.size} nodes where #{key} #{relation} #{this_value}"} if @@log.info?
350
+ return res
351
+ end
352
+
353
+ def find_equals(key, this_value)
354
+ results =[]
355
+ query_all.each do |record|
356
+ test_val = record[key]
357
+ results << record if test_val == this_value
358
+ end
359
+ @@log.debug {"Found equals results: #{results.inspect}"} if @@log.debug?
360
+ results
361
+ end
362
+
363
+ def find_contains(key, this_value)
364
+ #TODO: Make a view for this rather than doing it in ruby
365
+ results =[]
366
+ query_all.each do |record|
367
+ test_val = record[key]
368
+ results << record if find_contains_type_helper(test_val, this_value)
369
+ end
370
+ @@log.debug {"Found contains results: #{results.inspect}"} if @@log.debug?
371
+ results
372
+ end
373
+
374
+ def find_contains_type_helper(stored_data, this_value)
375
+ resp = nil
376
+ #stored_data = jparse(stored_dataj)
377
+ if stored_data.respond_to?(:"include?")
378
+ resp = (stored_data.include?(this_value))
379
+ else
380
+ resp = (stored_data == this_value)
381
+ end
382
+ return resp
383
+ end
384
+
385
+
386
+ def save(user_data)
387
+ #I thinkthis was why I originally created the namespace concept but reconciliation is now required
388
+ pk_data = generate_pk_data(user_data[@model_key])
389
+ record_namespace = "#{@user_datastore_location}_#{@model_name}"
390
+ #Major TODO: Deconflict module CouchrestView and CouchRestViews
391
+ namespace_key = CouchRestViews::ClassNamespaceKey
392
+ pl_metadata = {PersistLayerKey => pk_data, namespace_key => record_namespace}
393
+ new_data = user_data.dup.merge(pl_metadata)
394
+ db = @model_save_params[:db]
395
+
396
+ raise "No database found to save data" unless db
397
+ raise "No CouchDB Key (#{PersistLayerKey}) found in data: #{new_data.inspect}" unless new_data[PersistLayerKey]
398
+ raise "No [#{@model_key}] key found in model data: #{new_data.inspect}" unless new_data[@model_key]
399
+
400
+ model_data = HashKeys.sym_to_str(new_data)
401
+ #db.save_doc(model_data)
402
+ begin
403
+ #TODO: Genericize this
404
+ res = db.save_doc(model_data)
405
+ rescue RestClient::RequestFailed => e
406
+ #TODO Update specs to test for this
407
+ if e.http_code == 409
408
+ doc_str = "Document Conflict in the Database."
409
+ @@log.warn { doc_str } if @@log.warn?
410
+ existing_doc = db.get(model_data['_id'])
411
+ rev = existing_doc['_rev']
412
+ data_with_rev = model_data.merge({'_rev' => rev})
413
+ res = db.save_doc(data_with_rev)
414
+ else
415
+ raise "Request Failed -- Response: #{res.inspect} Error:#{e}"\
416
+ "\nAdditonal Data: model params: #{model_save_params.inspect}"\
417
+ "\n model data: #{model_data.inspect}"\
418
+ "\n all data: #{new_data.inspect}"
419
+ end
420
+ end
421
+ end
422
+
423
+ def get(id)
424
+ #id can be the model id or the persist layer id
425
+ pk_data = if id.include? self.class.name
426
+ id
427
+ else
428
+ generate_pk_data(id)
429
+ end
430
+ #maybe put in some validations to ensure its from the proper collection namespace?
431
+ #pk_data = id #generate_pk_data(id)
432
+ #Major TODO: Deconflict module CouchrestView and CouchRestViews
433
+ namespace_key = CouchRestViews::ClassNamespaceKey
434
+ #options, use native couchdb _id or buidl a view for the model key
435
+ #currently opting for using _id, but this requires rebuilding the primary key data
436
+ #which is not an ideal solution
437
+ rtn = begin
438
+ node = @db.get(pk_data)
439
+ node = HashKeys.str_to_sym(node)
440
+ node.delete(PersistLayerKey)
441
+ node.delete(namespace_key)
442
+ node
443
+ rescue RestClient::ResourceNotFound => e
444
+ nil
445
+ end
446
+ rtn
447
+ end
448
+
449
+ #Not tested in factory tests (but is in couchrest tests)
450
+ def destroy_node(model_metadata)
451
+ #att_doc = node.my_GlueEnv.attachClass.get(node.attachment_doc_id) if node.respond_to?(:attachment_doc_id)
452
+ attachClass = @moab_data[:attachClass]
453
+ att_doc_id = attachClass.uniq_att_doc_id(model_metadata[@persist_layer_key])
454
+ att_doc = attachClass.get(att_doc_id) if att_doc_id
455
+ #raise "Destroying Attachment #{att_doc.inspect} derived from #{@model_metadata.inspect} "
456
+ att_doc.destroy if att_doc
457
+ db_destroy(model_metadata)
458
+ end
459
+
460
+ #TODO: update glue models so that it's id and rev that are explicitly needed
461
+ #which begs the question whether rev is supported or not
462
+ def db_destroy(model_metadata)
463
+ id_to_delete = model_metadata[@persist_layer_key] || generate_pk_data(model_metadata[@node_key])
464
+ @@log.info { "Destroy in DB, Key: #{id_to_delete.inspect} in #{model_metadata.inspect}" } if @@log.info?
465
+
466
+ rev_to_delete = model_metadata[@version_key] || @db.get(id_to_delete)['_rev']
467
+ doc_to_delete = {'_id' => id_to_delete, '_rev' => rev_to_delete }
468
+
469
+ @@log.debug { "Attempting to Delete: #{doc_to_delete.inspect}" } if @@log.debug?
470
+ begin
471
+ @db.delete_doc(doc_to_delete)
472
+ rescue ArgumentError => e
473
+ puts "Rescued Error: #{e} while trying to destroy #{model_metadata[@model_key]} node"
474
+ #Code here was deleting the latest version, but rather than wait for an error, it was proactively checked
475
+ #so this block may not be needed any more
476
+ #node = node.class.get(model_metadata[@model_key]) #(model_metadata['_id'])
477
+ #db_destroy(model_metadata)
478
+ end
479
+ end
480
+
481
+
482
+ #def xdestroy_node(node)
483
+ # CouchRestEnv::destroy_node(node)
484
+ # node = nil
485
+ #end
486
+
487
+ #I hope this can be replaced by the generate_pk_data, but need to make sure
488
+ #FIXME: Actually it has to be
489
+ def generate_model_key(namespace, node_key_value)
490
+ #TODO: Make sure namespace is portable across model migrations (must be diff database)
491
+ #"#{namespace}::#{node_key}" # <== original if the below ends up breaking stuff
492
+ #"#{node_key}"
493
+ generate_pk_data(node_key_value)
494
+ end
495
+
496
+ def generate_pk_data(record_id)
497
+ #url_friendly_class_name = self.class.name.gsub('::','-')
498
+ "#{user_id}:#{self.class.name}:#{record_id}"
499
+ end
500
+
501
+ #some models have additional processing required, but not this one
502
+ def raw_all
503
+ query_all
504
+ end
505
+
506
+
507
+
508
+ #TODO: Investigate if Couchrest bulk actions or design views will assist here
509
+ #fixed to delete orphaned attachments, but this negates much of the advantage of using this method in the first place
510
+ #or perhaps using a close to the metal design view based on the class name?? (this may be better)
511
+ def destroy_bulk(user_records_to_delete)
512
+ #TODO: Investigate why mutiple ids may be returned for the same record
513
+ #Answer Database Corruption
514
+ user_records_to_delete.uniq!
515
+ #puts "List of all records: #{user_records_to_delete.map{|r| r['_id']}.inspect}"
516
+
517
+ user_records_to_delete.each do |user_rec|
518
+ begin
519
+ db_key = user_rec[@persist_layer_key] || generate_pk_data(user_rec[@model_key])
520
+ att_doc_id = attachClass.uniq_att_doc_id(db_key)
521
+ r = @db.get(db_key )
522
+ @db.delete_doc(r)
523
+ begin
524
+ att_doc = @db.get(att_doc_id)
525
+ rescue
526
+ att_doc = nil
527
+ end
528
+ @db.delete_doc(att_doc) if att_doc
529
+ rescue RestClient::RequestFailed
530
+ @@log.warn{ "Warning:: Failed to delete document?" } if @@log.warn?
531
+ end
532
+ end
533
+ nil #TODO ok to return nil if all docs destroyed? also, not verifying
534
+ end
535
+ end
536
+ end