simple_record 1.1.34 → 1.1.35

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.
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
+