simple_record 1.1.34 → 1.1.35

Sign up to get free protection for your applications and to get access to all the features.
data/lib/callbacks.rb CHANGED
@@ -4,7 +4,7 @@ module SimpleRecord::Callbacks
4
4
  #basically, this is how we recreate the callback functions
5
5
  @@callbacks=["before_validation", "before_validation_on_create", "before_validation_on_update",
6
6
  "after_validation", "after_validation_on_create", "after_validation_on_update",
7
- "before_save", "before_create", "before_update",
7
+ "before_save", "before_create", "before_update", "before_destroy",
8
8
  "after_create", "after_update", "after_save",
9
9
  "after_destroy"]
10
10
 
@@ -13,12 +13,6 @@ module SimpleRecord::Callbacks
13
13
 
14
14
  end
15
15
 
16
- def before_delete()
17
- end
18
-
19
- def after_delete()
20
- end
21
-
22
16
  def before_destroy()
23
17
  end
24
18
 
data/lib/simple_record.rb CHANGED
@@ -1,1155 +1,1165 @@
1
- # Usage:
2
- # require 'simple_record'
3
- #
4
- # class MyModel < SimpleRecord::Base
5
- #
6
- # has_attributes :name, :age
7
- # are_ints :age
8
- #
9
- # end
10
- #
11
- # AWS_ACCESS_KEY_ID='XXXXX'
12
- # AWS_SECRET_ACCESS_KEY='YYYYY'
13
- # SimpleRecord.establish_connection(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY)
14
- #
15
- ## Save an object
16
- # mm = MyModel.new
17
- # mm.name = "Travis"
18
- # mm.age = 32
19
- # mm.save
20
- # id = mm.id
21
- # # Get the object back
22
- # mm2 = MyModel.select(id)
23
- # puts 'got=' + mm2.name + ' and he/she is ' + mm.age.to_s + ' years old'
24
-
25
-
26
- require 'aws'
27
- require 'sdb/active_sdb'
28
- #require 'results_array' # why the heck isn't this picking up???
29
- require File.expand_path(File.dirname(__FILE__) + "/results_array")
30
- require File.expand_path(File.dirname(__FILE__) + "/stats")
31
- require File.expand_path(File.dirname(__FILE__) + "/callbacks")
32
-
33
- module SimpleRecord
34
-
35
- @@stats = SimpleRecord::Stats.new
36
-
37
- def self.stats
38
- @@stats
39
- end
40
-
41
- # Create a new handle to an Sdb account. All handles share the same per process or per thread
42
- # HTTP connection to Amazon Sdb. Each handle is for a specific account.
43
- # The +params+ are passed through as-is to Aws::SdbInterface.new
44
- # Params:
45
- # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
46
- # :port => 443 # Amazon service port: 80(default) or 443
47
- # :protocol => 'https' # Amazon service protocol: 'http'(default) or 'https'
48
- # :signature_version => '0' # The signature version : '0' or '1'(default)
49
- # :connection_mode => :default # options are
50
- # :default (will use best known safe (as in won't need explicit close) option, may change in the future)
51
- # :per_request (opens and closes a connection on every request to SDB)
52
- # :single (one thread across entire app)
53
- # :per_thread (one connection per thread)
54
- # :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET)
55
- # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
56
- def self.establish_connection(aws_access_key=nil, aws_secret_key=nil, params={})
57
- Aws::ActiveSdb.establish_connection(aws_access_key, aws_secret_key, params)
58
- end
59
-
60
- def self.close_connection()
61
- Aws::ActiveSdb.close_connection
62
- end
63
-
64
- class Base < Aws::ActiveSdb::Base
65
-
66
- include SimpleRecord::Callbacks
67
-
68
-
69
- def initialize(attrs={})
70
- # todo: Need to deal with objects passed in. iterate through belongs_to perhaps and if in attrs, set the objects id rather than the object itself
71
-
72
- #we have to handle the virtuals.
73
- @@virtuals.each do |virtual|
74
- #we first copy the information for the virtual to an instance variable of the same name
75
- eval("@#{virtual}=attrs['#{virtual}']")
76
- #and then remove the parameter before it is passed to initialize, so that it is NOT sent to SimpleDB
77
- eval("attrs.delete('#{virtual}')")
78
- end
79
- super
80
- @errors=SimpleRecord_errors.new
81
- @dirty = {}
82
- end
83
-
84
-
85
- # todo: move into Callbacks module
86
- #this bit of code creates a "run_blank" function for everything value in the @@callbacks array.
87
- #this function can then be inserted in the appropriate place in the save, new, destroy, etc overrides
88
- #basically, this is how we recreate the callback functions
89
- @@callbacks.each do |callback|
90
- instance_eval <<-endofeval
91
-
92
- #puts 'doing callback=' + callback + ' for ' + self.inspect
93
- #we first have to make an initialized array for each of the callbacks, to prevent problems if they are not called
94
-
95
- def #{callback}(*args)
96
- #puts 'callback called in ' + self.inspect + ' with ' + args.inspect
97
-
98
- #make_dirty(arg_s, value)
99
- #self[arg.to_s]=value
100
- #puts 'value in callback #{callback}=' + value.to_s
101
- args.each do |arg|
102
- cnames = callbacks['#{callback}']
103
- #puts '\tcnames1=' + cnames.inspect + ' for class ' + self.inspect
104
- cnames = [] if cnames.nil?
105
- cnames << arg.to_s if cnames.index(arg.to_s).nil?
106
- #puts '\tcnames2=' + cnames.inspect
107
- callbacks['#{callback}'] = cnames
108
- end
109
- end
110
-
111
- endofeval
112
- end
113
- #puts 'base methods=' + self.methods.inspect
114
-
115
-
116
- def self.inherited(base)
117
- #puts 'SimpleRecord::Base is inherited by ' + base.inspect
118
- setup_callbacks(base)
119
-
120
- base.has_dates :created, :updated
121
- base.before_create :set_created, :set_updated
122
- base.before_update :set_updated
123
-
124
- end
125
-
126
- def self.setup_callbacks(base)
127
- instance_eval <<-endofeval
128
-
129
- def callbacks
130
- @callbacks ||= {}
131
- @callbacks
132
- end
133
-
134
- def self.defined_attributes
135
- #puts 'class defined_attributes'
136
- @attributes ||= {}
137
- @attributes
138
- end
139
-
140
- endofeval
141
-
142
- @@callbacks.each do |callback|
143
- class_eval <<-endofeval
144
-
145
- def run_#{callback}
146
- # puts 'CLASS CALLBACKS for ' + self.inspect + ' = ' + self.class.callbacks.inspect
147
- return true if self.class.callbacks.nil?
148
- cnames = self.class.callbacks['#{callback}']
149
- cnames = [] if cnames.nil?
150
- #cnames += super.class.callbacks['#{callback}'] unless super.class.callbacks.nil?
151
- # puts 'cnames for #{callback} = ' + cnames.inspect
152
- return true if cnames.nil?
153
- cnames.each { |name|
154
- #puts 'run_ #{name}'
155
- if eval(name) == false # nil should be an ok return, only looking for false
156
- return false
157
- end
158
- }
159
- #super.run_#{callback}
160
- return true
161
- end
162
-
163
- endofeval
164
- end
165
- end
166
-
167
-
168
- # Holds information about an attribute
169
- class Attribute
170
- attr_accessor :type, :options
171
-
172
- def initialize(type)
173
- @type = type
174
- end
175
-
176
- end
177
-
178
-
179
- def defined_attributes_local
180
- #puts 'local defined_attributes'
181
- ret = self.class.defined_attributes
182
- ret.merge!(self.class.superclass.defined_attributes) if self.class.superclass.respond_to?(:defined_attributes)
183
- end
184
-
185
-
186
- attr_accessor :errors
187
-
188
- @domain_prefix = ''
189
- class << self;
190
- attr_accessor :domain_prefix;
191
- end
192
-
193
- #@domain_name_for_class = nil
194
-
195
- @@cache_store = nil
196
- # Set the cache to use
197
- def self.cache_store=(cache)
198
- @@cache_store = cache
199
- end
200
-
201
- def self.cache_store
202
- return @@cache_store
203
- end
204
-
205
- # If you want a domain prefix for all your models, set it here.
206
- def self.set_domain_prefix(prefix)
207
- #puts 'set_domain_prefix=' + prefix
208
- self.domain_prefix = prefix
209
- end
210
-
211
- # Same as set_table_name
212
- def self.set_table_name(table_name)
213
- set_domain_name table_name
214
- end
215
-
216
- # Sets the domain name for this class
217
- def self.set_domain_name(table_name)
218
- # puts 'setting domain name for class ' + self.inspect + '=' + table_name
219
- #@domain_name_for_class = table_name
220
- super
221
- end
222
-
223
- =begin
224
- def self.get_domain_name
225
- # puts 'returning domain_name=' + @domain_name_for_class.to_s
226
- #return @domain_name_for_class
227
- return self.domain
228
- end
229
-
230
- =end
231
-
232
- def domain
233
- super # super.domain
234
- end
235
-
236
- def self.domain
237
- #return self.get_domain_name unless self.get_domain_name.nil?
238
- d = super
239
- #puts 'in self.domain, d=' + d.to_s + ' domain_prefix=' + SimpleRecord::Base.domain_prefix.to_s
240
- domain_name_for_class = SimpleRecord::Base.domain_prefix + d.to_s
241
- #self.set_domain_name(domain_name_for_class)
242
- domain_name_for_class
243
- end
244
-
245
-
246
- # Since SimpleDB supports multiple attributes per value, the values are an array.
247
- # This method will return the value unwrapped if it's the only, otherwise it will return the array.
248
- def get_attribute(arg)
249
- arg = arg.to_s
250
- if self[arg].class==Array
251
- if self[arg].length==1
252
- ret = self[arg][0]
253
- else
254
- ret = self[arg]
255
- end
256
- else
257
- ret = self[arg]
258
- end
259
- ret
260
- end
261
-
262
- def make_dirty(arg, value)
263
- # todo: only set dirty if it changed
264
- #puts 'making dirty arg=' + arg.to_s + ' --- ' + @dirty.inspect
265
- @dirty[arg] = get_attribute(arg) # Store old value (not sure if we need it?)
266
- #puts 'end making dirty ' + @dirty.inspect
267
- end
268
-
269
- def self.has_attributes(*args)
270
- args.each do |arg|
271
- defined_attributes[arg] = SimpleRecord::Base::Attribute.new(:string) if defined_attributes[arg].nil?
272
- # define reader method
273
- arg_s = arg.to_s # to get rid of all the to_s calls
274
- send(:define_method, arg) do
275
- ret = nil
276
- ret = get_attribute(arg)
277
- return nil if ret.nil?
278
- return un_offset_if_int(arg, ret)
279
- end
280
-
281
- # define writer method
282
- send(:define_method, arg_s+"=") do |value|
283
- make_dirty(arg_s, value)
284
- self[arg_s]=value
285
- end
286
-
287
- # Now for dirty methods: http://api.rubyonrails.org/classes/ActiveRecord/Dirty.html
288
- # define changed? method
289
- send(:define_method, arg_s + "_changed?") do
290
- @dirty.has_key?(arg_s)
291
- end
292
-
293
- # define change method
294
- send(:define_method, arg_s + "_change") do
295
- old_val = @dirty[arg_s]
296
- return nil if old_val.nil?
297
- [old_val, get_attribute(arg_s)]
298
- end
299
-
300
- # define was method
301
- send(:define_method, arg_s + "_was") do
302
- old_val = @dirty[arg_s]
303
- old_val
304
- end
305
- end
306
- end
307
-
308
- def self.has_strings(*args)
309
- has_attributes(*args)
310
- end
311
-
312
- def self.has_ints(*args)
313
- has_attributes(*args)
314
- are_ints(*args)
315
- end
316
-
317
- def self.has_dates(*args)
318
- has_attributes(*args)
319
- are_dates(*args)
320
- end
321
-
322
- def self.has_booleans(*args)
323
- has_attributes(*args)
324
- are_booleans(*args)
325
- end
326
-
327
- def self.are_ints(*args)
328
- # puts 'calling are_ints: ' + args.inspect
329
- args.each do |arg|
330
- defined_attributes[arg].type = :int
331
- end
332
- end
333
-
334
- def self.are_dates(*args)
335
- args.each do |arg|
336
- defined_attributes[arg].type = :date
337
- end
338
- end
339
-
340
- def self.are_booleans(*args)
341
- args.each do |arg|
342
- defined_attributes[arg].type = :boolean
343
- end
344
- end
345
-
346
- @@virtuals=[]
347
-
348
- def self.has_virtuals(*args)
349
- @@virtuals = args
350
- args.each do |arg|
351
- #we just create the accessor functions here, the actual instance variable is created during initialize
352
- attr_accessor(arg)
353
- end
354
- end
355
-
356
- # One belongs_to association per call. Call multiple times if there are more than one.
357
- #
358
- # This method will also create an {association)_id method that will return the ID of the foreign object
359
- # without actually materializing it.
360
- def self.belongs_to(association_id, options = {})
361
- attribute = SimpleRecord::Base::Attribute.new(:belongs_to)
362
- defined_attributes[association_id] = attribute
363
- attribute.options = options
364
- #@@belongs_to_map[association_id] = options
365
- arg = association_id
366
- arg_s = arg.to_s
367
- arg_id = arg.to_s + '_id'
368
-
369
- # todo: should also handle foreign_key http://74.125.95.132/search?q=cache:KqLkxuXiBBQJ:wiki.rubyonrails.org/rails/show/belongs_to+rails+belongs_to&hl=en&ct=clnk&cd=1&gl=us
370
- # puts "arg_id=#{arg}_id"
371
- # puts "is defined? " + eval("(defined? #{arg}_id)").to_s
372
- # puts 'atts=' + @attributes.inspect
373
-
374
- # Define reader method
375
- send(:define_method, arg) do
376
- attribute = defined_attributes_local[arg]
377
- options2 = attribute.options # @@belongs_to_map[arg]
378
- class_name = options2[:class_name] || arg.to_s[0...1].capitalize + arg.to_s[1...arg.to_s.length]
379
-
380
- # Camelize classnames with underscores (ie my_model.rb --> MyModel)
381
- class_name = class_name.camelize
382
-
383
- # puts "attr=" + @attributes[arg_id].inspect
384
- # puts 'val=' + @attributes[arg_id][0].inspect unless @attributes[arg_id].nil?
385
- ret = nil
386
- arg_id = arg.to_s + '_id'
387
- if !@attributes[arg_id].nil? && @attributes[arg_id].size > 0 && @attributes[arg_id][0] != nil && @attributes[arg_id][0] != ''
388
- if !@@cache_store.nil?
389
- arg_id_val = @attributes[arg_id][0]
390
- cache_key = self.class.cache_key(class_name, arg_id_val)
391
- # puts 'cache_key=' + cache_key
392
- ret = @@cache_store.read(cache_key)
393
- # puts 'belongs_to incache=' + ret.inspect
394
- end
395
- if ret.nil?
396
- to_eval = "#{class_name}.find(@attributes['#{arg_id}'][0])"
397
- # puts 'to eval=' + to_eval
398
- begin
399
- ret = eval(to_eval) # (defined? #{arg}_id)
400
- rescue Aws::ActiveSdb::ActiveSdbError
401
- if $!.message.include? "Couldn't find"
402
- ret = nil
403
- else
404
- raise $!
405
- end
406
- end
407
-
408
- end
409
- end
410
- # puts 'ret=' + ret.inspect
411
- return ret
412
- end
413
-
414
-
415
- # Define writer method
416
- send(:define_method, arg.to_s + "=") do |value|
417
- arg_id = arg.to_s + '_id'
418
- if value.nil?
419
- make_dirty(arg_id, nil)
420
- self[arg_id]=nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
421
- else
422
- make_dirty(arg_id, value.id)
423
- self[arg_id]=value.id
424
- end
425
- end
426
-
427
-
428
- # Define ID reader method for reading the associated objects id without getting the entire object
429
- send(:define_method, arg_id) do
430
- if !@attributes[arg_id].nil? && @attributes[arg_id].size > 0 && @attributes[arg_id][0] != nil && @attributes[arg_id][0] != ''
431
- return @attributes[arg_id][0]
432
- end
433
- return nil
434
- end
435
-
436
- # Define writer method for setting the _id directly without the associated object
437
- send(:define_method, arg_id + "=") do |value|
438
- if value.nil?
439
- self[arg_id] = nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
440
- else
441
- self[arg_id] = value
442
- end
443
- end
444
-
445
- send(:define_method, "create_"+arg.to_s) do |*params|
446
- newsubrecord=eval(arg.to_s.classify).new(*params)
447
- newsubrecord.save
448
- arg_id = arg.to_s + '_id'
449
- self[arg_id]=newsubrecord.id
450
- end
451
- end
452
-
453
- def self.has_many(*args)
454
- args.each do |arg|
455
- #okay, this creates an instance method with the pluralized name of the symbol passed to belongs_to
456
- send(:define_method, arg) do
457
- #when called, the method creates a new, very temporary instance of the Activerecordtosdb_subrecord class
458
- #It is passed the three initializers it needs:
459
- #note the first parameter is just a string by time new gets it, like "user"
460
- #the second and third parameters are still a variable when new gets it, like user_id
461
- return eval(%{Activerecordtosdb_subrecord_array.new('#{arg}', self.class.name ,id)})
462
- end
463
- end
464
- #Disclaimer: this whole funciton just seems crazy to me, and a bit inefficient. But it was the clearest way I could think to do it code wise.
465
- #It's bad programming form (imo) to have a class method require something that isn't passed to it through it's variables.
466
- #I couldn't pass the id when calling find, since the original find doesn't work that way, so I was left with this.
467
- end
468
-
469
- def self.has_one(*args)
470
-
471
- end
472
-
473
- def clear_errors
474
- @errors=SimpleRecord_errors.new
475
- end
476
-
477
- def []=(attribute, values)
478
- make_dirty(attribute, values)
479
- super
480
- end
481
-
482
-
483
- def set_created
484
- # puts 'SETTING CREATED'
485
- # @created = DateTime.now
486
- self[:created] = DateTime.now
487
- # @tester = 'some test value'
488
- # self[:tester] = 'some test value'
489
- end
490
-
491
- def set_updated
492
- #puts 'SETTING UPDATED'
493
- # @updated = DateTime.now
494
- self[:updated] = DateTime.now
495
- # @tester = 'some test value updated'
496
- end
497
-
498
-
499
- @@offset = 9223372036854775808
500
- @@padding = 20
501
- @@date_format = "%Y-%m-%dT%H:%M:%S"; # Time to second precision
502
-
503
- def self.pad_and_offset(x) # Change name to something more appropriate like ruby_to_sdb
504
- # todo: add Float, etc
505
- # puts 'padding=' + x.class.name + " -- " + x.inspect
506
- if x.kind_of? Integer
507
- x += @@offset
508
- x_str = x.to_s
509
- # pad
510
- x_str = '0' + x_str while x_str.size < 20
511
- return x_str
512
- elsif x.respond_to?(:iso8601)
513
- # puts x.class.name + ' responds to iso8601'
514
- #
515
- # There is an issue here where Time.iso8601 on an incomparable value to DateTime.iso8601.
516
- # Amazon suggests: 2008-02-10T16:52:01.000-05:00
517
- # "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
518
- #
519
- if x.is_a? DateTime
520
- x_str = x.getutc.strftime(@@date_format)
521
- elsif x.is_a? Time
522
- x_str = x.getutc.strftime(@@date_format)
523
- elsif x.is_a? Date
524
- x_str = x.strftime(@@date_format)
525
-
526
- end
527
- return x_str
528
- else
529
- return x
530
- end
531
- end
532
-
533
- def domain_ok(ex)
534
- if (ex.message().index("NoSuchDomain") != nil)
535
- puts "Creating new SimpleDB Domain: " + domain
536
- self.class.create_domain
537
- return true
538
- end
539
- return false
540
- end
541
-
542
- def valid?
543
- errors.clear
544
-
545
- # run_callbacks(:validate)
546
- validate
547
-
548
- if new_record?
549
- # run_callbacks(:validate_on_create)
550
- validate_on_create
551
- else
552
- # run_callbacks(:validate_on_update)
553
- validate_on_update
554
- end
555
-
556
- errors.empty?
557
- end
558
-
559
- def new_record?
560
- # todo: new_record in activesdb should align with how we're defining a new record here, ie: if id is nil
561
- super
562
- end
563
-
564
- def invalid?
565
- !valid?
566
- end
567
-
568
- def validate
569
- true
570
- end
571
-
572
- def validate_on_create
573
- true
574
- end
575
-
576
- def validate_on_update
577
- true
578
- end
579
-
580
- @create_domain_called = false
581
-
582
- # Options:
583
- # - :except => Array of attributes to NOT save
584
- # - :dirty => true - Will only store attributes that were modified
585
- #
586
- def save(options={})
587
- # puts 'SAVING: ' + self.inspect
588
- clear_errors
589
- # todo: decide whether this should go before pre_save or after pre_save? pre_save dirties "updated" and perhaps other items due to callbacks
590
- if options[:dirty] # Only used in simple_record right now
591
- puts '@dirty=' + @dirty.inspect
592
- return true if @dirty.size == 0 # Nothing to save so skip it
593
- options[:dirty_atts] = @dirty
594
- end
595
- is_create = self[:id].nil?
596
- ok = pre_save(options)
597
- if ok
598
- begin
599
- # puts 'is frozen? ' + self.frozen?.to_s + ' - ' + self.inspect
600
- # if options[:dirty] # Only used in simple_record right now
601
- # puts '@dirty=' + @dirty.inspect
602
- # return true if @dirty.size == 0 # Nothing to save so skip it
603
- # options[:dirty_atts] = @dirty
604
- # end
605
- to_delete = get_atts_to_delete # todo: this should use the @dirty hash now
606
- # puts 'done to_delete ' + to_delete.inspect
607
- SimpleRecord.stats.puts += 1
608
- if super(options)
609
- # puts 'SAVED super'
610
- self.class.cache_results(self)
611
- delete_niled(to_delete)
612
- if (is_create ? run_after_create : run_after_update) && run_after_save
613
- # puts 'all good?'
614
- return true
615
- else
616
- return false
617
- end
618
- else
619
- return false
620
- end
621
- rescue Aws::AwsError
622
- # puts "RESCUED in save: " + $!
623
- if (domain_ok($!))
624
- if !@create_domain_called
625
- @create_domain_called = true
626
- save(options)
627
- else
628
- raise $!
629
- end
630
- else
631
- raise $!
632
- end
633
- end
634
- else
635
- #@debug = "not saved"
636
- return false
637
- end
638
- end
639
-
640
- def save_with_validation!(options={})
641
- if valid?
642
- save
643
- else
644
- raise RecordInvalid.new(self)
645
- end
646
- end
647
-
648
- def pad_and_offset_ints_to_sdb()
649
-
650
- defined_attributes_local.each_pair do |name, att_meta|
651
- # puts 'int encoding: ' + i.to_s
652
- if att_meta.type == :int && !self[name.to_s].nil?
653
- # puts 'before: ' + self[i.to_s].inspect
654
- # puts @attributes.inspect
655
- # puts @attributes[i.to_s].inspect
656
- arr = @attributes[name.to_s]
657
- arr.collect!{ |x| self.class.pad_and_offset(x) }
658
- @attributes[name.to_s] = arr
659
- # puts 'after: ' + @attributes[i.to_s].inspect
660
- end
661
- end
662
- end
663
-
664
- def convert_dates_to_sdb()
665
-
666
- defined_attributes_local.each_pair do |name, att_meta|
667
- # puts 'int encoding: ' + i.to_s
668
- if att_meta.type == :date && !self[name.to_s].nil?
669
- # puts 'before: ' + self[i.to_s].inspect
670
- # puts @attributes.inspect
671
- # puts @attributes[i.to_s].inspect
672
- arr = @attributes[name.to_s]
673
- #puts 'padding date=' + i.to_s
674
- arr.collect!{ |x| self.class.pad_and_offset(x) }
675
- @attributes[name.to_s] = arr
676
- # puts 'after: ' + @attributes[i.to_s].inspect
677
- else
678
- # puts 'was nil'
679
- end
680
- end
681
- end
682
-
683
- def pre_save(options)
684
-
685
- is_create = self[:id].nil?
686
- ok = run_before_validation && (is_create ? run_before_validation_on_create : run_before_validation_on_update)
687
- return false unless ok
688
-
689
- validate()
690
-
691
- is_create ? validate_on_create : validate_on_update
692
- # puts 'AFTER VALIDATIONS, ERRORS=' + errors.inspect
693
- if (!@errors.nil? && @errors.length > 0 )
694
- # puts 'THERE ARE ERRORS, returning false'
695
- return false
696
- end
697
-
698
- ok = run_after_validation && (is_create ? run_after_validation_on_create : run_after_validation_on_update)
699
- return false unless ok
700
-
701
- ok = respond_to?('before_save') ? before_save : true
702
- if ok
703
- if is_create && respond_to?('before_create')
704
- ok = before_create
705
- elsif !is_create && respond_to?('before_update')
706
- ok = before_update
707
- end
708
- end
709
- if ok
710
- ok = run_before_save && (is_create ? run_before_create : run_before_update)
711
- end
712
- if ok
713
- # puts 'ABOUT TO SAVE: ' + self.inspect
714
- # First we gotta pad and offset
715
- pad_and_offset_ints_to_sdb()
716
- convert_dates_to_sdb()
717
- end
718
- ok
719
- end
720
-
721
- def save_attributes(*params)
722
- ret = super(*params)
723
- if ret
724
- self.class.cache_results(self)
725
- end
726
- ret
727
- end
728
-
729
- def get_atts_to_delete
730
- # todo: this should use the @dirty hash now
731
- to_delete = []
732
- @attributes.each do |key, value|
733
- # puts 'key=' + key.inspect + ' value=' + value.inspect
734
- if value.nil? || (value.is_a?(Array) && value.size == 0) || (value.is_a?(Array) && value.size == 1 && value[0] == nil)
735
- to_delete << key
736
- @attributes.delete(key)
737
- end
738
- end
739
- return to_delete
740
- end
741
-
742
- # Run pre_save on each object, then runs batch_put_attributes
743
- # Returns
744
- def self.batch_save(objects, options={})
745
- results = []
746
- to_save = []
747
- if objects && objects.size > 0
748
- objects.each do |o|
749
- ok = o.pre_save(options)
750
- raise "Pre save failed on object [" + o.inspect + "]" if !ok
751
- results << ok
752
- next if !ok # todo: this shouldn't be here should it? raises above
753
- o.pre_save2
754
- to_save << Aws::SdbInterface::Item.new(o.id, o.attributes, true)
755
- if to_save.size == 25 # Max amount SDB will accept
756
- connection.batch_put_attributes(domain, to_save)
757
- to_save.clear
758
- end
759
- end
760
- end
761
- connection.batch_put_attributes(domain, to_save) if to_save.size > 0
762
- results
763
- end
764
-
765
- #
766
- # Usage: ClassName.delete id
767
- # todo: move to Aws
768
- #
769
- def self.delete(id)
770
- connection.delete_attributes(domain, id)
771
- end
772
-
773
- def delete
774
- before_delete
775
- super
776
- after_delete
777
- end
778
-
779
- def delete_niled(to_delete)
780
- # puts 'to_delete=' + to_delete.inspect
781
- if to_delete.size > 0
782
- # puts 'Deleting attributes=' + to_delete.inspect
783
- delete_attributes to_delete
784
- end
785
- end
786
-
787
- def un_offset_if_int(arg, x)
788
- att_meta = defined_attributes_local[arg]
789
- # puts 'int encoding: ' + i.to_s
790
- if att_meta.type == :int
791
- x = un_offset_int(x)
792
- elsif att_meta.type == :date
793
- x = to_date(x)
794
- elsif att_meta.type == :boolean
795
- x = to_bool(x)
796
- end
797
- x
798
- end
799
-
800
-
801
- def to_date(x)
802
- if x.is_a?(String)
803
- DateTime.parse(x)
804
- else
805
- x
806
- end
807
-
808
- end
809
-
810
- def to_bool(x)
811
- if x.is_a?(String)
812
- x == "true" || x == "1"
813
- else
814
- x
815
- end
816
- end
817
-
818
-
819
- def un_offset_int(x)
820
- if x.is_a?(String)
821
- x2 = x.to_i
822
- # puts 'to_i=' + x2.to_s
823
- x2 -= @@offset
824
- # puts 'after subtracting offset='+ x2.to_s
825
- x2
826
- else
827
- x
828
- end
829
- end
830
-
831
- def unpad(i, attributes)
832
- if !attributes[i].nil?
833
- # puts 'before=' + self[i].inspect
834
- attributes[i].collect!{ |x|
835
- un_offset_int(x)
836
-
837
- }
838
- # for x in self[i]
839
- # x = self[i][0].to_i
840
- # x -= @@offset
841
- # self[i] = x
842
- # end
843
- end
844
- end
845
-
846
- def unpad_self
847
- defined_attributes_local.each_pair do |name, att_meta|
848
- if att_meta.type == :int
849
- unpad(name, @attributes)
850
- end
851
- end
852
- end
853
-
854
- def reload
855
- super()
856
- end
857
-
858
- def update_attributes(*params)
859
- return save_attributes(*params)
860
- end
861
-
862
- def destroy(*params)
863
- if super(*params)
864
- if run_after_destroy
865
- return true
866
- else
867
- return false
868
- end
869
- else
870
- return false
871
- end
872
- end
873
-
874
- def self.quote_regexp(a, re)
875
- a =~ re
876
- #was there a match?
877
- if $&
878
- before=$`
879
- middle=$&
880
- after=$'
881
-
882
- before =~ /'$/ #is there already a quote immediately before the match?
883
- unless $&
884
- return "#{before}'#{middle}'#{quote_regexp(after, re)}" #if not, put quotes around the match
885
- else
886
- return "#{before}#{middle}#{quote_regexp(after, re)}" #if so, assume it is quoted already and move on
887
- end
888
- else
889
- #no match, just return the string
890
- return a
891
- end
892
- end
893
-
894
- @@regex_no_id = /.*Couldn't find.*with ID.*/
895
-
896
- #
897
- # Usage:
898
- # Find by ID:
899
- # MyModel.find(ID)
900
- #
901
- # Query example:
902
- # MyModel.find(:all, :conditions=>["name = ?", name], :order=>"created desc", :limit=>10)
903
- #
904
- def self.find(*params)
905
- #puts 'params=' + params.inspect
906
- q_type = :all
907
- select_attributes=[]
908
-
909
- if params.size > 0
910
- q_type = params[0]
911
- end
912
-
913
- # Pad and Offset number attributes
914
- options = {}
915
- if params.size > 1
916
- options = params[1]
917
- #puts 'options=' + options.inspect
918
- #puts 'after collect=' + options.inspect
919
- convert_condition_params(options)
920
- end
921
- # puts 'params2=' + params.inspect
922
-
923
- results = q_type == :all ? [] : nil
924
- begin
925
- results=super(*params)
926
- #puts 'params3=' + params.inspect
927
- SimpleRecord.stats.selects += 1
928
- if q_type != :count
929
- cache_results(results)
930
- if results.is_a?(Array)
931
- results = SimpleRecord::ResultsArray.new(self, params, results, next_token)
932
- end
933
- end
934
- rescue Aws::AwsError, Aws::ActiveSdb::ActiveSdbError
935
- puts "RESCUED: " + $!.message
936
- if ($!.message().index("NoSuchDomain") != nil)
937
- # this is ok
938
- elsif ($!.message() =~ @@regex_no_id)
939
- results = nil
940
- else
941
- raise $!
942
- end
943
- end
944
- return results
945
- end
946
-
947
- def self.select(*params)
948
- return find(*params)
949
- end
950
-
951
- def self.convert_condition_params(options)
952
- return if options.nil?
953
- conditions = options[:conditions]
954
- if !conditions.nil? && conditions.size > 1
955
- # all after first are values
956
- conditions.collect! { |x|
957
- self.pad_and_offset(x)
958
- }
959
- end
960
-
961
- end
962
-
963
- def self.cache_results(results)
964
- if !@@cache_store.nil? && !results.nil?
965
- if results.is_a?(Array)
966
- # todo: cache each result
967
- else
968
- class_name = results.class.name
969
- id = results.id
970
- cache_key = self.cache_key(class_name, id)
971
- #puts 'caching result at ' + cache_key + ': ' + results.inspect
972
- @@cache_store.write(cache_key, results, :expires_in =>30)
973
- end
974
- end
975
- end
976
-
977
- def self.cache_key(class_name, id)
978
- return class_name + "/" + id.to_s
979
- end
980
-
981
- @@debug=""
982
-
983
- def self.debug
984
- @@debug
985
- end
986
-
987
- def self.sanitize_sql(*params)
988
- return ActiveRecord::Base.sanitize_sql(*params)
989
- end
990
-
991
- def self.table_name
992
- return domain
993
- end
994
-
995
- def changed
996
- return @dirty.keys
997
- end
998
-
999
- def changed?
1000
- return @dirty.size > 0
1001
- end
1002
-
1003
- def changes
1004
- ret = {}
1005
- #puts 'in CHANGES=' + @dirty.inspect
1006
- @dirty.each_pair {|key, value| ret[key] = [value, get_attribute(key)]}
1007
- return ret
1008
- end
1009
-
1010
- def mark_as_old
1011
- super
1012
- @dirty = {}
1013
- end
1014
-
1015
- end
1016
-
1017
- class SimpleRecord_errors
1018
- def initialize(*params)
1019
- super(*params)
1020
- @errors=[]
1021
- end
1022
-
1023
- def add_to_base(value)
1024
- @errors+=[value]
1025
- end
1026
-
1027
- def add(attribute, value)
1028
- @errors+=["#{attribute.to_s} #{value}"]
1029
- end
1030
-
1031
- def count
1032
- return length
1033
- end
1034
-
1035
- def length
1036
- return @errors.length
1037
- end
1038
-
1039
- def size
1040
- return length
1041
- end
1042
-
1043
- def full_messages
1044
- return @errors
1045
- end
1046
-
1047
- def clear
1048
- @errors.clear
1049
- end
1050
-
1051
- def empty?
1052
- @errors.empty?
1053
- end
1054
- end
1055
-
1056
- class Activerecordtosdb_subrecord_array
1057
- def initialize(subname, referencename, referencevalue)
1058
- @subname=subname.classify
1059
- @referencename=referencename.tableize.singularize + "_id"
1060
- @referencevalue=referencevalue
1061
- end
1062
-
1063
- # Performance optimization if you know the array should be empty
1064
-
1065
- def init_empty
1066
- @records = []
1067
- end
1068
-
1069
- def load
1070
- if @records.nil?
1071
- @records = find_all
1072
- end
1073
- return @records
1074
- end
1075
-
1076
- def [](key)
1077
- return load[key]
1078
- end
1079
-
1080
- def <<(ob)
1081
- return load << ob
1082
- end
1083
-
1084
- def count
1085
- return load.count
1086
- end
1087
-
1088
- def size
1089
- return count
1090
- end
1091
-
1092
- def each(*params, &block)
1093
- return load.each(*params){|record| block.call(record)}
1094
- end
1095
-
1096
- def find_all(*params)
1097
- find(:all, *params)
1098
- end
1099
-
1100
- def empty?
1101
- return load.empty?
1102
- end
1103
-
1104
- def build(*params)
1105
- params[0][@referencename]=@referencevalue
1106
- eval(@subname).new(*params)
1107
- end
1108
-
1109
- def create(*params)
1110
- params[0][@referencename]=@referencevalue
1111
- record = eval(@subname).new(*params)
1112
- record.save
1113
- end
1114
-
1115
- def find(*params)
1116
- query=[:first, {}]
1117
- #{:conditions=>"id=>1"}
1118
- if params[0]
1119
- if params[0]==:all
1120
- query[0]=:all
1121
- end
1122
- end
1123
-
1124
- if params[1]
1125
- query[1]=params[1]
1126
- if query[1][:conditions]
1127
- query[1][:conditions]=SimpleRecord::Base.sanitize_sql(query[1][:conditions])+" AND "+ SimpleRecord::Base.sanitize_sql(["#{@referencename} = ?", @referencevalue])
1128
- #query[1][:conditions]=Activerecordtosdb.sanitize_sql(query[1][:conditions])+" AND id='#{@id}'"
1129
- else
1130
- query[1][:conditions]=["#{@referencename} = ?", @referencevalue]
1131
- #query[1][:conditions]="id='#{@id}'"
1132
- end
1133
- else
1134
- query[1][:conditions]=["#{@referencename} = ?", @referencevalue]
1135
- #query[1][:conditions]="id='#{@id}'"
1136
- end
1137
-
1138
- return eval(@subname).find(*query)
1139
- end
1140
-
1141
- end
1142
-
1143
- class SimpleRecordError < StandardError
1144
-
1145
- end
1146
-
1147
- class RecordInvalid < SimpleRecordError
1148
- attr_accessor :record
1149
-
1150
- def initialize(record)
1151
- @record = record
1152
- end
1153
- end
1154
- end
1155
-
1
+ # Usage:
2
+ # require 'simple_record'
3
+ #
4
+ # class MyModel < SimpleRecord::Base
5
+ #
6
+ # has_attributes :name, :age
7
+ # are_ints :age
8
+ #
9
+ # end
10
+ #
11
+ # AWS_ACCESS_KEY_ID='XXXXX'
12
+ # AWS_SECRET_ACCESS_KEY='YYYYY'
13
+ # SimpleRecord.establish_connection(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY)
14
+ #
15
+ ## Save an object
16
+ # mm = MyModel.new
17
+ # mm.name = "Travis"
18
+ # mm.age = 32
19
+ # mm.save
20
+ # id = mm.id
21
+ # # Get the object back
22
+ # mm2 = MyModel.select(id)
23
+ # puts 'got=' + mm2.name + ' and he/she is ' + mm.age.to_s + ' years old'
24
+
25
+
26
+ require 'aws'
27
+ require 'sdb/active_sdb'
28
+ #require 'results_array' # why the heck isn't this picking up???
29
+ require File.expand_path(File.dirname(__FILE__) + "/results_array")
30
+ require File.expand_path(File.dirname(__FILE__) + "/stats")
31
+ require File.expand_path(File.dirname(__FILE__) + "/callbacks")
32
+
33
+ module SimpleRecord
34
+
35
+ @@stats = SimpleRecord::Stats.new
36
+
37
+ def self.stats
38
+ @@stats
39
+ end
40
+
41
+ # Create a new handle to an Sdb account. All handles share the same per process or per thread
42
+ # HTTP connection to Amazon Sdb. Each handle is for a specific account.
43
+ # The +params+ are passed through as-is to Aws::SdbInterface.new
44
+ # Params:
45
+ # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
46
+ # :port => 443 # Amazon service port: 80(default) or 443
47
+ # :protocol => 'https' # Amazon service protocol: 'http'(default) or 'https'
48
+ # :signature_version => '0' # The signature version : '0' or '1'(default)
49
+ # :connection_mode => :default # options are
50
+ # :default (will use best known safe (as in won't need explicit close) option, may change in the future)
51
+ # :per_request (opens and closes a connection on every request to SDB)
52
+ # :single (one thread across entire app)
53
+ # :per_thread (one connection per thread)
54
+ # :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET)
55
+ # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
56
+ def self.establish_connection(aws_access_key=nil, aws_secret_key=nil, params={})
57
+ Aws::ActiveSdb.establish_connection(aws_access_key, aws_secret_key, params)
58
+ end
59
+
60
+ def self.close_connection()
61
+ Aws::ActiveSdb.close_connection
62
+ end
63
+
64
+ class Base < Aws::ActiveSdb::Base
65
+
66
+ include SimpleRecord::Callbacks
67
+
68
+
69
+ def initialize(attrs={})
70
+ # todo: Need to deal with objects passed in. iterate through belongs_to perhaps and if in attrs, set the objects id rather than the object itself
71
+
72
+ #we have to handle the virtuals.
73
+ @@virtuals.each do |virtual|
74
+ #we first copy the information for the virtual to an instance variable of the same name
75
+ eval("@#{virtual}=attrs['#{virtual}']")
76
+ #and then remove the parameter before it is passed to initialize, so that it is NOT sent to SimpleDB
77
+ eval("attrs.delete('#{virtual}')")
78
+ end
79
+ super
80
+ @errors=SimpleRecord_errors.new
81
+ @dirty = {}
82
+ end
83
+
84
+
85
+ # todo: move into Callbacks module
86
+ #this bit of code creates a "run_blank" function for everything value in the @@callbacks array.
87
+ #this function can then be inserted in the appropriate place in the save, new, destroy, etc overrides
88
+ #basically, this is how we recreate the callback functions
89
+ @@callbacks.each do |callback|
90
+ instance_eval <<-endofeval
91
+
92
+ #puts 'doing callback=' + callback + ' for ' + self.inspect
93
+ #we first have to make an initialized array for each of the callbacks, to prevent problems if they are not called
94
+
95
+ def #{callback}(*args)
96
+ #puts 'callback called in ' + self.inspect + ' with ' + args.inspect
97
+
98
+ #make_dirty(arg_s, value)
99
+ #self[arg.to_s]=value
100
+ #puts 'value in callback #{callback}=' + value.to_s
101
+ args.each do |arg|
102
+ cnames = callbacks['#{callback}']
103
+ #puts '\tcnames1=' + cnames.inspect + ' for class ' + self.inspect
104
+ cnames = [] if cnames.nil?
105
+ cnames << arg.to_s if cnames.index(arg.to_s).nil?
106
+ #puts '\tcnames2=' + cnames.inspect
107
+ callbacks['#{callback}'] = cnames
108
+ end
109
+ end
110
+
111
+ endofeval
112
+ end
113
+ #puts 'base methods=' + self.methods.inspect
114
+
115
+
116
+ def self.inherited(base)
117
+ #puts 'SimpleRecord::Base is inherited by ' + base.inspect
118
+ setup_callbacks(base)
119
+
120
+ base.has_dates :created, :updated
121
+ base.before_create :set_created, :set_updated
122
+ base.before_update :set_updated
123
+
124
+ end
125
+
126
+ def self.setup_callbacks(base)
127
+ instance_eval <<-endofeval
128
+
129
+ def callbacks
130
+ @callbacks ||= {}
131
+ @callbacks
132
+ end
133
+
134
+ def self.defined_attributes
135
+ #puts 'class defined_attributes'
136
+ @attributes ||= {}
137
+ @attributes
138
+ end
139
+
140
+ endofeval
141
+
142
+ @@callbacks.each do |callback|
143
+ class_eval <<-endofeval
144
+
145
+ def run_#{callback}
146
+ # puts 'CLASS CALLBACKS for ' + self.inspect + ' = ' + self.class.callbacks.inspect
147
+ return true if self.class.callbacks.nil?
148
+ cnames = self.class.callbacks['#{callback}']
149
+ cnames = [] if cnames.nil?
150
+ #cnames += super.class.callbacks['#{callback}'] unless super.class.callbacks.nil?
151
+ # puts 'cnames for #{callback} = ' + cnames.inspect
152
+ return true if cnames.nil?
153
+ cnames.each { |name|
154
+ #puts 'run_ #{name}'
155
+ if eval(name) == false # nil should be an ok return, only looking for false
156
+ return false
157
+ end
158
+ }
159
+ #super.run_#{callback}
160
+ return true
161
+ end
162
+
163
+ endofeval
164
+ end
165
+ end
166
+
167
+
168
+ # Holds information about an attribute
169
+ class Attribute
170
+ attr_accessor :type, :options
171
+
172
+ def initialize(type)
173
+ @type = type
174
+ end
175
+
176
+ end
177
+
178
+
179
+ def defined_attributes_local
180
+ #puts 'local defined_attributes'
181
+ ret = self.class.defined_attributes
182
+ ret.merge!(self.class.superclass.defined_attributes) if self.class.superclass.respond_to?(:defined_attributes)
183
+ end
184
+
185
+
186
+ attr_accessor :errors
187
+
188
+ @domain_prefix = ''
189
+ class << self;
190
+ attr_accessor :domain_prefix;
191
+ end
192
+
193
+ #@domain_name_for_class = nil
194
+
195
+ @@cache_store = nil
196
+ # Set the cache to use
197
+ def self.cache_store=(cache)
198
+ @@cache_store = cache
199
+ end
200
+
201
+ def self.cache_store
202
+ return @@cache_store
203
+ end
204
+
205
+ # If you want a domain prefix for all your models, set it here.
206
+ def self.set_domain_prefix(prefix)
207
+ #puts 'set_domain_prefix=' + prefix
208
+ self.domain_prefix = prefix
209
+ end
210
+
211
+ # Same as set_table_name
212
+ def self.set_table_name(table_name)
213
+ set_domain_name table_name
214
+ end
215
+
216
+ # Sets the domain name for this class
217
+ def self.set_domain_name(table_name)
218
+ # puts 'setting domain name for class ' + self.inspect + '=' + table_name
219
+ #@domain_name_for_class = table_name
220
+ super
221
+ end
222
+
223
+ =begin
224
+ def self.get_domain_name
225
+ # puts 'returning domain_name=' + @domain_name_for_class.to_s
226
+ #return @domain_name_for_class
227
+ return self.domain
228
+ end
229
+
230
+ =end
231
+
232
+ def domain
233
+ super # super.domain
234
+ end
235
+
236
+ def self.domain
237
+ #return self.get_domain_name unless self.get_domain_name.nil?
238
+ d = super
239
+ #puts 'in self.domain, d=' + d.to_s + ' domain_prefix=' + SimpleRecord::Base.domain_prefix.to_s
240
+ domain_name_for_class = SimpleRecord::Base.domain_prefix + d.to_s
241
+ #self.set_domain_name(domain_name_for_class)
242
+ domain_name_for_class
243
+ end
244
+
245
+
246
+ # Since SimpleDB supports multiple attributes per value, the values are an array.
247
+ # This method will return the value unwrapped if it's the only, otherwise it will return the array.
248
+ def get_attribute(arg)
249
+ arg = arg.to_s
250
+ if self[arg].class==Array
251
+ if self[arg].length==1
252
+ ret = self[arg][0]
253
+ else
254
+ ret = self[arg]
255
+ end
256
+ else
257
+ ret = self[arg]
258
+ end
259
+ ret
260
+ end
261
+
262
+ def make_dirty(arg, value)
263
+ # todo: only set dirty if it changed
264
+ #puts 'making dirty arg=' + arg.to_s + ' --- ' + @dirty.inspect
265
+ @dirty[arg] = get_attribute(arg) # Store old value (not sure if we need it?)
266
+ #puts 'end making dirty ' + @dirty.inspect
267
+ end
268
+
269
+ def self.has_attributes(*args)
270
+ args.each do |arg|
271
+ defined_attributes[arg] = SimpleRecord::Base::Attribute.new(:string) if defined_attributes[arg].nil?
272
+ # define reader method
273
+ arg_s = arg.to_s # to get rid of all the to_s calls
274
+ send(:define_method, arg) do
275
+ ret = nil
276
+ ret = get_attribute(arg)
277
+ return nil if ret.nil?
278
+ return Base.un_offset_if_int(arg, ret)
279
+ end
280
+
281
+ # define writer method
282
+ send(:define_method, arg_s+"=") do |value|
283
+ make_dirty(arg_s, value)
284
+ self[arg_s]=value
285
+ end
286
+
287
+ # Now for dirty methods: http://api.rubyonrails.org/classes/ActiveRecord/Dirty.html
288
+ # define changed? method
289
+ send(:define_method, arg_s + "_changed?") do
290
+ @dirty.has_key?(arg_s)
291
+ end
292
+
293
+ # define change method
294
+ send(:define_method, arg_s + "_change") do
295
+ old_val = @dirty[arg_s]
296
+ return nil if old_val.nil?
297
+ [old_val, get_attribute(arg_s)]
298
+ end
299
+
300
+ # define was method
301
+ send(:define_method, arg_s + "_was") do
302
+ old_val = @dirty[arg_s]
303
+ old_val
304
+ end
305
+ end
306
+ end
307
+
308
+ def self.has_strings(*args)
309
+ has_attributes(*args)
310
+ end
311
+
312
+ def self.has_ints(*args)
313
+ has_attributes(*args)
314
+ are_ints(*args)
315
+ end
316
+
317
+ def self.has_dates(*args)
318
+ has_attributes(*args)
319
+ are_dates(*args)
320
+ end
321
+
322
+ def self.has_booleans(*args)
323
+ has_attributes(*args)
324
+ are_booleans(*args)
325
+ end
326
+
327
+ def self.are_ints(*args)
328
+ # puts 'calling are_ints: ' + args.inspect
329
+ args.each do |arg|
330
+ defined_attributes[arg].type = :int
331
+ end
332
+ end
333
+
334
+ def self.are_dates(*args)
335
+ args.each do |arg|
336
+ defined_attributes[arg].type = :date
337
+ end
338
+ end
339
+
340
+ def self.are_booleans(*args)
341
+ args.each do |arg|
342
+ defined_attributes[arg].type = :boolean
343
+ end
344
+ end
345
+
346
+ @@virtuals=[]
347
+
348
+ def self.has_virtuals(*args)
349
+ @@virtuals = args
350
+ args.each do |arg|
351
+ #we just create the accessor functions here, the actual instance variable is created during initialize
352
+ attr_accessor(arg)
353
+ end
354
+ end
355
+
356
+ # One belongs_to association per call. Call multiple times if there are more than one.
357
+ #
358
+ # This method will also create an {association)_id method that will return the ID of the foreign object
359
+ # without actually materializing it.
360
+ def self.belongs_to(association_id, options = {})
361
+ attribute = SimpleRecord::Base::Attribute.new(:belongs_to)
362
+ defined_attributes[association_id] = attribute
363
+ attribute.options = options
364
+ #@@belongs_to_map[association_id] = options
365
+ arg = association_id
366
+ arg_s = arg.to_s
367
+ arg_id = arg.to_s + '_id'
368
+
369
+ # todo: should also handle foreign_key http://74.125.95.132/search?q=cache:KqLkxuXiBBQJ:wiki.rubyonrails.org/rails/show/belongs_to+rails+belongs_to&hl=en&ct=clnk&cd=1&gl=us
370
+ # puts "arg_id=#{arg}_id"
371
+ # puts "is defined? " + eval("(defined? #{arg}_id)").to_s
372
+ # puts 'atts=' + @attributes.inspect
373
+
374
+ # Define reader method
375
+ send(:define_method, arg) do
376
+ attribute = defined_attributes_local[arg]
377
+ options2 = attribute.options # @@belongs_to_map[arg]
378
+ class_name = options2[:class_name] || arg.to_s[0...1].capitalize + arg.to_s[1...arg.to_s.length]
379
+
380
+ # Camelize classnames with underscores (ie my_model.rb --> MyModel)
381
+ class_name = class_name.camelize
382
+
383
+ # puts "attr=" + @attributes[arg_id].inspect
384
+ # puts 'val=' + @attributes[arg_id][0].inspect unless @attributes[arg_id].nil?
385
+ ret = nil
386
+ arg_id = arg.to_s + '_id'
387
+ if !@attributes[arg_id].nil? && @attributes[arg_id].size > 0 && @attributes[arg_id][0] != nil && @attributes[arg_id][0] != ''
388
+ if !@@cache_store.nil?
389
+ arg_id_val = @attributes[arg_id][0]
390
+ cache_key = self.class.cache_key(class_name, arg_id_val)
391
+ # puts 'cache_key=' + cache_key
392
+ ret = @@cache_store.read(cache_key)
393
+ # puts 'belongs_to incache=' + ret.inspect
394
+ end
395
+ if ret.nil?
396
+ to_eval = "#{class_name}.find(@attributes['#{arg_id}'][0])"
397
+ # puts 'to eval=' + to_eval
398
+ begin
399
+ ret = eval(to_eval) # (defined? #{arg}_id)
400
+ rescue Aws::ActiveSdb::ActiveSdbError
401
+ if $!.message.include? "Couldn't find"
402
+ ret = nil
403
+ else
404
+ raise $!
405
+ end
406
+ end
407
+
408
+ end
409
+ end
410
+ # puts 'ret=' + ret.inspect
411
+ return ret
412
+ end
413
+
414
+
415
+ # Define writer method
416
+ send(:define_method, arg.to_s + "=") do |value|
417
+ arg_id = arg.to_s + '_id'
418
+ if value.nil?
419
+ make_dirty(arg_id, nil)
420
+ self[arg_id]=nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
421
+ else
422
+ make_dirty(arg_id, value.id)
423
+ self[arg_id]=value.id
424
+ end
425
+ end
426
+
427
+
428
+ # Define ID reader method for reading the associated objects id without getting the entire object
429
+ send(:define_method, arg_id) do
430
+ if !@attributes[arg_id].nil? && @attributes[arg_id].size > 0 && @attributes[arg_id][0] != nil && @attributes[arg_id][0] != ''
431
+ return @attributes[arg_id][0]
432
+ end
433
+ return nil
434
+ end
435
+
436
+ # Define writer method for setting the _id directly without the associated object
437
+ send(:define_method, arg_id + "=") do |value|
438
+ if value.nil?
439
+ self[arg_id] = nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
440
+ else
441
+ self[arg_id] = value
442
+ end
443
+ end
444
+
445
+ send(:define_method, "create_"+arg.to_s) do |*params|
446
+ newsubrecord=eval(arg.to_s.classify).new(*params)
447
+ newsubrecord.save
448
+ arg_id = arg.to_s + '_id'
449
+ self[arg_id]=newsubrecord.id
450
+ end
451
+ end
452
+
453
+ def self.has_many(*args)
454
+ args.each do |arg|
455
+ #okay, this creates an instance method with the pluralized name of the symbol passed to belongs_to
456
+ send(:define_method, arg) do
457
+ #when called, the method creates a new, very temporary instance of the Activerecordtosdb_subrecord class
458
+ #It is passed the three initializers it needs:
459
+ #note the first parameter is just a string by time new gets it, like "user"
460
+ #the second and third parameters are still a variable when new gets it, like user_id
461
+ return eval(%{Activerecordtosdb_subrecord_array.new('#{arg}', self.class.name ,id)})
462
+ end
463
+ end
464
+ #Disclaimer: this whole funciton just seems crazy to me, and a bit inefficient. But it was the clearest way I could think to do it code wise.
465
+ #It's bad programming form (imo) to have a class method require something that isn't passed to it through it's variables.
466
+ #I couldn't pass the id when calling find, since the original find doesn't work that way, so I was left with this.
467
+ end
468
+
469
+ def self.has_one(*args)
470
+
471
+ end
472
+
473
+ def clear_errors
474
+ @errors=SimpleRecord_errors.new
475
+ end
476
+
477
+ def []=(attribute, values)
478
+ make_dirty(attribute, values)
479
+ super
480
+ end
481
+
482
+
483
+ def set_created
484
+ # puts 'SETTING CREATED'
485
+ # @created = DateTime.now
486
+ self[:created] = DateTime.now
487
+ # @tester = 'some test value'
488
+ # self[:tester] = 'some test value'
489
+ end
490
+
491
+ def set_updated
492
+ #puts 'SETTING UPDATED'
493
+ # @updated = DateTime.now
494
+ self[:updated] = DateTime.now
495
+ # @tester = 'some test value updated'
496
+ end
497
+
498
+
499
+ @@offset = 9223372036854775808
500
+ @@padding = 20
501
+ @@date_format = "%Y-%m-%dT%H:%M:%S"; # Time to second precision
502
+
503
+ def self.pad_and_offset(x) # Change name to something more appropriate like ruby_to_sdb
504
+ # todo: add Float, etc
505
+ # puts 'padding=' + x.class.name + " -- " + x.inspect
506
+ if x.kind_of? Integer
507
+ x += @@offset
508
+ x_str = x.to_s
509
+ # pad
510
+ x_str = '0' + x_str while x_str.size < 20
511
+ return x_str
512
+ elsif x.respond_to?(:iso8601)
513
+ # puts x.class.name + ' responds to iso8601'
514
+ #
515
+ # There is an issue here where Time.iso8601 on an incomparable value to DateTime.iso8601.
516
+ # Amazon suggests: 2008-02-10T16:52:01.000-05:00
517
+ # "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
518
+ #
519
+ if x.is_a? DateTime
520
+ x_str = x.getutc.strftime(@@date_format)
521
+ elsif x.is_a? Time
522
+ x_str = x.getutc.strftime(@@date_format)
523
+ elsif x.is_a? Date
524
+ x_str = x.strftime(@@date_format)
525
+
526
+ end
527
+ return x_str
528
+ else
529
+ return x
530
+ end
531
+ end
532
+
533
+ def domain_ok(ex)
534
+ if (ex.message().index("NoSuchDomain") != nil)
535
+ puts "Creating new SimpleDB Domain: " + domain
536
+ self.class.create_domain
537
+ return true
538
+ end
539
+ return false
540
+ end
541
+
542
+ def valid?
543
+ errors.clear
544
+
545
+ # run_callbacks(:validate)
546
+ validate
547
+
548
+ if new_record?
549
+ # run_callbacks(:validate_on_create)
550
+ validate_on_create
551
+ else
552
+ # run_callbacks(:validate_on_update)
553
+ validate_on_update
554
+ end
555
+
556
+ errors.empty?
557
+ end
558
+
559
+ def new_record?
560
+ # todo: new_record in activesdb should align with how we're defining a new record here, ie: if id is nil
561
+ super
562
+ end
563
+
564
+ def invalid?
565
+ !valid?
566
+ end
567
+
568
+ def validate
569
+ true
570
+ end
571
+
572
+ def validate_on_create
573
+ true
574
+ end
575
+
576
+ def validate_on_update
577
+ true
578
+ end
579
+
580
+ @create_domain_called = false
581
+
582
+ # Options:
583
+ # - :except => Array of attributes to NOT save
584
+ # - :dirty => true - Will only store attributes that were modified
585
+ #
586
+ def save(options={})
587
+ # puts 'SAVING: ' + self.inspect
588
+ clear_errors
589
+ # todo: decide whether this should go before pre_save or after pre_save? pre_save dirties "updated" and perhaps other items due to callbacks
590
+ if options[:dirty] # Only used in simple_record right now
591
+ # puts '@dirty=' + @dirty.inspect
592
+ return true if @dirty.size == 0 # Nothing to save so skip it
593
+ options[:dirty_atts] = @dirty
594
+ end
595
+ is_create = self[:id].nil?
596
+ ok = pre_save(options)
597
+ if ok
598
+ begin
599
+ # puts 'is frozen? ' + self.frozen?.to_s + ' - ' + self.inspect
600
+ # if options[:dirty] # Only used in simple_record right now
601
+ # puts '@dirty=' + @dirty.inspect
602
+ # return true if @dirty.size == 0 # Nothing to save so skip it
603
+ # options[:dirty_atts] = @dirty
604
+ # end
605
+ to_delete = get_atts_to_delete # todo: this should use the @dirty hash now
606
+ # puts 'done to_delete ' + to_delete.inspect
607
+ SimpleRecord.stats.puts += 1
608
+ if super(options)
609
+ # puts 'SAVED super'
610
+ self.class.cache_results(self)
611
+ delete_niled(to_delete)
612
+ if (is_create ? run_after_create : run_after_update) && run_after_save
613
+ # puts 'all good?'
614
+ return true
615
+ else
616
+ return false
617
+ end
618
+ else
619
+ return false
620
+ end
621
+ rescue Aws::AwsError
622
+ # puts "RESCUED in save: " + $!
623
+ if (domain_ok($!))
624
+ if !@create_domain_called
625
+ @create_domain_called = true
626
+ save(options)
627
+ else
628
+ raise $!
629
+ end
630
+ else
631
+ raise $!
632
+ end
633
+ end
634
+ else
635
+ #@debug = "not saved"
636
+ return false
637
+ end
638
+ end
639
+
640
+ def save_with_validation!(options={})
641
+ if valid?
642
+ save
643
+ else
644
+ raise RecordInvalid.new(self)
645
+ end
646
+ end
647
+
648
+ def pad_and_offset_ints_to_sdb()
649
+
650
+ defined_attributes_local.each_pair do |name, att_meta|
651
+ # puts 'int encoding: ' + i.to_s
652
+ if att_meta.type == :int && !self[name.to_s].nil?
653
+ # puts 'before: ' + self[i.to_s].inspect
654
+ # puts @attributes.inspect
655
+ # puts @attributes[i.to_s].inspect
656
+ arr = @attributes[name.to_s]
657
+ arr.collect!{ |x| self.class.pad_and_offset(x) }
658
+ @attributes[name.to_s] = arr
659
+ # puts 'after: ' + @attributes[i.to_s].inspect
660
+ end
661
+ end
662
+ end
663
+
664
+ def convert_dates_to_sdb()
665
+
666
+ defined_attributes_local.each_pair do |name, att_meta|
667
+ # puts 'int encoding: ' + i.to_s
668
+ if att_meta.type == :date && !self[name.to_s].nil?
669
+ # puts 'before: ' + self[i.to_s].inspect
670
+ # puts @attributes.inspect
671
+ # puts @attributes[i.to_s].inspect
672
+ arr = @attributes[name.to_s]
673
+ #puts 'padding date=' + i.to_s
674
+ arr.collect!{ |x| self.class.pad_and_offset(x) }
675
+ @attributes[name.to_s] = arr
676
+ # puts 'after: ' + @attributes[i.to_s].inspect
677
+ else
678
+ # puts 'was nil'
679
+ end
680
+ end
681
+ end
682
+
683
+ def pre_save(options)
684
+
685
+ is_create = self[:id].nil?
686
+ ok = run_before_validation && (is_create ? run_before_validation_on_create : run_before_validation_on_update)
687
+ return false unless ok
688
+
689
+ validate()
690
+
691
+ is_create ? validate_on_create : validate_on_update
692
+ # puts 'AFTER VALIDATIONS, ERRORS=' + errors.inspect
693
+ if (!@errors.nil? && @errors.length > 0 )
694
+ # puts 'THERE ARE ERRORS, returning false'
695
+ return false
696
+ end
697
+
698
+ ok = run_after_validation && (is_create ? run_after_validation_on_create : run_after_validation_on_update)
699
+ return false unless ok
700
+
701
+ ok = respond_to?('before_save') ? before_save : true
702
+ if ok
703
+ if is_create && respond_to?('before_create')
704
+ ok = before_create
705
+ elsif !is_create && respond_to?('before_update')
706
+ ok = before_update
707
+ end
708
+ end
709
+ if ok
710
+ ok = run_before_save && (is_create ? run_before_create : run_before_update)
711
+ end
712
+ if ok
713
+ # puts 'ABOUT TO SAVE: ' + self.inspect
714
+ # First we gotta pad and offset
715
+ pad_and_offset_ints_to_sdb()
716
+ convert_dates_to_sdb()
717
+ end
718
+ ok
719
+ end
720
+
721
+ def save_attributes(*params)
722
+ ret = super(*params)
723
+ if ret
724
+ self.class.cache_results(self)
725
+ end
726
+ ret
727
+ end
728
+
729
+ def get_atts_to_delete
730
+ # todo: this should use the @dirty hash now
731
+ to_delete = []
732
+ @attributes.each do |key, value|
733
+ # puts 'key=' + key.inspect + ' value=' + value.inspect
734
+ if value.nil? || (value.is_a?(Array) && value.size == 0) || (value.is_a?(Array) && value.size == 1 && value[0] == nil)
735
+ to_delete << key
736
+ @attributes.delete(key)
737
+ end
738
+ end
739
+ return to_delete
740
+ end
741
+
742
+ # Run pre_save on each object, then runs batch_put_attributes
743
+ # Returns
744
+ def self.batch_save(objects, options={})
745
+ results = []
746
+ to_save = []
747
+ if objects && objects.size > 0
748
+ objects.each do |o|
749
+ ok = o.pre_save(options)
750
+ raise "Pre save failed on object [" + o.inspect + "]" if !ok
751
+ results << ok
752
+ next if !ok # todo: this shouldn't be here should it? raises above
753
+ o.pre_save2
754
+ to_save << Aws::SdbInterface::Item.new(o.id, o.attributes, true)
755
+ if to_save.size == 25 # Max amount SDB will accept
756
+ connection.batch_put_attributes(domain, to_save)
757
+ to_save.clear
758
+ end
759
+ end
760
+ end
761
+ connection.batch_put_attributes(domain, to_save) if to_save.size > 0
762
+ results
763
+ end
764
+
765
+ #
766
+ # Usage: ClassName.delete id
767
+ #
768
+ def self.delete(id)
769
+ connection.delete_attributes(domain, id)
770
+ end
771
+
772
+ def self.delete_all(*params)
773
+ # could make this quicker by just getting item_names and deleting attributes rather than creating objects
774
+ obs = self.find(params)
775
+ i = 0
776
+ obs.each do |a|
777
+ a.delete
778
+ i+=1
779
+ end
780
+ return i
781
+ end
782
+
783
+ def self.destroy_all(*params)
784
+ obs = self.find(params)
785
+ i = 0
786
+ obs.each do |a|
787
+ a.destroy
788
+ i+=1
789
+ end
790
+ return i
791
+ end
792
+
793
+ def delete
794
+ super
795
+ end
796
+
797
+ def destroy(*params)
798
+ return run_before_destroy && super(*params) && run_after_destroy
799
+ end
800
+
801
+ def delete_niled(to_delete)
802
+ # puts 'to_delete=' + to_delete.inspect
803
+ if to_delete.size > 0
804
+ # puts 'Deleting attributes=' + to_delete.inspect
805
+ delete_attributes to_delete
806
+ end
807
+ end
808
+
809
+ def un_offset_if_int(arg, x)
810
+ att_meta = defined_attributes_local[arg]
811
+ # puts 'int encoding: ' + i.to_s
812
+ if att_meta.type == :int
813
+ x = Base.un_offset_int(x)
814
+ elsif att_meta.type == :date
815
+ x = to_date(x)
816
+ elsif att_meta.type == :boolean
817
+ x = to_bool(x)
818
+ end
819
+ x
820
+ end
821
+
822
+
823
+ def to_date(x)
824
+ if x.is_a?(String)
825
+ DateTime.parse(x)
826
+ else
827
+ x
828
+ end
829
+
830
+ end
831
+
832
+ def to_bool(x)
833
+ if x.is_a?(String)
834
+ x == "true" || x == "1"
835
+ else
836
+ x
837
+ end
838
+ end
839
+
840
+
841
+ def self.un_offset_int(x)
842
+ if x.is_a?(String)
843
+ x2 = x.to_i
844
+ puts 'to_i=' + x2.to_s
845
+ x2 -= @@offset
846
+ # puts 'after subtracting offset='+ x2.to_s
847
+ x2
848
+ else
849
+ x
850
+ end
851
+ end
852
+
853
+ def unpad(i, attributes)
854
+ if !attributes[i].nil?
855
+ # puts 'before=' + self[i].inspect
856
+ attributes[i].collect!{ |x|
857
+ un_offset_int(x)
858
+
859
+ }
860
+ # for x in self[i]
861
+ # x = self[i][0].to_i
862
+ # x -= @@offset
863
+ # self[i] = x
864
+ # end
865
+ end
866
+ end
867
+
868
+ def unpad_self
869
+ defined_attributes_local.each_pair do |name, att_meta|
870
+ if att_meta.type == :int
871
+ unpad(name, @attributes)
872
+ end
873
+ end
874
+ end
875
+
876
+ def reload
877
+ super()
878
+ end
879
+
880
+ def update_attributes(*params)
881
+ return save_attributes(*params)
882
+ end
883
+
884
+ def self.quote_regexp(a, re)
885
+ a =~ re
886
+ #was there a match?
887
+ if $&
888
+ before=$`
889
+ middle=$&
890
+ after=$'
891
+
892
+ before =~ /'$/ #is there already a quote immediately before the match?
893
+ unless $&
894
+ return "#{before}'#{middle}'#{quote_regexp(after, re)}" #if not, put quotes around the match
895
+ else
896
+ return "#{before}#{middle}#{quote_regexp(after, re)}" #if so, assume it is quoted already and move on
897
+ end
898
+ else
899
+ #no match, just return the string
900
+ return a
901
+ end
902
+ end
903
+
904
+ @@regex_no_id = /.*Couldn't find.*with ID.*/
905
+
906
+ #
907
+ # Usage:
908
+ # Find by ID:
909
+ # MyModel.find(ID)
910
+ #
911
+ # Query example:
912
+ # MyModel.find(:all, :conditions=>["name = ?", name], :order=>"created desc", :limit=>10)
913
+ #
914
+ def self.find(*params)
915
+ #puts 'params=' + params.inspect
916
+ q_type = :all
917
+ select_attributes=[]
918
+
919
+ if params.size > 0
920
+ q_type = params[0]
921
+ end
922
+
923
+ # Pad and Offset number attributes
924
+ options = {}
925
+ if params.size > 1
926
+ options = params[1]
927
+ #puts 'options=' + options.inspect
928
+ #puts 'after collect=' + options.inspect
929
+ convert_condition_params(options)
930
+ end
931
+ # puts 'params2=' + params.inspect
932
+
933
+ results = q_type == :all ? [] : nil
934
+ begin
935
+ results=super(*params)
936
+ #puts 'params3=' + params.inspect
937
+ SimpleRecord.stats.selects += 1
938
+ if q_type != :count
939
+ cache_results(results)
940
+ if results.is_a?(Array)
941
+ results = SimpleRecord::ResultsArray.new(self, params, results, next_token)
942
+ end
943
+ end
944
+ rescue Aws::AwsError, Aws::ActiveSdb::ActiveSdbError
945
+ puts "RESCUED: " + $!.message
946
+ if ($!.message().index("NoSuchDomain") != nil)
947
+ # this is ok
948
+ elsif ($!.message() =~ @@regex_no_id)
949
+ results = nil
950
+ else
951
+ raise $!
952
+ end
953
+ end
954
+ return results
955
+ end
956
+
957
+ def self.select(*params)
958
+ return find(*params)
959
+ end
960
+
961
+ def self.convert_condition_params(options)
962
+ return if options.nil?
963
+ conditions = options[:conditions]
964
+ if !conditions.nil? && conditions.size > 1
965
+ # all after first are values
966
+ conditions.collect! { |x|
967
+ self.pad_and_offset(x)
968
+ }
969
+ end
970
+
971
+ end
972
+
973
+ def self.cache_results(results)
974
+ if !@@cache_store.nil? && !results.nil?
975
+ if results.is_a?(Array)
976
+ # todo: cache each result
977
+ else
978
+ class_name = results.class.name
979
+ id = results.id
980
+ cache_key = self.cache_key(class_name, id)
981
+ #puts 'caching result at ' + cache_key + ': ' + results.inspect
982
+ @@cache_store.write(cache_key, results, :expires_in =>30)
983
+ end
984
+ end
985
+ end
986
+
987
+ def self.cache_key(class_name, id)
988
+ return class_name + "/" + id.to_s
989
+ end
990
+
991
+ @@debug=""
992
+
993
+ def self.debug
994
+ @@debug
995
+ end
996
+
997
+ def self.sanitize_sql(*params)
998
+ return ActiveRecord::Base.sanitize_sql(*params)
999
+ end
1000
+
1001
+ def self.table_name
1002
+ return domain
1003
+ end
1004
+
1005
+ def changed
1006
+ return @dirty.keys
1007
+ end
1008
+
1009
+ def changed?
1010
+ return @dirty.size > 0
1011
+ end
1012
+
1013
+ def changes
1014
+ ret = {}
1015
+ #puts 'in CHANGES=' + @dirty.inspect
1016
+ @dirty.each_pair {|key, value| ret[key] = [value, get_attribute(key)]}
1017
+ return ret
1018
+ end
1019
+
1020
+ def mark_as_old
1021
+ super
1022
+ @dirty = {}
1023
+ end
1024
+
1025
+ end
1026
+
1027
+ class SimpleRecord_errors
1028
+ def initialize(*params)
1029
+ super(*params)
1030
+ @errors=[]
1031
+ end
1032
+
1033
+ def add_to_base(value)
1034
+ @errors+=[value]
1035
+ end
1036
+
1037
+ def add(attribute, value)
1038
+ @errors+=["#{attribute.to_s} #{value}"]
1039
+ end
1040
+
1041
+ def count
1042
+ return length
1043
+ end
1044
+
1045
+ def length
1046
+ return @errors.length
1047
+ end
1048
+
1049
+ def size
1050
+ return length
1051
+ end
1052
+
1053
+ def full_messages
1054
+ return @errors
1055
+ end
1056
+
1057
+ def clear
1058
+ @errors.clear
1059
+ end
1060
+
1061
+ def empty?
1062
+ @errors.empty?
1063
+ end
1064
+ end
1065
+
1066
+ class Activerecordtosdb_subrecord_array
1067
+ def initialize(subname, referencename, referencevalue)
1068
+ @subname=subname.classify
1069
+ @referencename=referencename.tableize.singularize + "_id"
1070
+ @referencevalue=referencevalue
1071
+ end
1072
+
1073
+ # Performance optimization if you know the array should be empty
1074
+
1075
+ def init_empty
1076
+ @records = []
1077
+ end
1078
+
1079
+ def load
1080
+ if @records.nil?
1081
+ @records = find_all
1082
+ end
1083
+ return @records
1084
+ end
1085
+
1086
+ def [](key)
1087
+ return load[key]
1088
+ end
1089
+
1090
+ def <<(ob)
1091
+ return load << ob
1092
+ end
1093
+
1094
+ def count
1095
+ return load.count
1096
+ end
1097
+
1098
+ def size
1099
+ return count
1100
+ end
1101
+
1102
+ def each(*params, &block)
1103
+ return load.each(*params){|record| block.call(record)}
1104
+ end
1105
+
1106
+ def find_all(*params)
1107
+ find(:all, *params)
1108
+ end
1109
+
1110
+ def empty?
1111
+ return load.empty?
1112
+ end
1113
+
1114
+ def build(*params)
1115
+ params[0][@referencename]=@referencevalue
1116
+ eval(@subname).new(*params)
1117
+ end
1118
+
1119
+ def create(*params)
1120
+ params[0][@referencename]=@referencevalue
1121
+ record = eval(@subname).new(*params)
1122
+ record.save
1123
+ end
1124
+
1125
+ def find(*params)
1126
+ query=[:first, {}]
1127
+ #{:conditions=>"id=>1"}
1128
+ if params[0]
1129
+ if params[0]==:all
1130
+ query[0]=:all
1131
+ end
1132
+ end
1133
+
1134
+ if params[1]
1135
+ query[1]=params[1]
1136
+ if query[1][:conditions]
1137
+ query[1][:conditions]=SimpleRecord::Base.sanitize_sql(query[1][:conditions])+" AND "+ SimpleRecord::Base.sanitize_sql(["#{@referencename} = ?", @referencevalue])
1138
+ #query[1][:conditions]=Activerecordtosdb.sanitize_sql(query[1][:conditions])+" AND id='#{@id}'"
1139
+ else
1140
+ query[1][:conditions]=["#{@referencename} = ?", @referencevalue]
1141
+ #query[1][:conditions]="id='#{@id}'"
1142
+ end
1143
+ else
1144
+ query[1][:conditions]=["#{@referencename} = ?", @referencevalue]
1145
+ #query[1][:conditions]="id='#{@id}'"
1146
+ end
1147
+
1148
+ return eval(@subname).find(*query)
1149
+ end
1150
+
1151
+ end
1152
+
1153
+ class SimpleRecordError < StandardError
1154
+
1155
+ end
1156
+
1157
+ class RecordInvalid < SimpleRecordError
1158
+ attr_accessor :record
1159
+
1160
+ def initialize(record)
1161
+ @record = record
1162
+ end
1163
+ end
1164
+ end
1165
+