persistence-adapter-flat_file 0.0.1

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.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,4 @@
1
+
2
+ ## 6/26/2012 ##
3
+
4
+ Initial release.
data/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # Flat File Persistence Adapter #
2
+
3
+ http://rubygems.org/gems/persistence-adapter-flat_file
4
+
5
+ # Summary #
6
+
7
+ Adapter to use flat files as storage port for <a href="https://rubygems.org/gems/persistence">Persistence</a> (<a href="https://github.com/RidiculousPower/persistence">on GitHub</a>).
8
+
9
+ # Description #
10
+
11
+ Implements necessary methods to run Persistence on top of the file system without a database.
12
+
13
+ # Install #
14
+
15
+ * sudo gem install persistence-adapter-flat_file
16
+
17
+ # Usage #
18
+
19
+ The FlatFile adapter is an abstract implementation. Using it requires specifying serialization methods. This permits the creation of concrete adapter implementations that are highly configurable.
20
+
21
+ At this point, two versions exist:
22
+
23
+ * FlatFile::Marshal
24
+ * FlatFile::YAML
25
+
26
+ To use Marshal:
27
+
28
+ ```ruby
29
+ flat_file_adapter = ::Persistence::Adapter::FlatFile::Marshal.new
30
+
31
+ Persistence.enable_port( :flat_file_marshal_port, flat_file_adapter )
32
+ ```
33
+
34
+ To use YAML:
35
+
36
+ ```ruby
37
+ flat_file_adapter = ::Persistence::Adapter::FlatFile::YAML.new
38
+
39
+ Persistence.enable_port( :flat_file_yaml_port, flat_file_adapter )
40
+ ```
41
+
42
+ # License #
43
+
44
+ (The MIT License)
45
+
46
+ Copyright (c) 2012, Asher, Ridiculous Power
47
+
48
+ Permission is hereby granted, free of charge, to any person obtaining
49
+ a copy of this software and associated documentation files (the
50
+ 'Software'), to deal in the Software without restriction, including
51
+ without limitation the rights to use, copy, modify, merge, publish,
52
+ distribute, sublicense, and/or sell copies of the Software, and to
53
+ permit persons to whom the Software is furnished to do so, subject to
54
+ the following conditions:
55
+
56
+ The above copyright notice and this permission notice shall be
57
+ included in all copies or substantial portions of the Software.
58
+
59
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
60
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
61
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
62
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
63
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
64
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
65
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+
2
+ require 'base64'
3
+
4
+ #require 'persistence'
5
+ require_relative '../../../../persistence/lib/persistence.rb'
6
+
7
+ # namespaces that have to be declared ahead of time for proper load order
8
+ require_relative './namespaces'
9
+
10
+ # source file requires
11
+ require_relative './requires.rb'
12
+
13
+ class ::Persistence::Adapter::FlatFile
14
+
15
+ include ::Persistence::Adapter::FlatFile::AdapterInterface
16
+
17
+ end
@@ -0,0 +1,246 @@
1
+
2
+ module ::Persistence::Adapter::FlatFile::AdapterInterface
3
+
4
+ include ::Persistence::Adapter::Abstract::EnableDisable
5
+ include ::Persistence::Adapter::Abstract::PrimaryKey::IDPropertyString
6
+
7
+ include ::Persistence::Adapter::FlatFile::PathHelpers
8
+ include ::Persistence::Adapter::FlatFile::Serialization
9
+
10
+ Delimiter = '.'
11
+
12
+ attr_accessor :parent_bucket
13
+
14
+ ################
15
+ # initialize #
16
+ ################
17
+
18
+ def initialize( home_directory )
19
+
20
+ super( home_directory )
21
+
22
+ @buckets = { }
23
+
24
+ # make sure we have a sequence
25
+ unless File.exists?( file__global_id_sequence )
26
+ create_or_update_value_serialize_and_write( file__global_id_sequence, -1 )
27
+ end
28
+
29
+ end
30
+
31
+ ###################
32
+ # adapter_class #
33
+ ###################
34
+
35
+ def adapter_class
36
+
37
+ return self.class
38
+
39
+ end
40
+
41
+ ########################
42
+ # persistence_bucket #
43
+ ########################
44
+
45
+ def persistence_bucket( bucket_name )
46
+
47
+ bucket_instance = nil
48
+
49
+ unless bucket_instance = @buckets[ bucket_name ]
50
+ bucket_instance = ::Persistence::Adapter::FlatFile::Bucket.new( self, bucket_name )
51
+ @buckets[ bucket_name ] = bucket_instance
52
+ end
53
+
54
+ return bucket_instance
55
+
56
+ end
57
+
58
+ ###################################
59
+ # get_bucket_name_for_object_id #
60
+ ###################################
61
+
62
+ def get_bucket_name_for_object_id( global_id )
63
+
64
+ file__bucket = file__bucket_name_for_id( global_id )
65
+
66
+ return open_read_unserialize_and_close( file__bucket )
67
+
68
+ end
69
+
70
+ #############################
71
+ # get_class_for_object_id #
72
+ #############################
73
+
74
+ def get_class_for_object_id( global_id )
75
+
76
+ file__class = file__class_for_id( global_id )
77
+
78
+ return open_read_unserialize_and_close( file__class )
79
+
80
+ end
81
+
82
+ #################################
83
+ # delete_bucket_for_object_id #
84
+ #################################
85
+
86
+ def delete_bucket_for_object_id( global_id )
87
+
88
+ file__bucket = file__bucket_name_for_id( global_id )
89
+
90
+ File.delete( file__bucket )
91
+
92
+ end
93
+
94
+ ################################
95
+ # delete_class_for_object_id #
96
+ ################################
97
+
98
+ def delete_class_for_object_id( global_id )
99
+
100
+ file__class = file__class_for_id( global_id )
101
+
102
+ File.delete( file__class )
103
+
104
+ end
105
+
106
+ ##########################################
107
+ # ensure_object_has_globally_unique_id #
108
+ ##########################################
109
+
110
+ def ensure_object_has_globally_unique_id( object )
111
+
112
+ unless global_id = object.persistence_id
113
+
114
+ # iterate the sequence by 1 and use the ID
115
+ object.persistence_id = global_id = iterate_id_sequence
116
+
117
+ # store bucket name for ID
118
+ store_bucket_name_for_id( object.persistence_bucket.name, global_id )
119
+
120
+ # store class for ID
121
+ store_class_for_id( object.class, global_id )
122
+
123
+ end
124
+
125
+ return global_id
126
+
127
+ end
128
+
129
+ ##################################################################################################
130
+ private ######################################################################################
131
+ ##################################################################################################
132
+
133
+ #########################
134
+ # iterate_id_sequence #
135
+ #########################
136
+
137
+ def iterate_id_sequence
138
+
139
+ new_object_id = nil
140
+
141
+ open_read_unserialize_perform_action_serialize_write_and_close( file__global_id_sequence ) do |last_persistence_id|
142
+
143
+ # iterate for persistence ID - "last_persistence_id" is the last ID used
144
+ last_persistence_id = last_persistence_id + 1
145
+
146
+ # assign to object
147
+ new_object_id = last_persistence_id
148
+
149
+ end
150
+
151
+ return new_object_id
152
+
153
+ end
154
+
155
+ ##############################
156
+ # store_bucket_name_for_id #
157
+ ##############################
158
+
159
+ def store_bucket_name_for_id( bucket_name, global_id )
160
+
161
+ file__bucket_name = file__bucket_name_for_id( global_id )
162
+
163
+ create_or_update_value_serialize_and_write( file__bucket_name, bucket_name )
164
+
165
+ end
166
+
167
+ ########################
168
+ # store_class_for_id #
169
+ ########################
170
+
171
+ def store_class_for_id( klass, global_id )
172
+
173
+ file__class = file__class_for_id( global_id )
174
+
175
+ serialize_as_class = self.class::StringifyClassnames ? klass.to_s : klass
176
+
177
+ create_or_update_value_serialize_and_write( file__class, serialize_as_class )
178
+
179
+ end
180
+
181
+ ###################################
182
+ # directory__bucket_name_for_id #
183
+ ###################################
184
+
185
+ def directory__bucket_name_for_id
186
+
187
+ directory__bucket_name_for_id = File.join( home_directory, 'bucket_name_for_id' )
188
+
189
+ ensure_directory_path_exists( directory__bucket_name_for_id )
190
+
191
+ return directory__bucket_name_for_id
192
+
193
+ end
194
+
195
+ #############################
196
+ # directory__class_for_id #
197
+ #############################
198
+
199
+ def directory__class_for_id
200
+
201
+ directory__class_for_id = File.join( home_directory, 'class_for_id' )
202
+
203
+ ensure_directory_path_exists( directory__class_for_id )
204
+
205
+ return directory__class_for_id
206
+
207
+ end
208
+
209
+ ##############################
210
+ # file__global_id_sequence #
211
+ ##############################
212
+
213
+ # Global ID Sequence: <home_directory>/global_id_sequence.ruby_serialize.txt
214
+ def file__global_id_sequence
215
+
216
+ return File.join( home_directory,
217
+ 'global_id_sequence' + self.class::SerializationExtension )
218
+
219
+ end
220
+
221
+ ##############################
222
+ # file__bucket_name_for_id #
223
+ ##############################
224
+
225
+ # Bucket/key/class: <home_directory>/bucket_class/id.ruby_serialize.txt
226
+ def file__bucket_name_for_id( global_id )
227
+
228
+ return File.join( directory__bucket_name_for_id,
229
+ global_id.to_s + self.class::SerializationExtension )
230
+
231
+ end
232
+
233
+ ########################
234
+ # file__class_for_id #
235
+ ########################
236
+
237
+ # Bucket/key/class: <home_directory>/bucket_class/id.ruby_serialize.txt
238
+ def file__class_for_id( global_id )
239
+
240
+ return File.join( directory__class_for_id,
241
+ global_id.to_s + self.class::SerializationExtension )
242
+
243
+ end
244
+
245
+ end
246
+
@@ -0,0 +1,6 @@
1
+
2
+ class ::Persistence::Adapter::FlatFile::Bucket
3
+
4
+ include ::Persistence::Adapter::FlatFile::Bucket::BucketInterface
5
+
6
+ end
@@ -0,0 +1,374 @@
1
+
2
+ module ::Persistence::Adapter::FlatFile::Bucket::BucketInterface
3
+
4
+ include ::Persistence::Adapter::Abstract::PrimaryKey::Simple
5
+
6
+ include ::Persistence::Adapter::FlatFile::PathHelpers
7
+ include ::Persistence::Adapter::FlatFile::Serialization
8
+
9
+ attr_accessor :name, :parent_adapter
10
+
11
+ ################
12
+ # initialize #
13
+ ################
14
+
15
+ def initialize( parent_adapter, bucket_name )
16
+
17
+ @parent_adapter = parent_adapter
18
+ @name = bucket_name
19
+
20
+ # storage for index objects
21
+ @indexes = { }
22
+
23
+ end
24
+
25
+ ###################
26
+ # adapter_class #
27
+ ###################
28
+
29
+ def adapter_class
30
+
31
+ return @parent_adapter.class
32
+
33
+ end
34
+
35
+ ############
36
+ # cursor #
37
+ ############
38
+
39
+ def cursor
40
+
41
+ return ::Persistence::Adapter::FlatFile::Cursor.new( self, nil )
42
+
43
+ end
44
+
45
+ ###########
46
+ # count #
47
+ ###########
48
+
49
+ def count
50
+
51
+ glob_list = Dir.glob( File.join( directory__ids_in_bucket, '*' ) )
52
+
53
+ return glob_list.count
54
+
55
+ end
56
+
57
+ #################
58
+ # put_object! #
59
+ #################
60
+
61
+ # must be recoverable by information in the object
62
+ # we currently use class and persistence key
63
+ def put_object!( object )
64
+
65
+ @parent_adapter.ensure_object_has_globally_unique_id( object )
66
+
67
+ # write ID to bucket's contents
68
+ file__ids_in_bucket = file__ids_in_bucket( object.persistence_id )
69
+ create_or_update_value_serialize_and_write( file__ids_in_bucket, object.persistence_id )
70
+
71
+ object_persistence_hash = object.persistence_hash_to_port
72
+
73
+ # iterate flat properties:
74
+ # * remove delete cascades for this attribute
75
+ object_persistence_hash.each do |this_attribute, this_attribute_value|
76
+
77
+ file__attribute = file__attributes( object.persistence_id, this_attribute )
78
+
79
+ create_or_update_value_serialize_and_write( file__attribute, this_attribute_value )
80
+
81
+ end
82
+
83
+ return object.persistence_id
84
+
85
+ end
86
+
87
+ ################
88
+ # get_object #
89
+ ################
90
+
91
+ def get_object( global_id )
92
+
93
+ persistence_hash_from_port = { }
94
+
95
+ # iterate directory of flat objects and unload into hash
96
+ ::Dir[ ::File.join( directory__attributes( global_id ), '*' ) ].each do |this_file|
97
+
98
+ # unserialize contents of file at path for flat attribute value
99
+ this_attribute = attribute_name_from_file_path( this_file )
100
+ this_attribute = this_attribute.to_sym if this_attribute
101
+ this_value = open_read_unserialize_and_close( this_file )
102
+
103
+ persistence_hash_from_port[ this_attribute ] = this_value
104
+
105
+ end
106
+
107
+ return persistence_hash_from_port.empty? ? nil : persistence_hash_from_port
108
+
109
+ end
110
+
111
+ ####################
112
+ # delete_object! #
113
+ ####################
114
+
115
+ def delete_object!( global_id )
116
+
117
+ # delete flat properties
118
+ ::Dir[ ::File.join( directory__attributes( global_id ), '*' ) ].each do |this_file|
119
+
120
+ ::File.delete( this_file )
121
+
122
+ end
123
+
124
+ file__ids_in_bucket = file__ids_in_bucket( global_id )
125
+ ::File.delete( file__ids_in_bucket )
126
+
127
+ @parent_adapter.delete_bucket_for_object_id( global_id )
128
+ @parent_adapter.delete_class_for_object_id( global_id )
129
+
130
+ return self
131
+
132
+ end
133
+
134
+ ####################
135
+ # put_attribute! #
136
+ ####################
137
+
138
+ def put_attribute!( global_id, attribute_name, value )
139
+
140
+ file__attribute = file__attributes( global_id, attribute_name )
141
+
142
+ create_or_update_value_serialize_and_write( file__attribute, value )
143
+
144
+ return self
145
+
146
+ end
147
+
148
+ ###################
149
+ # get_attribute #
150
+ ###################
151
+
152
+ def get_attribute( global_id, attribute_name )
153
+
154
+ file__attribute = file__attributes( global_id, attribute_name )
155
+
156
+ return open_read_unserialize_and_close( file__attribute )
157
+
158
+ end
159
+
160
+ #######################
161
+ # delete_attribute! #
162
+ #######################
163
+
164
+ def delete_attribute!( global_id, attribute_name )
165
+
166
+ file__attribute = file__attributes( global_id, attribute_name )
167
+
168
+ # delete this attribute
169
+ ::File.delete( file__attribute )
170
+
171
+ end
172
+
173
+ ##################
174
+ # create_index #
175
+ ##################
176
+
177
+ def create_index( index_name, permits_duplicates )
178
+
179
+ unless ( permits_duplicates_value = permits_duplicates?( index_name ) ).nil?
180
+
181
+ if ! permits_duplicates_value != ! permits_duplicates
182
+ raise 'Index on :' + index_name.to_s + ' already exists and ' +
183
+ ( permits_duplicates ? 'does not permit' : 'permits' ) + ' duplicates, which conflicts.'
184
+ end
185
+
186
+ else
187
+
188
+ file__permits_duplicates = file__index_permits_duplicates( index_name )
189
+
190
+ create_or_update_value_serialize_and_write( file__permits_duplicates,
191
+ permits_duplicates )
192
+
193
+ end
194
+
195
+ # create/instantiate the index
196
+ index_instance = ::Persistence::Adapter::FlatFile::Bucket::Index.new( index_name,
197
+ self,
198
+ permits_duplicates )
199
+
200
+ # store index instance
201
+ @indexes[ index_name ] = index_instance
202
+
203
+ return index_instance
204
+
205
+ end
206
+
207
+ ###########
208
+ # index #
209
+ ###########
210
+
211
+ def index( index_name )
212
+
213
+ return @indexes[ index_name ]
214
+
215
+ end
216
+
217
+ ################
218
+ # has_index? #
219
+ ################
220
+
221
+ def has_index?( index_name )
222
+
223
+ return @indexes.has_key?( index_name )
224
+
225
+ end
226
+
227
+ #########################
228
+ # permits_duplicates? #
229
+ #########################
230
+
231
+ def permits_duplicates?( index_name )
232
+
233
+ file__permits_duplicates = file__index_permits_duplicates( index_name )
234
+
235
+ return open_read_unserialize_and_close( file__permits_duplicates )
236
+
237
+ end
238
+
239
+ ##################
240
+ # delete_index #
241
+ ##################
242
+
243
+ def delete_index( index_name )
244
+
245
+ index_instance = @indexes.delete( index_name )
246
+
247
+ index_instance.delete
248
+
249
+ # delete permits_duplicates
250
+ File.delete( file__index_permits_duplicates( index_name ) )
251
+
252
+ return self
253
+
254
+ end
255
+
256
+ ##################################################################################################
257
+ private ######################################################################################
258
+ ##################################################################################################
259
+
260
+ #######################
261
+ # directory__object #
262
+ #######################
263
+
264
+ def directory__object( global_id )
265
+
266
+ directory__object = File.join( @parent_adapter.home_directory,
267
+ 'objects',
268
+ @name.to_s,
269
+ global_id.to_s )
270
+
271
+ ensure_directory_path_exists( directory__object )
272
+
273
+ return directory__object
274
+
275
+ end
276
+
277
+ ###########################
278
+ # directory__attributes #
279
+ ###########################
280
+
281
+ # Flat Property Directory: <home_directory>/objects/bucket/id/flat_properties
282
+ def directory__attributes( global_id )
283
+
284
+ directory__attributes = File.join( directory__object( global_id ),
285
+ 'attributes' )
286
+
287
+ ensure_directory_path_exists( directory__attributes )
288
+
289
+ return directory__attributes
290
+
291
+ end
292
+
293
+ #########################################
294
+ # directory__index_permits_duplicates #
295
+ #########################################
296
+
297
+ def directory__index_permits_duplicates
298
+
299
+ directory__index_permits_duplicates = File.join( @parent_adapter.home_directory, 'indexes' )
300
+
301
+ ensure_directory_path_exists( directory__index_permits_duplicates )
302
+
303
+ return directory__index_permits_duplicates
304
+
305
+ end
306
+
307
+ ##############################
308
+ # directory__ids_in_bucket #
309
+ ##############################
310
+
311
+ # Global IDs: <home_directory>/global_ids/bucket/
312
+ def directory__ids_in_bucket
313
+
314
+ directory__ids_in_bucket = File.join( @parent_adapter.home_directory,
315
+ 'global_ids',
316
+ @name.to_s )
317
+
318
+ ensure_directory_path_exists( directory__ids_in_bucket )
319
+
320
+ return directory__ids_in_bucket
321
+
322
+ end
323
+
324
+ #########################
325
+ # file__ids_in_bucket #
326
+ #########################
327
+
328
+ def file__ids_in_bucket( global_id )
329
+
330
+ return File.join( directory__ids_in_bucket,
331
+ global_id.to_s + @parent_adapter.class::SerializationExtension )
332
+
333
+ end
334
+
335
+ ######################
336
+ # file__attributes #
337
+ ######################
338
+
339
+ # Flat Attributes: <home_directory>/objects/bucket/id/flat_properties/attribute.ruby_serialize.txt
340
+ def file__attributes( global_id, attribute_name )
341
+
342
+ return File.join( directory__attributes( global_id ),
343
+ attribute_name.to_s + @parent_adapter.class::SerializationExtension )
344
+
345
+ end
346
+
347
+ ####################################
348
+ # file__index_permits_duplicates #
349
+ ####################################
350
+
351
+ # Global ID: <home_directory>/indexes/bucket_name__index_name__permits_duplicates.ruby_serialize.txt
352
+ def file__index_permits_duplicates( index_name )
353
+
354
+ key = key.to_s if key.is_a?( Class )
355
+
356
+ return File.join( directory__index_permits_duplicates,
357
+ @name.to_s + '__' + index_name.to_s + '__permits_duplicates' + @parent_adapter.class::SerializationExtension )
358
+
359
+ end
360
+
361
+ ###################################
362
+ # attribute_name_from_file_path #
363
+ ###################################
364
+
365
+ def attribute_name_from_file_path( file_path )
366
+
367
+ # get basename instead of path
368
+ file_basename = File.basename( file_path )
369
+
370
+ return file_basename.slice( 0, file_basename.length - @parent_adapter.class::SerializationExtension.length )
371
+
372
+ end
373
+
374
+ end