simple_record 1.1.37 → 1.1.40

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