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 +35 -8
- data/lib/simple_record.rb +1342 -1165
- data/lib/{callbacks.rb → simple_record/callbacks.rb} +22 -21
- data/lib/simple_record/encryptor.rb +54 -0
- data/lib/simple_record/errors.rb +17 -0
- data/lib/simple_record/password.rb +56 -0
- data/lib/{results_array.rb → simple_record/results_array.rb} +1 -0
- data/lib/{stats.rb → simple_record/stats.rb} +16 -16
- data/test/dirty_test.rb +52 -0
- data/test/model_with_enc.rb +7 -0
- data/test/my_base_model.rb +1 -0
- data/test/temp_test.rb +63 -63
- data/test/test_simple_record.rb +64 -15
- metadata +10 -5
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
|
|
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
|
-
|
|
27
|
-
require '
|
|
28
|
-
|
|
29
|
-
require
|
|
30
|
-
require File.expand_path(File.dirname(__FILE__) + "/
|
|
31
|
-
require File.expand_path(File.dirname(__FILE__) + "/callbacks")
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
#we first
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
=
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
#puts '
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
end
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
#
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
#
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
#
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
def
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
#
|
|
652
|
-
|
|
653
|
-
#
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
if
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
#
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
end
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
end
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
def
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
end
|
|
875
|
-
|
|
876
|
-
def
|
|
877
|
-
super
|
|
878
|
-
end
|
|
879
|
-
|
|
880
|
-
def
|
|
881
|
-
return
|
|
882
|
-
end
|
|
883
|
-
|
|
884
|
-
def
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
end
|
|
1032
|
-
|
|
1033
|
-
def
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
end
|
|
1040
|
-
|
|
1041
|
-
def
|
|
1042
|
-
|
|
1043
|
-
end
|
|
1044
|
-
|
|
1045
|
-
def
|
|
1046
|
-
return
|
|
1047
|
-
end
|
|
1048
|
-
|
|
1049
|
-
def
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
#
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
def
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
#
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
+
|