simple_record 1.1.37 → 1.1.40

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