flexirecord 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +15 -0
- data/lib/flexirecord-demo.rb +6 -6
- data/lib/flexirecord.rb +197 -113
- metadata +3 -2
data/CHANGELOG
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
CHANGELOG for FlexiRecord:
|
3
|
+
|
4
|
+
2007-02-10:
|
5
|
+
- Changed the order of arguments passed to FlexiRecord::Reference#combine to match the documentation and renamed the method to FlexiRecord::Reference#connect.
|
6
|
+
- Minor bugfix in FlexiRecord::Relationship#new
|
7
|
+
- Added FlexiRecord::BaseRecord.add_connected_references as a more simple way to add many-to-many relations.
|
8
|
+
|
9
|
+
2007-02-11:
|
10
|
+
- Renamed the word 'column' to 'attr' or 'attribute' at several places in the code and documentation.
|
11
|
+
- Freezed record arrays, which are fetched through attributes for many-to-one or many-to-many relations.
|
12
|
+
- Added a lock method, to lock tables in a nicier way.
|
13
|
+
|
14
|
+
Release of version 0.0.4.
|
15
|
+
|
data/lib/flexirecord-demo.rb
CHANGED
@@ -70,9 +70,9 @@ module FlexiRecordDemo
|
|
70
70
|
class MediumEntry < FlexiRecord::BaseRecord
|
71
71
|
include FlexiRecord::ListRecord
|
72
72
|
self.table_name = 'medium_entry'
|
73
|
-
|
74
|
-
|
75
|
-
:
|
73
|
+
add_connected_references(
|
74
|
+
Medium, 'medium_', :medium, :entries, :movies,
|
75
|
+
Movie, 'movie_', :movie, :movie_entries, :media
|
76
76
|
)
|
77
77
|
Medium.add_read_option :entries, :default, 'ORDER BY "position"'
|
78
78
|
Medium.add_read_option :movies, :default, 'ORDER BY "rel"."position"'
|
@@ -83,9 +83,9 @@ module FlexiRecordDemo
|
|
83
83
|
# CREATE TABLE "rating" ("person_id" int8 not null references "person" ("id") on delete cascade on update cascade, "movie_id" int8 not null references "movie" ("id") on delete cascade on update cascade, "rating" numeric, "comment" text, PRIMARY KEY ("person_id", "movie_id") );
|
84
84
|
class Rating < FlexiRecord::Relationship
|
85
85
|
self.table_name = 'rating'
|
86
|
-
|
87
|
-
|
88
|
-
:
|
86
|
+
add_connected_references(
|
87
|
+
Person, 'person_', :person, :ratings, :rated_movies,
|
88
|
+
Movie, ['movie_id', 'id'], :movie, :ratings, :rated_by
|
89
89
|
)
|
90
90
|
end
|
91
91
|
|
data/lib/flexirecord.rb
CHANGED
@@ -104,23 +104,81 @@ module FlexiRecord
|
|
104
104
|
self.to_i <=> other.to_i
|
105
105
|
end
|
106
106
|
|
107
|
-
ReadUncommitted = new(0, :read_uncommitted,
|
108
|
-
ReadCommitted = new(1, :read_committed,
|
109
|
-
RepeatableRead = new(2, :repeatable_read,
|
110
|
-
Serializable = new(3, :serializable,
|
107
|
+
ReadUncommitted = new(0, :read_uncommitted, 'READ UNCOMMITTED', 'ReadUncommitted')
|
108
|
+
ReadCommitted = new(1, :read_committed, 'READ COMMITTED', 'ReadCommitted')
|
109
|
+
RepeatableRead = new(2, :repeatable_read, 'REPEATABLE READ', 'RepeatableRead')
|
110
|
+
Serializable = new(3, :serializable, 'SERIALIZABLE', 'Serializable')
|
111
111
|
|
112
112
|
end # end of class IsolationLevel
|
113
113
|
|
114
114
|
|
115
|
-
#
|
115
|
+
# Table lock modes are represented by (constant) LockMode objects:
|
116
|
+
# - LockMode::AccessShare
|
117
|
+
# (Acquired by any read operation.)
|
118
|
+
# - LockMode::RowShare
|
119
|
+
# (Acquired for any row-locking operation.)
|
120
|
+
# - LockMode::RowExclusive
|
121
|
+
# (Acquired when data in a table is modified.)
|
122
|
+
# - LockMode::ShareUpdateExclusive
|
123
|
+
# - LockMode::Share
|
124
|
+
# (Prohibits data changes in the table.)
|
125
|
+
# - LockMode::ShareRowExclusive
|
126
|
+
# (Prohibits data changes and other Share or ShareRowExclusive locks. Used instead of LockMode::Share to prohibit dead locks.)
|
127
|
+
# - LockMode::Exclusive
|
128
|
+
# (Prohibits anything else than reading from the table.)
|
129
|
+
# - LockMode::AccessExclusive
|
130
|
+
# (Prohibits any other access to the table.)
|
131
|
+
|
132
|
+
class LockMode
|
133
|
+
|
134
|
+
private_class_method :new
|
135
|
+
@@symbols = {}
|
136
|
+
|
137
|
+
# Returns a LockMode object, matching the given symbol.
|
138
|
+
def self.by_symbol(symbol)
|
139
|
+
@@symbols[symbol.to_sym]
|
140
|
+
end
|
141
|
+
|
142
|
+
# Used for generating the constants representing the possible table lock modes.
|
143
|
+
def initialize(symbol, sql, name)
|
144
|
+
@symbol = symbol.to_sym
|
145
|
+
@sql = sql.to_s.dup.freeze
|
146
|
+
@name = name.to_s.dup.freeze
|
147
|
+
@@symbols[@symbol] = self
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
|
151
|
+
# Returns the SQL string representation of the lock mode.
|
152
|
+
def to_s
|
153
|
+
@sql
|
154
|
+
end
|
155
|
+
|
156
|
+
# Returns the name of the constant referring to the lock mode.
|
157
|
+
def inspect
|
158
|
+
"#{self.class.name}::#{@name}"
|
159
|
+
end
|
160
|
+
|
161
|
+
AccessShare = new(:access_share, 'ACCESS SHARE MODE', 'AccessShare')
|
162
|
+
RowShare = new(:row_share, 'ROW SHARE MODE', 'RowShare')
|
163
|
+
RowExclusive = new(:row_exclusive, 'ROW EXCLUSIVE MODE', 'RowExclusive')
|
164
|
+
ShareUpdateExclusive = new(:share_update_exclusive, 'SHARE UPDATE EXCLUSIVE MODE', 'ShareUpdateExclusive')
|
165
|
+
Share = new(:share, 'SHARE MODE', 'Share')
|
166
|
+
ShareRowExclusive = new(:share_row_exclusive, 'SHARE ROW EXCLUSIVE MODE', 'ShareRowExclusive')
|
167
|
+
Exclusive = new(:exclusive, 'EXCLUSIVE MODE', 'Exclusive')
|
168
|
+
AccessExclusive = new(:access_exclusive, 'ACCESS EXCLUSIVE MODE', 'AccessExclusive')
|
169
|
+
|
170
|
+
end # end of class LockMode
|
171
|
+
|
172
|
+
|
173
|
+
# Objects of this class are used to describe a reference between two tables. You can create and register them by calling FlexiRecord::BaseRecord.add_many_to_one_reference or FlexiRecord::RaseRecord.add_one_to_one_reference. For using many-to-many relationships you have to extend the class FlexiRecord::Relationship and create two connected ManyToOneReference's for that class by calling FlexiRecord::BaseRecord.add_connected_references.
|
116
174
|
|
117
175
|
class Reference
|
118
176
|
|
119
177
|
private_class_method :new
|
120
178
|
|
121
|
-
# Returns a new reference object, describing a relation where objects of the 'source_class' refer to objects of the 'destination_class'. The 'column_info' field describes the columns used for that reference. If the 'column_info' field is a string, the primary key is used in the destination class, and the primary key prefixed by the string given in 'column_info' is used as the foreign key in the source class. If 'column_info' is an array, it contains the columns in the source class, followed by the
|
179
|
+
# Returns a new reference object, describing a relation where objects of the 'source_class' refer to objects of the 'destination_class'. The 'column_info' field describes the columns used for that reference. If the 'column_info' field is a string, the primary key is used in the destination class, and the primary key prefixed by the string given in 'column_info' is used as the foreign key in the source class. If 'column_info' is an array, it contains the columns in the source class, followed by the columns in the destination class. The field 'src_to_dst_attr' contains the name of the attribute in the source class, which is referring to one object of the destination class. The field 'dst_to_src_attr' contains the name of the attribute in the destination class, which is referring to one or many objects of the source class. After the reference has been created, reader, loader and setter functions are added to the 'source_class' and 'destination_class', to provide access to referenced and referring objects.
|
122
180
|
# This method is private, use one of the child classes OneToOneReference or ManyToOneReference, which get the same arguments, to generate references of a given type.
|
123
|
-
def initialize(source_class, destination_class, column_info,
|
181
|
+
def initialize(source_class, destination_class, column_info, src_to_dst_attr, dst_to_src_attr)
|
124
182
|
unless source_class.kind_of? Class and destination_class.kind_of? Class
|
125
183
|
raise TypeError, "Class expected"
|
126
184
|
end
|
@@ -146,10 +204,10 @@ module FlexiRecord
|
|
146
204
|
end
|
147
205
|
@source_columns.freeze
|
148
206
|
@destination_columns.freeze
|
149
|
-
@
|
150
|
-
@
|
207
|
+
@src_to_dst_attr = src_to_dst_attr.to_s.dup.freeze
|
208
|
+
@dst_to_src_attr = dst_to_src_attr.to_s.dup.freeze
|
151
209
|
# Work at the source class:
|
152
|
-
@source_class.set_loader(@
|
210
|
+
@source_class.set_loader(@src_to_dst_attr) do |source_records, arguments|
|
153
211
|
unless arguments.empty?
|
154
212
|
raise ArgumentError, "No extra arguments may be specified for outgoing reference columns."
|
155
213
|
end
|
@@ -164,15 +222,15 @@ module FlexiRecord
|
|
164
222
|
destination_records.each do |destination_record|
|
165
223
|
destination_record_hash[@destination_columns.collect { |column| destination_record[column] }] = destination_record
|
166
224
|
if self.one_to_one?
|
167
|
-
destination_record[@
|
225
|
+
destination_record[@dst_to_src_attr] = nil
|
168
226
|
end
|
169
227
|
end
|
170
228
|
source_records.each do |source_record|
|
171
229
|
destination_record =
|
172
230
|
destination_record_hash[@source_columns.collect { |column| source_record[column] }]
|
173
|
-
source_record[@
|
231
|
+
source_record[@src_to_dst_attr, *arguments] = destination_record
|
174
232
|
if destination_record and self.one_to_one?
|
175
|
-
destination_record[@
|
233
|
+
destination_record[@dst_to_src_attr] = source_record
|
176
234
|
end
|
177
235
|
end
|
178
236
|
next destination_records
|
@@ -181,28 +239,28 @@ module FlexiRecord
|
|
181
239
|
source_column = @source_columns[column_index]
|
182
240
|
destination_column = @destination_columns[column_index]
|
183
241
|
@source_class.set_reader(source_column) do |source_record, arguments|
|
184
|
-
destination_record = source_record[@
|
242
|
+
destination_record = source_record[@src_to_dst_attr]
|
185
243
|
next destination_record ? destination_record[destination_column] : source_record[source_column]
|
186
244
|
end
|
187
245
|
@source_class.set_setter(source_column) do |source_record, value|
|
188
|
-
source_record.delete_from_cache(@
|
246
|
+
source_record.delete_from_cache(@src_to_dst_attr)
|
189
247
|
source_record[source_column] = value
|
190
248
|
end
|
191
249
|
end
|
192
250
|
if self.one_to_one?
|
193
|
-
@source_class.set_setter(@
|
194
|
-
old_destination_record = source_record[@
|
251
|
+
@source_class.set_setter(@src_to_dst_attr) do |source_record, value|
|
252
|
+
old_destination_record = source_record[@src_to_dst_attr]
|
195
253
|
if old_destination_record
|
196
|
-
old_destination_record[@
|
254
|
+
old_destination_record[@dst_to_src_attr] = nil
|
197
255
|
end
|
198
|
-
source_record[@
|
256
|
+
source_record[@src_to_dst_attr] = value
|
199
257
|
if value
|
200
|
-
value[@
|
258
|
+
value[@dst_to_src_attr] = source_record
|
201
259
|
end
|
202
260
|
end
|
203
261
|
end
|
204
262
|
# Work at the destination class:
|
205
|
-
@destination_class.set_loader(@
|
263
|
+
@destination_class.set_loader(@dst_to_src_attr) do |destination_records, arguments|
|
206
264
|
unless arguments.empty?
|
207
265
|
unless arguments[0].respond_to? :to_str
|
208
266
|
raise "First argument of reader method is not a SQL snippet string."
|
@@ -219,7 +277,7 @@ module FlexiRecord
|
|
219
277
|
destination_record_hash = {}
|
220
278
|
destination_records.each do |destination_record|
|
221
279
|
destination_record_hash[@destination_columns.collect { |column| destination_record[column] }] = destination_record
|
222
|
-
destination_record[@
|
280
|
+
destination_record[@dst_to_src_attr, *arguments] =
|
223
281
|
if self.many_to_one?
|
224
282
|
FlexiRecord::RecordArray.new(@source_class)
|
225
283
|
else
|
@@ -228,49 +286,54 @@ module FlexiRecord
|
|
228
286
|
end
|
229
287
|
source_records.each do |source_record|
|
230
288
|
destination_record = destination_record_hash[@source_columns.collect { |column| source_record[column] }]
|
231
|
-
source_record[@
|
289
|
+
source_record[@src_to_dst_attr] = destination_record
|
232
290
|
if destination_record
|
233
291
|
if self.many_to_one?
|
234
|
-
destination_record[@
|
292
|
+
destination_record[@dst_to_src_attr, *arguments] << source_record
|
235
293
|
else
|
236
|
-
destination_record[@
|
294
|
+
destination_record[@dst_to_src_attr, *arguments] = source_record
|
237
295
|
end
|
238
296
|
end
|
239
297
|
end
|
298
|
+
if self.many_to_one?
|
299
|
+
destination_records.each do |destination_record|
|
300
|
+
destination_record[@dst_to_src_attr, *arguments].freeze
|
301
|
+
end
|
302
|
+
end
|
240
303
|
next source_records
|
241
304
|
end
|
242
305
|
if self.one_to_one?
|
243
|
-
@destination_class.set_setter(@
|
244
|
-
old_source_record = destination_record[@
|
306
|
+
@destination_class.set_setter(@dst_to_src_attr) do |destination_record, value|
|
307
|
+
old_source_record = destination_record[@dst_to_src_attr]
|
245
308
|
if old_source_record
|
246
|
-
old_source_record[@
|
309
|
+
old_source_record[@src_to_dst_attr] = nil
|
247
310
|
end
|
248
|
-
destination_record[@
|
311
|
+
destination_record[@dst_to_src_attr] = value
|
249
312
|
if value
|
250
|
-
value[@
|
313
|
+
value[@src_to_dst_attr] = destination_record
|
251
314
|
end
|
252
315
|
end
|
253
316
|
end
|
254
317
|
return self
|
255
318
|
end
|
256
319
|
|
257
|
-
#
|
258
|
-
def
|
320
|
+
# Connects two ManyToOneReference's to form a many-to-many relation. 'other_reference' is another Reference object (as returned by FlexiRecord::Reference.new, 'own_attr' is the attribute to be installed in the destination_class of this Reference object for accessing objects of the destination_class of the 'other_reference' object, and 'other_attr' is the attribute to be installed in the destination_class of the 'other_reference' object for accessing objects of the destination_class of this Reference object.
|
321
|
+
def connect(other_reference, own_attr, other_attr)
|
259
322
|
unless other_reference.kind_of? FlexiRecord::Reference
|
260
323
|
raise TypeError, "Object of class FlexiRecord::Reference expected."
|
261
324
|
end
|
262
325
|
reference1 = self
|
263
326
|
reference2 = other_reference
|
264
|
-
|
265
|
-
|
327
|
+
attr1 = own_attr.to_s
|
328
|
+
attr2 = other_attr.to_s
|
266
329
|
unless self.source_class == other_reference.source_class
|
267
330
|
"Combining references having different source classes is not possible."
|
268
331
|
end
|
269
332
|
relationship_class = self.source_class
|
270
333
|
[
|
271
|
-
[reference1, reference2,
|
272
|
-
[reference2, reference1,
|
273
|
-
].each do |source_reference, destination_reference,
|
334
|
+
[reference1, reference2, attr1, attr2],
|
335
|
+
[reference2, reference1, attr2, attr1]
|
336
|
+
].each do |source_reference, destination_reference, source_attr, destination_attr|
|
274
337
|
source_class = source_reference.destination_class
|
275
338
|
destination_class = destination_reference.destination_class
|
276
339
|
tmp1 = []
|
@@ -280,7 +343,7 @@ module FlexiRecord
|
|
280
343
|
destination_reference.destination_columns[column_index]
|
281
344
|
]
|
282
345
|
end
|
283
|
-
source_class.set_loader
|
346
|
+
source_class.set_loader source_attr do |source_records, arguments|
|
284
347
|
sql_arguments = arguments.dup
|
285
348
|
sql_snippet = sql_arguments.shift
|
286
349
|
unless sql_snippet.nil?
|
@@ -307,7 +370,7 @@ module FlexiRecord
|
|
307
370
|
destination_record['_flexirecord_rel_' << column]
|
308
371
|
}
|
309
372
|
] ||= FlexiRecord::RecordArray.new(destination_class)) << destination_record
|
310
|
-
relationship_hash = { destination_reference.
|
373
|
+
relationship_hash = { destination_reference.src_to_dst_attr => destination_record }
|
311
374
|
relationship_class.columns.each do |column|
|
312
375
|
relationship_hash[column] =
|
313
376
|
destination_record.delete_from_cache("_flexirecord_rel_#{column}")
|
@@ -315,13 +378,18 @@ module FlexiRecord
|
|
315
378
|
destination_record[FlexiRecord::RelationshipColumn] = relationship_class.new(relationship_hash)
|
316
379
|
end
|
317
380
|
source_records.each do |source_record|
|
318
|
-
source_record[
|
381
|
+
source_record[source_attr, *arguments] = ((destination_record_hash[source_reference.destination_columns.collect { |column| source_record[column] } ]) || FlexiRecord::RecordArray.new(destination_class)).freeze
|
319
382
|
end
|
320
383
|
next destination_records
|
321
384
|
end
|
322
385
|
end
|
323
386
|
end
|
324
387
|
|
388
|
+
# Deprecated. Use FlexiRecord::Reference#connect instead.
|
389
|
+
def combine(*arguments)
|
390
|
+
self.connect(*arguments)
|
391
|
+
end
|
392
|
+
|
325
393
|
# Class, whose objects are referring to others.
|
326
394
|
attr_reader :source_class
|
327
395
|
|
@@ -334,11 +402,11 @@ module FlexiRecord
|
|
334
402
|
# Columns in the referred class, providing a unique or primary key.
|
335
403
|
attr_reader :destination_columns
|
336
404
|
|
337
|
-
# Name (String) of the
|
338
|
-
attr_reader :
|
405
|
+
# Name (String) of the attribute in the source class, which is referring to one object of the destination class.
|
406
|
+
attr_reader :src_to_dst_attr
|
339
407
|
|
340
|
-
# Name (String) of the
|
341
|
-
attr_reader :
|
408
|
+
# Name (String) of the attribute in the destination class, which is referring to one or many objects of the source class.
|
409
|
+
attr_reader :dst_to_src_attr
|
342
410
|
|
343
411
|
# Returns true, if the object describes a one-to-one relation.
|
344
412
|
def one_to_one?
|
@@ -383,7 +451,7 @@ module FlexiRecord
|
|
383
451
|
end # end of class ManyToOneReference
|
384
452
|
|
385
453
|
|
386
|
-
# An abstract record, super-class of all record classes. By now there is only one sub-class 'BaseRecord', which represents a record being directly stored in one database table. Other sub-classes might be added in
|
454
|
+
# An abstract record, super-class of all record classes. By now there is only one sub-class 'BaseRecord', which represents a record being directly stored in one database table. Other sub-classes might be added in future, to be able to use inheritance of data models.
|
387
455
|
|
388
456
|
class AbstractRecord
|
389
457
|
|
@@ -408,18 +476,18 @@ module FlexiRecord
|
|
408
476
|
@flexirecord_class
|
409
477
|
end
|
410
478
|
|
411
|
-
# Preloads
|
412
|
-
def preload(
|
413
|
-
|
414
|
-
@flexirecord_class.prepare_read_parameters(
|
415
|
-
cache_key = [
|
479
|
+
# Preloads the attribute named 'attr' in each record of the RecordArray, without doing one SQL query for each record. Can return another RecordArray, which can be used for the next level of preloading.
|
480
|
+
def preload(attr, *arguments)
|
481
|
+
attr = attr.to_s
|
482
|
+
@flexirecord_class.prepare_read_parameters(attr, arguments)
|
483
|
+
cache_key = [attr] + arguments
|
416
484
|
if @flexirecord_preloaded.has_key?(cache_key)
|
417
485
|
return @flexirecord_preloaded[cache_key]
|
418
486
|
end
|
419
|
-
|
420
|
-
loader = self.record_class.loader(
|
487
|
+
attr = attr.to_s
|
488
|
+
loader = self.record_class.loader(attr)
|
421
489
|
unless loader
|
422
|
-
raise ArgumentError, "Could not preload
|
490
|
+
raise ArgumentError, "Could not preload attribute '#{attr}', due to missing loading procedure."
|
423
491
|
end
|
424
492
|
return @flexirecord_preloaded[cache_key] = loader.call(self, arguments)
|
425
493
|
end
|
@@ -464,7 +532,7 @@ module FlexiRecord
|
|
464
532
|
)
|
465
533
|
end
|
466
534
|
|
467
|
-
# Returns the database schema name, even if no schema is set (in this case "public" is returned.
|
535
|
+
# Returns the database schema name, even if no schema is set (in this case "public" is returned).
|
468
536
|
def schema_name!
|
469
537
|
schema_name or "public".freeze
|
470
538
|
end
|
@@ -562,6 +630,17 @@ module FlexiRecord
|
|
562
630
|
result
|
563
631
|
end
|
564
632
|
|
633
|
+
# Locks the table used to store objects of this class. Calling this command only makes sense, if a transaction is in progress.
|
634
|
+
def lock(mode)
|
635
|
+
if not mode.kind_of? FlexiRecord::LockMode and mode.respond_to? :to_sym
|
636
|
+
mode = FlexiRecord::LockMode.by_symbol(mode)
|
637
|
+
end
|
638
|
+
if mode.nil?
|
639
|
+
raise "Table lock mode expected"
|
640
|
+
end
|
641
|
+
db_execute("LOCK #{table} IN #{mode}")
|
642
|
+
end
|
643
|
+
|
565
644
|
# Autodetects the columns (and primary key columns) of the underlaying table, to be later retrieved by the 'columns' and the 'primary_columns' methods.
|
566
645
|
def autodetect_columns
|
567
646
|
columns = []
|
@@ -682,22 +761,22 @@ module FlexiRecord
|
|
682
761
|
# Adds a "shortcut" for a parameter to reader and loader functions.
|
683
762
|
#
|
684
763
|
# Example: List.add_read_option :items, :by_name, 'ORDER BY "name"'
|
685
|
-
def add_read_option(
|
764
|
+
def add_read_option(attr, symbol, value)
|
686
765
|
unless symbol.kind_of? Symbol
|
687
766
|
raise "Symbol expected as second argument to 'add_read_option'."
|
688
767
|
end
|
689
|
-
(@read_options ||= {})[[
|
768
|
+
(@read_options ||= {})[[attr.to_s, symbol]] = value
|
690
769
|
end
|
691
770
|
|
692
771
|
# Returns the value of a "shortcut" for a parameter to reader and loader functions.
|
693
|
-
def read_option_value(
|
694
|
-
value = (@read_options || {})[[
|
772
|
+
def read_option_value(attr, symbol)
|
773
|
+
value = (@read_options || {})[[attr.to_s, symbol]] || ((superclass <= FlexiRecord::BaseRecord) ? superclass.read_option_value(attr, symbol) : nil)
|
695
774
|
end
|
696
775
|
|
697
776
|
# Modifies a parameter array by replacing "shortcut" symbols being defined by add_read_option. Returns the parameter array.
|
698
|
-
def prepare_read_parameters(
|
777
|
+
def prepare_read_parameters(attr, parameters)
|
699
778
|
option = parameters.first
|
700
|
-
value = read_option_value(
|
779
|
+
value = read_option_value(attr, option.nil? ? :default : option)
|
701
780
|
if value
|
702
781
|
parameters[0] = value
|
703
782
|
elsif option == :default
|
@@ -706,43 +785,43 @@ module FlexiRecord
|
|
706
785
|
return parameters
|
707
786
|
end
|
708
787
|
|
709
|
-
# Sets a given block to be the "reader function" for a particlular
|
710
|
-
def set_reader(
|
711
|
-
(@readers ||= {})[
|
788
|
+
# Sets a given block to be the "reader function" for a particlular attribute. A reader function is invoked, when you try to read a value of the given attribute of a record. Two arguments are passed to the block. The first is the record, whose data is to be read, the second is an Array of arguments passed to the method used for reading the value. The block has to evaluate to the value which should be read.
|
789
|
+
def set_reader(attr, &reader)
|
790
|
+
(@readers ||= {})[attr.to_s] = reader
|
712
791
|
end
|
713
792
|
|
714
|
-
# Returns the reader function (a Proc object) for a certain
|
715
|
-
def reader(
|
716
|
-
(@readers || {})[
|
793
|
+
# Returns the reader function (a Proc object) for a certain attribute.
|
794
|
+
def reader(attr)
|
795
|
+
(@readers || {})[attr.to_s] or ((superclass <= FlexiRecord::BaseRecord) ? superclass.reader(attr) : nil)
|
717
796
|
end
|
718
797
|
|
719
|
-
# Returns an array containing all
|
720
|
-
def
|
798
|
+
# Returns an array containing all attributes having a reader Proc stored in the class.
|
799
|
+
def reader_attrs
|
721
800
|
(@readers || {}).keys + (
|
722
801
|
(superclass <= FlexiRecord::BaseRecord) ?
|
723
|
-
superclass.
|
802
|
+
superclass.reader_attrs :
|
724
803
|
[]
|
725
804
|
)
|
726
805
|
end
|
727
806
|
|
728
|
-
# Sets a given block to be the "loader function" for a particular
|
729
|
-
def set_loader(
|
730
|
-
(@loaders ||= {})[
|
807
|
+
# Sets a given block to be the "loader function" for a particular attribute. Loader functions are invoked, when you try to read an uncached value of the given attribute of a record, or when you preload data for a whole Array of records. Two arguments are passed to the block. The first is an Array of records, whose data is to be loaded, the second is an Array of arguments passed to the method used for accessing the value. The block has to evaluate to an Array of records, which can be used for more than one level deep preloads.
|
808
|
+
def set_loader(attr, &loader)
|
809
|
+
(@loaders ||= {})[attr.to_s] = loader
|
731
810
|
end
|
732
811
|
|
733
|
-
# Returns the loader function (a Proc object) for a certain
|
734
|
-
def loader(
|
735
|
-
(@loaders || {})[
|
812
|
+
# Returns the loader function (a Proc object) for a certain attribute.
|
813
|
+
def loader(attr)
|
814
|
+
(@loaders || {})[attr.to_s] or ((superclass <= FlexiRecord::BaseRecord) ? superclass.loader(attr) : nil)
|
736
815
|
end
|
737
816
|
|
738
|
-
# Sets a given block to be the "setter function" for a particular
|
739
|
-
def set_setter(
|
740
|
-
(@setters ||= {})[
|
817
|
+
# Sets a given block to be the "setter function" for a particular attribute. The setter function is invoked, when you set the value of the given attribute of a record. Two arguments are passed to the block. The first is the record, whose data is to be changed, the second is the new value to be written into the field.
|
818
|
+
def set_setter(attr, &setter)
|
819
|
+
(@setters ||= {})[attr.to_s] = setter
|
741
820
|
end
|
742
821
|
|
743
|
-
# Returns the setter function (a Proc object) for a certain
|
744
|
-
def setter(
|
745
|
-
(@setters || {})[
|
822
|
+
# Returns the setter function (a Proc object) for a certain attribute.
|
823
|
+
def setter(attr)
|
824
|
+
(@setters || {})[attr.to_s] or ((superclass <= FlexiRecord::BaseRecord) ? superclass.setter(attr) : nil)
|
746
825
|
end
|
747
826
|
|
748
827
|
# Adds an OneToOneReference to the class (by simply creating it). The first argument is the destination class, followed by arguments being passed to Reference.new.
|
@@ -759,6 +838,11 @@ module FlexiRecord
|
|
759
838
|
)
|
760
839
|
end
|
761
840
|
|
841
|
+
# Adds two ManyToOneReferences and connects them to form a many-to-many relation between two other record classes. Please look at the FlexiRecordDemo module to see how this works.
|
842
|
+
def add_connected_references(destination_class1, column_info1, src_to_dst_attr1, dst_to_src_attr1, class1_to_class2_attr, destination_class2, column_info2, src_to_dst_attr2, dst_to_src_attr2, class2_to_class1_attr)
|
843
|
+
self.add_many_to_one_reference(destination_class1, column_info1, src_to_dst_attr1, dst_to_src_attr1).combine(self.add_many_to_one_reference(destination_class2, column_info2, src_to_dst_attr2, dst_to_src_attr2), class1_to_class2_attr, class2_to_class1_attr)
|
844
|
+
end
|
845
|
+
|
762
846
|
end # end of singleton meta class of BaseRecord
|
763
847
|
|
764
848
|
private # methods of BaseRecord
|
@@ -801,7 +885,7 @@ module FlexiRecord
|
|
801
885
|
|
802
886
|
public # methods of BaseRecord
|
803
887
|
|
804
|
-
# Rewrites several attributes given by the keys and values in the 'data' hash. Returns self.
|
888
|
+
# Rewrites several attributes given by the keys and values in the 'data' hash. Note: A SQL UPDATE command is not executed before 'save' is called, this method just updates the ruby object. Returns self.
|
805
889
|
def update(data)
|
806
890
|
synchronize do
|
807
891
|
data.each { |key, value| self.set(key, value) }
|
@@ -827,14 +911,14 @@ module FlexiRecord
|
|
827
911
|
end
|
828
912
|
end
|
829
913
|
|
830
|
-
# Reads a value (whose key can consist of multiple fields) from the internal cache. The first argument is by convention a name of a column as String(!), but NOT as a Symbol.
|
914
|
+
# Reads a value (whose key can consist of multiple fields) from the internal cache. The first argument is by convention usually a name of a column as String(!), but NOT as a Symbol.
|
831
915
|
def [](*key)
|
832
916
|
synchronize do
|
833
917
|
return @data_hash[key]
|
834
918
|
end
|
835
919
|
end
|
836
920
|
|
837
|
-
# Writes a value to the internal cache. The first argument is by convention a name of a column as String(!), but NOT as a Symbol.
|
921
|
+
# Writes a value to the internal cache. The first argument is by convention usually a name of a column as String(!), but NOT as a Symbol.
|
838
922
|
def []=(*arguments)
|
839
923
|
synchronize do
|
840
924
|
value = arguments.pop
|
@@ -857,13 +941,13 @@ module FlexiRecord
|
|
857
941
|
end
|
858
942
|
end
|
859
943
|
|
860
|
-
# Reads
|
861
|
-
def read(
|
862
|
-
|
863
|
-
self.class.prepare_read_parameters(
|
864
|
-
data_hash_key = [
|
865
|
-
reader = self.class.reader(
|
866
|
-
loader = self.class.loader(
|
944
|
+
# Reads an attribute. If there is a dynamic reader, this reader is used, otherwise an existent assigned loader function is invoked or the internal cache will give a result. If there is no data for the attribute with the given name, then nil will be returned.
|
945
|
+
def read(attr, *arguments)
|
946
|
+
attr = attr.to_s
|
947
|
+
self.class.prepare_read_parameters(attr, arguments)
|
948
|
+
data_hash_key = [attr] + arguments
|
949
|
+
reader = self.class.reader(attr)
|
950
|
+
loader = self.class.loader(attr)
|
867
951
|
synchronize do
|
868
952
|
if reader
|
869
953
|
return reader.call(self, arguments)
|
@@ -879,22 +963,22 @@ module FlexiRecord
|
|
879
963
|
end
|
880
964
|
end
|
881
965
|
|
882
|
-
# Sets
|
883
|
-
def set(
|
884
|
-
|
885
|
-
setter = self.class.setter(
|
966
|
+
# Sets an attribute. If there is a dynamic setter, this setter is used, otherwise the value get's written in the internal cache.
|
967
|
+
def set(attr, value)
|
968
|
+
attr = attr.to_s
|
969
|
+
setter = self.class.setter(attr)
|
886
970
|
if setter
|
887
971
|
setter.call(self, value)
|
888
|
-
return @data_hash[[
|
972
|
+
return @data_hash[[attr]]
|
889
973
|
else
|
890
|
-
return @data_hash[[
|
974
|
+
return @data_hash[[attr]] = value
|
891
975
|
end
|
892
976
|
end
|
893
977
|
|
894
978
|
# Returns an array of strings of the columns in the backend database, which have either values in the internal cache, or which have a dynamic reader function.
|
895
979
|
def used_columns
|
896
980
|
synchronize do
|
897
|
-
return self.class.columns & (self.class.
|
981
|
+
return self.class.columns & (self.class.reader_attrs + @data_hash.keys.reject { |key| key.length > 1 }.collect { |key| key.first })
|
898
982
|
end
|
899
983
|
end
|
900
984
|
|
@@ -928,20 +1012,20 @@ module FlexiRecord
|
|
928
1012
|
# Provides easy access to the fields of the record.
|
929
1013
|
def method_missing(method_symbol, *arguments)
|
930
1014
|
synchronize do
|
931
|
-
|
932
|
-
if
|
933
|
-
|
1015
|
+
attr = method_symbol.to_s
|
1016
|
+
if attr[-1, 1] == '='
|
1017
|
+
attr = attr[0, attr.length-1]
|
934
1018
|
mode = :write
|
935
1019
|
value = arguments.pop
|
936
1020
|
else
|
937
1021
|
mode = :read
|
938
1022
|
end
|
939
|
-
reader = self.class.reader(
|
940
|
-
loader = self.class.loader(
|
941
|
-
table_column_existent = self.class.columns.include?(
|
1023
|
+
reader = self.class.reader(attr)
|
1024
|
+
loader = self.class.loader(attr)
|
1025
|
+
table_column_existent = self.class.columns.include?(attr)
|
942
1026
|
if mode == :write
|
943
|
-
data_hash_key
|
944
|
-
setter = self.class.setter(
|
1027
|
+
data_hash_key = [attr] + arguments
|
1028
|
+
setter = self.class.setter(attr)
|
945
1029
|
if setter
|
946
1030
|
setter.call(self, value)
|
947
1031
|
return nil
|
@@ -950,8 +1034,8 @@ module FlexiRecord
|
|
950
1034
|
return nil
|
951
1035
|
end
|
952
1036
|
elsif mode == :read
|
953
|
-
self.class.prepare_read_parameters(
|
954
|
-
data_hash_key = [
|
1037
|
+
self.class.prepare_read_parameters(attr, arguments)
|
1038
|
+
data_hash_key = [attr] + arguments
|
955
1039
|
if reader
|
956
1040
|
return reader.call(self, arguments)
|
957
1041
|
elsif @data_hash.has_key?(data_hash_key)
|
@@ -1079,7 +1163,7 @@ module FlexiRecord
|
|
1079
1163
|
|
1080
1164
|
def initialize(*arguments)
|
1081
1165
|
super
|
1082
|
-
self['void'] = nil
|
1166
|
+
self['void'] = nil unless self.has_key?('void')
|
1083
1167
|
end
|
1084
1168
|
|
1085
1169
|
# Alias for the (field) method 'void'. True, if the Relationship is void, and is to be removed, when calling 'save'.
|
@@ -1091,7 +1175,7 @@ module FlexiRecord
|
|
1091
1175
|
def save
|
1092
1176
|
# TODO: improve efficiency, when a "REPLACE" command is available in PostgreSQL
|
1093
1177
|
self.class.transaction(self, :read_committed) do
|
1094
|
-
self.class.
|
1178
|
+
self.class.lock(:share_row_exclusive)
|
1095
1179
|
@saved =
|
1096
1180
|
self.class.select_by_value_set(self.class.primary_columns, [self.class.primary_columns.collect { |column| self.read(column) }]).length > 0
|
1097
1181
|
copy_primary_key
|
@@ -1187,7 +1271,7 @@ module FlexiRecord
|
|
1187
1271
|
}
|
1188
1272
|
raise ArgumentError, "Too many arguments supplied for SQL command." unless command_arguments.empty?
|
1189
1273
|
if $flexirecord_debug_output
|
1190
|
-
$flexirecord_debug_output << "
|
1274
|
+
$flexirecord_debug_output << "#{command}\n"
|
1191
1275
|
end
|
1192
1276
|
begin
|
1193
1277
|
synchronize do
|
@@ -1399,8 +1483,8 @@ module FlexiRecord
|
|
1399
1483
|
|
1400
1484
|
def save
|
1401
1485
|
self.class.transaction(self, :read_committed) do
|
1402
|
-
self.class.
|
1403
|
-
|
1486
|
+
self.class.lock(:share_row_exclusive)
|
1487
|
+
if self.position == :first
|
1404
1488
|
db_result = self.class.db_query1(
|
1405
1489
|
'SELECT COALESCE("position" - 1, 0) AS "result" ' <<
|
1406
1490
|
'FROM ' << self.class.table << ' WHERE "position" NOTNULL ORDER BY "position" ASC)')
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: flexirecord
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2007-02-
|
6
|
+
version: 0.0.4
|
7
|
+
date: 2007-02-11 00:00:00 +00:00
|
8
8
|
summary: Object-Oriented Database Access Library
|
9
9
|
require_paths:
|
10
10
|
- lib/
|
@@ -30,6 +30,7 @@ authors:
|
|
30
30
|
- Jan Behrens
|
31
31
|
files:
|
32
32
|
- ./LICENSE
|
33
|
+
- ./CHANGELOG
|
33
34
|
- lib/thread_resource_pool.rb
|
34
35
|
- lib/flexirecord.rb
|
35
36
|
- lib/flexirecord-demo.rb
|