persistence-adapter-flat_file 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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