sdb_dal 0.0.1

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