sdb_dal 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.
@@ -0,0 +1,642 @@
1
+ require "active_support"
2
+ require 'uuid'
3
+ require File.dirname(__FILE__) +'/domain_attribute_description.rb'
4
+ require File.dirname(__FILE__) +"/reference.rb"
5
+ require File.dirname(__FILE__) +"/index_description.rb"
6
+ require File.dirname(__FILE__) +"/lazy_loading_text.rb"
7
+ module SdbDal
8
+ class DomainObject
9
+
10
+ @@repository =nil
11
+ @@items_to_commit=nil
12
+ @@transaction_depth=0
13
+ attr_accessor :sdb_key
14
+ def initialize(options={})
15
+ @attribute_values=HashWithIndifferentAccess.new
16
+ self.class.attribute_descriptions.each do |attribute_name,description|
17
+ if description.is_primary_key && options.has_key?(:primary_key)
18
+ @attribute_values[attribute_name]=options[:primary_key]
19
+ else
20
+ value=options[attribute_name]
21
+ value=value || options[attribute_name.intern]
22
+ if !description.is_collection && value.respond_to?(:flatten) && value.length==1
23
+ value=value[0]
24
+ end
25
+ @attribute_values[attribute_name]=value
26
+ end
27
+ end
28
+ @accessor_cache={}
29
+ @repository=options[:repository]
30
+ end
31
+ def self.primary_key_attribute_name
32
+ return @primary_key_attribute_name
33
+ end
34
+ def id
35
+ return @attribute_values[:id] if self.class.attribute_descriptions[:id]
36
+ return base.id
37
+ end
38
+ def self.index_names
39
+ []
40
+ end
41
+ def self.index(name,columns,options={})
42
+ class_eval <<-GETTERDONE
43
+ @@index_names||=[]
44
+ @@index_descriptions||={}
45
+ @@index_names<<:#{name}
46
+ def self.index_names
47
+ @@index_names
48
+ end
49
+
50
+ attribute_description=SdbDal::DomainAttributeDescription.new(name,:string,{})
51
+ @@index_descriptions[name]=SdbDal::IndexDescription.new(name,columns)
52
+
53
+ GETTERDONE
54
+
55
+ getter_params=[]
56
+ finder_code=""
57
+
58
+ params=[]
59
+ columns.each do |column|
60
+ if column.respond_to?(:transform)
61
+ finder_code<<"h[:#{column.name}]=#{column.name.to_s.downcase}\n"
62
+ getter_params<<"@attribute_values[:#{column.source_column}]"
63
+
64
+ params<<"#{column.name.to_s.downcase}"
65
+ else
66
+ finder_code<<"h[:#{column}]=#{column.to_s.downcase}\n"
67
+ getter_params<<"@attribute_values[:#{column}]"
68
+ params<<"#{column.to_s.downcase}"
69
+
70
+ end
71
+
72
+ end
73
+ find_scope=":all"
74
+ if options[:unique]
75
+ find_scope=":first"
76
+ end
77
+ class_eval <<-GETTERDONE
78
+ def #{name}
79
+
80
+ return self.class.calculate_#{name}(#{getter_params.join(",")})
81
+
82
+ end
83
+ def self.calculate_#{name}(#{params.join(",")})
84
+ index_description=index_descriptions[:#{name}]
85
+ h={}
86
+ #{finder_code}
87
+ index_description.format_index_entry(@@attribute_descriptions,h)
88
+ end
89
+ def self.find_by_#{name}(#{params.join(",")},options={})
90
+ options[:params]={:#{name}=>self.calculate_#{name}(#{params.join(",")})}
91
+ find(#{find_scope},options)
92
+ end
93
+ GETTERDONE
94
+ end
95
+ def self.data_attribute(name,type,options={})
96
+ class_eval <<-GETTERDONE
97
+ @@attribute_descriptions||=HashWithIndifferentAccess.new
98
+
99
+ @@non_clob_attribute_names||=[]
100
+ @@clob_attribute_names||=[]
101
+ @@on_destroy_blocks||=[]
102
+ def self.on_destroy_blocks
103
+ @@on_destroy_blocks
104
+ end
105
+ def self.attribute_descriptions
106
+ @@attribute_descriptions
107
+ end
108
+ def self.index_descriptions
109
+ @@index_descriptions
110
+ end
111
+ def self.non_clob_attribute_names
112
+ @@non_clob_attribute_names
113
+ end
114
+ def self.clob_attribute_names
115
+ @@clob_attribute_names
116
+ end
117
+ def is_dirty(attribute_name)
118
+ if @attribute_values[attribute_name]!= nil &&
119
+ @attribute_values[attribute_name].respond_to?(:value)
120
+ return @attribute_values[attribute_name].is_dirty
121
+ else
122
+ return true
123
+ end
124
+ end
125
+ GETTERDONE
126
+
127
+
128
+ attribute_description=DomainAttributeDescription.new(name,type,options)
129
+ self.attribute_descriptions[name] = attribute_description
130
+ if attribute_description.is_primary_key
131
+ class_eval <<-GETTERDONE
132
+ def self.primary_key_attribute_name
133
+ '#{name}'
134
+ end
135
+ def self.exists?(primary_key)
136
+ return self.find(primary_key)!=nil
137
+ end
138
+
139
+ GETTERDONE
140
+ end
141
+ if !attribute_description.is_primary_key
142
+ if attribute_description.value_type==:clob
143
+ clob_attribute_names<<attribute_description.name
144
+ else
145
+ non_clob_attribute_names<<attribute_description.name
146
+ end
147
+ end
148
+ scope=":all"
149
+ if(options[:unique] && options[:unique]==true)
150
+ scope=":first"
151
+ end
152
+ if type==:reference_set
153
+
154
+
155
+ class_eval <<-GETTERDONE
156
+ def #{attribute_description.name}
157
+ result=@attribute_values[:#{attribute_description.name}] || []
158
+ result=result.sort_by{|item|item.index}
159
+
160
+ end
161
+ def add_to_#{attribute_description.name}(item,index=-1)
162
+ @attribute_values[:#{attribute_description.name}] ||=[]
163
+ @attribute_values[:#{attribute_description.name}] <<SdbDal::Reference.new(:target=> item ,:index=>index)
164
+ end
165
+ def remove_from_#{attribute_description.name}(item)
166
+ @attribute_values[:#{attribute_description.name}] ||=[]
167
+ @attribute_values.each do |reference|
168
+ if reference.targets?(item)
169
+ @attribute_values.remove(reference)
170
+ return
171
+ end
172
+ end
173
+ end
174
+
175
+ GETTERDONE
176
+
177
+ elsif type==:clob
178
+ class_eval <<-GETTERDONE
179
+
180
+ def #{attribute_description.name}
181
+ if @attribute_values[:#{attribute_description.name}]!= nil &&
182
+ @attribute_values[:#{attribute_description.name}].respond_to?(:value)
183
+ return @attribute_values[:#{attribute_description.name}].value
184
+ else
185
+ return @attribute_values[:#{attribute_description.name}]
186
+ end
187
+ end
188
+
189
+ GETTERDONE
190
+ else
191
+
192
+ class_eval <<-GETTERDONE
193
+ def #{attribute_description.name}
194
+ return @attribute_values[:#{attribute_description.name}]
195
+ end
196
+
197
+ GETTERDONE
198
+ end
199
+ class_eval <<-GETTERDONE
200
+
201
+ def #{attribute_description.name}=(xvalue)
202
+ @attribute_values[:#{attribute_description.name}] =xvalue
203
+ end
204
+ def #{attribute_description.name}?
205
+ return @attribute_values[:#{attribute_description.name}]
206
+ end
207
+
208
+ def self.find_by_#{attribute_description.name}(#{attribute_description.name.to_s.downcase},options={})
209
+ options[:params]={:#{attribute_description.name}=>#{attribute_description.name.to_s.downcase}}
210
+ find(#{scope},options)
211
+ end
212
+ GETTERDONE
213
+ end
214
+
215
+ def self.belongs_to(domain_class,foreign_key_attribute=nil,accesser_attribute_name=nil,options={})
216
+
217
+ foreign_key_attribute ||= "#{domain_class.to_s.downcase}_id".to_sym
218
+ accesser_attribute_name ||=domain_class.to_s.downcase
219
+
220
+ class_eval <<-GETTERDONE
221
+ def #{accesser_attribute_name}
222
+ #{domain_class}.find(self.#{foreign_key_attribute})
223
+ end
224
+
225
+ GETTERDONE
226
+ end
227
+ def self.many_to_many(domain_class,
228
+ through_class,
229
+ reflecting_key_attribute=nil,
230
+ foriegn_key_attribute=nil,
231
+ accesser_attribute_name=nil,
232
+ options={})
233
+ reflecting_key_attribute ||= (self.name+"_"+primary_key_attribute_name.to_s).downcase.to_sym
234
+ accesser_attribute_name ||=domain_class.to_s.downcase+'s'
235
+ order=""
236
+ if options[:order_by]
237
+ if options[:order]
238
+ order="result=DomainObject.sort_result(result,:#{options[:order_by]},:#{options[:order]})"
239
+ else
240
+ order="result=DomainObject.sort_result(result,:#{options[:order_by]},:ascending)"
241
+ end
242
+ end
243
+
244
+
245
+ foriegn_key_attribute=options[:foriegn_key_attribute] || "#{domain_class.to_s.downcase}_id".to_sym
246
+ primary_key=options[:primary_key] || "id".to_sym
247
+ class_eval <<-XXDONE
248
+ def #{accesser_attribute_name}
249
+ #unless @accessor_cache.has_key? :#{accesser_attribute_name}
250
+ through_results= #{through_class}.find(:all,:params=>{:#{reflecting_key_attribute}=>self.primary_key})
251
+ result=[]
252
+ through_results.each do |through_result|
253
+ item= #{domain_class}.find(through_result.#{foriegn_key_attribute})
254
+ result<< item if item
255
+ end
256
+ #{order}
257
+
258
+ # @accessor_cache[:#{accesser_attribute_name}]=result
259
+ return result
260
+ #end
261
+ #return @accessor_cache[:#{accesser_attribute_name}]
262
+
263
+ end
264
+ def connect_#{domain_class.to_s.downcase}(#{domain_class.to_s.downcase})
265
+ connector=#{through_class}.new
266
+ connector.#{foriegn_key_attribute}=#{domain_class.to_s.downcase}.primary_key
267
+ connector.#{reflecting_key_attribute}=self.primary_key
268
+ connector.save
269
+ end
270
+ XXDONE
271
+
272
+
273
+ end
274
+ def DomainObject.transaction
275
+ @@items_to_commit||=[]
276
+ @@transaction_depth+=1
277
+ begin
278
+ yield
279
+ rescue # catch all
280
+ raise $! # rethrow
281
+ ensure
282
+ @@transaction_depth-=1
283
+
284
+ end
285
+
286
+
287
+ if @@transaction_depth==0
288
+
289
+ @@items_to_commit.uniq.each do |item |
290
+ item.save!
291
+ end
292
+ @@items_to_commit=[]
293
+ end
294
+
295
+ end
296
+ def DomainObject.sort_result(results,sort_by,order=:ascending)
297
+ non_null_results=[]
298
+ null_results=[]
299
+ results.each { |i|
300
+ sorter_value=i.get_attribute(sort_by)
301
+ if sorter_value
302
+ non_null_results<<i
303
+ else
304
+ null_results<<i
305
+ end
306
+ }
307
+ sorted_results=non_null_results.sort{ |a,b|
308
+ a_val= a.get_sortable_attribute(sort_by)
309
+ b_val= b.get_sortable_attribute(sort_by)
310
+
311
+ a_val <=> b_val
312
+ }
313
+
314
+ if order!=:ascending
315
+ sorted_results.reverse!
316
+ end
317
+ sorted_results.concat( null_results)
318
+ sorted_results
319
+ end
320
+ def get_sortable_attribute(attr_name)
321
+ a_val= self.get_attribute(attr_name)
322
+ return 0 unless a_val
323
+ if a_val.class== Time
324
+ return a_val.to_f
325
+ else
326
+ return a_val
327
+ end
328
+ end
329
+ def self.has_many(domain_class,
330
+ reflecting_key_attribute=nil,
331
+ accesser_attribute_name=nil,
332
+ options={})
333
+
334
+ reflecting_key_attribute ||= (self.name+"_"+primary_key_attribute_name.to_s).downcase.to_sym
335
+ accesser_attribute_name ||=domain_class.to_s.downcase+'s'
336
+ order=""
337
+ if options[:order_by]
338
+ order=",:order_by=>:#{options[:order_by]}"
339
+ end
340
+ if options[:order]
341
+ order<<",:order=>:#{options[:order]}"
342
+ end
343
+
344
+ if options[:dependent]
345
+
346
+ class_eval <<-XXDONE
347
+
348
+ on_destroy_blocks<<"#{accesser_attribute_name}.each{|item|item.destroy}"
349
+ XXDONE
350
+ end
351
+ # if options[:tracking]
352
+ # #add add_xxx method
353
+ # #add to list of tracker attributes
354
+ #
355
+ # class_eval <<-XXDONE
356
+ #
357
+ # @@attribute_descriptions[ :#{accesser_attribute_name}_tracker]=TrackerDescription.new(:#{accesser_attribute_name}_tracker,:#{domain_class},:#{reflecting_key_attribute})
358
+ # def #{accesser_attribute_name}
359
+ # find_tracked_list(:#{accesser_attribute_name}_tracker,#{domain_class},:#{reflecting_key_attribute})
360
+ # end
361
+ # def add_to_#{accesser_attribute_name}(item)
362
+ # item.save!
363
+ # add_to_tracked_list(:#{accesser_attribute_name}_tracker,#{domain_class.to_s},:#{reflecting_key_attribute},item.primary_key)
364
+ # item.set_attribute(:#{reflecting_key_attribute},self.primary_key)
365
+ #
366
+ #
367
+ # end
368
+ # XXDONE
369
+ # else
370
+ class_eval <<-XXDONE
371
+ def #{accesser_attribute_name}
372
+ if !@accessor_cache.has_key? :#{accesser_attribute_name}
373
+ @accessor_cache[:#{accesser_attribute_name}]=#{domain_class}.find(:all,:params=>{:#{reflecting_key_attribute}=>self.primary_key}#{order})
374
+ end
375
+ return @accessor_cache[:#{accesser_attribute_name}]
376
+ end
377
+ XXDONE
378
+ # end
379
+ end
380
+ def self.AttributeDescription(name)
381
+ return attribute_descriptions[name]
382
+ end
383
+ def self.ColumnDescription(name)
384
+ return column_descriptions[name]
385
+ end
386
+ def primary_key
387
+
388
+ @attribute_values[ self.class.primary_key_attribute_name]
389
+ end
390
+ def primary_key=(value)
391
+ @attribute_values[ self.class.primary_key_attribute_name]=value
392
+ end
393
+ def method_missing(method_symbol, *arguments) #:nodoc:
394
+ method_name = method_symbol.to_s
395
+
396
+ case method_name.last
397
+ when "="
398
+ if @attribute_values.has_key?(method_name.first(-1))
399
+ @attribute_values[method_name.first(-1)] = arguments.first
400
+ else
401
+ super
402
+ end
403
+ when "?"
404
+ @attribute_values[method_name.first(-1)]
405
+ else
406
+ @attribute_values.has_key?(method_name) ? @attribute_values[method_name] : super
407
+ end
408
+ end
409
+ def index_values
410
+ result={}
411
+ self.class.index_names.each do |name|
412
+
413
+ result[name]= self.send("#{name}")
414
+
415
+ end
416
+ result
417
+ end
418
+ def set_attribute(name,value)
419
+ @attribute_values[name]=value
420
+ end
421
+ def get_attribute(name)
422
+ @attribute_values[name]
423
+ end
424
+ def to_xml
425
+ result= "<#{self.class.table_name}>"
426
+ attributes.each do |key,value|
427
+ if value.respond_to?(:flatten)
428
+ result<<"<#{key}>"
429
+ value.each do |sub_value|
430
+ result<<"<value>#{h sub_value.to_s}</value>"
431
+ end
432
+ result<<"</#{key}>"
433
+ else
434
+ result<<"<#{key}>#{h value.to_s}</#{key}>"
435
+ end
436
+ end
437
+ result<< "</#{self.class.table_name}>"
438
+ result
439
+ end
440
+ def destroy(options={})
441
+ self.class.on_destroy_blocks.each do |block|
442
+ instance_eval <<-XXDONE
443
+ #{block}
444
+
445
+ XXDONE
446
+
447
+ end
448
+ if self.primary_key
449
+ self.repository(options).destroy(self.table_name,self.primary_key)
450
+ end
451
+ end
452
+ def attributes
453
+ result={}
454
+ #todo refactor to avoid this copy
455
+ self.class.attribute_descriptions.values.each do |description|
456
+ if !description.is_clob || is_dirty(description.name)
457
+ result[description.name]=@attribute_values[description.name]
458
+ end
459
+ end
460
+ return result
461
+ end
462
+
463
+ def find_tracked_list(tracking_attribute,other_class,reflecting_attribute)
464
+ #if the attribute has no value do a query
465
+ list=@attribute_values[tracking_attribute]
466
+ if !list
467
+ list=other_class.query_ids(:params=>{reflecting_attribute=>self.primary_key})
468
+ @attribute_values[tracking_attribute]=list
469
+ self.save!
470
+ end
471
+
472
+ return other_class.find_list(list)
473
+
474
+ end
475
+
476
+ def add_to_tracked_list(tracking_attribute,other_class,reflecting_attribute,value)
477
+ list=@attribute_values[tracking_attribute]
478
+ if !list
479
+ list=other_class.query_ids(:params=>{reflecting_attribute=>self.primary_key})
480
+
481
+ end
482
+ list<<value
483
+ @attribute_values[tracking_attribute]=list.uniq
484
+
485
+ end
486
+ def save!(options={})
487
+ save(options)
488
+ end
489
+ def save(options={})
490
+ if !self.primary_key
491
+ self.primary_key= UUID.generate.to_s
492
+ end
493
+
494
+ if @@transaction_depth>0
495
+ #we are in a transaction. wait to commit
496
+ @@items_to_commit<<self
497
+
498
+ else
499
+ temp_accessor_cache=@accessor_cache
500
+ @accessor_cache=nil
501
+
502
+ # $service.put_attributes(class_object.table_name,item.send(name_column),attributes,true)
503
+ attributes={}
504
+ #todo refactor to avoid this copy
505
+ self.class.attribute_descriptions.values.each do |description|
506
+ if !description.is_clob || is_dirty(description.name)
507
+ value=@attribute_values[description.name]
508
+ attributes[description]=value
509
+ end
510
+
511
+
512
+
513
+ end
514
+ self.index_values.each do |name,value|
515
+
516
+ attributes[self.class.index_descriptions[name]]=value
517
+ end
518
+
519
+ self.repository(options).save(self.table_name,self.primary_key,attributes)
520
+ @accessor_cache=temp_accessor_cache
521
+ end
522
+
523
+ end
524
+ def table_name
525
+ return self.class.table_name
526
+ end
527
+ def DomainObject.table_name
528
+ return self.name
529
+ end
530
+ # def find
531
+ #
532
+ # attributes=repository.find(self.class.name,self.primary_key,@@non_clob_attribute_names,@@clob_attribute_names)
533
+ # attributes.each do |key,value|
534
+ # @attribute_values[key]=value
535
+ # end
536
+ # end
537
+ def repository(options=nil)
538
+
539
+ if options and options.has_key?(:repository)
540
+ return options[:repository]
541
+ end
542
+ if @repository
543
+ return @repository
544
+ end
545
+ if @@repository
546
+ return @@repository
547
+ end
548
+ return $repository
549
+ end
550
+ class << self
551
+ def repository(options=nil)
552
+
553
+ if options and options.has_key?(:repository)
554
+ return options[:repository]
555
+ end
556
+ if @repository
557
+ return @repository
558
+ end
559
+ return $repository
560
+ end
561
+ def find(*arguments)
562
+ scope = arguments.slice!(0)
563
+ options = arguments.slice!(0) || {}
564
+
565
+ case scope
566
+ when :all then return find_every(options)
567
+ when :first then return find_one(options)
568
+ else return find_single(scope, options)
569
+ end
570
+ end
571
+ def find_one(options)
572
+ options[:limit]=1
573
+ find_every(options).first
574
+ end
575
+ def find_every(options)
576
+
577
+ results=[]
578
+ untyped_results=self.repository.query(self.table_name,attribute_descriptions,options)
579
+ untyped_results.each do |item_attributes|
580
+
581
+ results<<istantiate(item_attributes,self.repository(options))
582
+ end
583
+ return results
584
+
585
+ end
586
+ def find_list(primary_keys,options={})
587
+ result=[]
588
+ primary_keys.each do |key|
589
+ item=find_single(key,options)
590
+ result<<item if item
591
+ end
592
+ return result
593
+ end
594
+ def query_ids(options={})
595
+ self.repository.query_ids(self.table_name,attribute_descriptions,options)
596
+ end
597
+ def istantiate(sdb_attributes,repository)
598
+ this_result=new(sdb_attributes||{})
599
+ get_clob_proc=nil
600
+ clob_attribute_names.each do |clob_attribute_name|
601
+ get_clob_proc= proc {
602
+ repository.get_clob(self.table_name,this_result.primary_key,clob_attribute_name)
603
+ }
604
+
605
+ this_result.set_attribute(clob_attribute_name, LazyLoadingText.new(get_clob_proc))
606
+ end
607
+ this_result
608
+ end
609
+
610
+ def find_single(id, options)
611
+ return nil if id==nil
612
+ attributes=self.repository(options).find_one(self.table_name,id,attribute_descriptions)
613
+ if attributes && attributes.length>0
614
+ attributes[@primary_key_attribute_name]=id
615
+ attributes[:repository]=self.repository(options)
616
+ return istantiate(attributes,self.repository(options))
617
+ end
618
+ return nil
619
+ end
620
+ def find_recent_with_exponential_expansion(time_column,how_many,options={})
621
+ found=[]
622
+ tries=0
623
+ timespan=options[:timespan_hint] || 60*60*4
624
+
625
+ params = options[:params] || {}
626
+
627
+ while found.length<how_many && tries<4
628
+ time=Time.now.gmtime-timespan
629
+ params[time_column] =AttributeRange.new(:greater_than=>time)
630
+ found= find(:all,:limit=>how_many,:order=>:descending,:order_by => time_column,
631
+ :params=>params)
632
+
633
+ tries=tries+1
634
+ timespan=timespan*3
635
+ end
636
+ return found
637
+
638
+ end
639
+
640
+ end
641
+ end
642
+ end
@@ -0,0 +1,14 @@
1
+ module SdbDal
2
+
3
+
4
+ class DomainObjectCacheItem
5
+ attr_accessor :table_name
6
+ attr_accessor :primary_key
7
+ attr_accessor :non_clob_attributes
8
+ def initialize(table_name,primary_key,non_clob_attributes)
9
+ self.table_name=table_name
10
+ self.primary_key=primary_key
11
+ self.non_clob_attributes=non_clob_attributes
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,18 @@
1
+ module SdbDal
2
+
3
+
4
+ class EqualsCondition
5
+ attr_accessor :attribute_description
6
+ attr_accessor :value
7
+ def initialize(attribute_description,value)
8
+ self.attribute_description=attribute_description
9
+ self.value=value
10
+ end
11
+ def matches?(domain_object)
12
+ return domain_object[attribute_description.name]==value
13
+ end
14
+ def to_sdb_query
15
+ return "'#{self.attribute_description.name}'='#{self.attribute_description.format_for_sdb(self.value)}'"
16
+ end
17
+ end
18
+ end