simple_record 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +43 -11
- data/lib/simple_record.rb +62 -20
- data/lib/simple_record/active_sdb.rb +2 -2
- data/lib/simple_record/attributes.rb +39 -9
- data/test/my_model.rb +13 -2
- data/test/test_lobs.rb +57 -13
- metadata +3 -3
data/README.markdown
CHANGED
@@ -19,7 +19,7 @@ Brought to you by: [![Appoxy](http://www.simpledeployr.com/images/global/appoxy-
|
|
19
19
|
require 'simple_record'
|
20
20
|
|
21
21
|
class MyModel < SimpleRecord::Base
|
22
|
-
|
22
|
+
has_strings :name
|
23
23
|
has_ints :age
|
24
24
|
end
|
25
25
|
|
@@ -45,25 +45,26 @@ More about ModelAttributes below.
|
|
45
45
|
puts 'got=' + mm2.name + ' and he/she is ' + mm.age.to_s + ' years old'
|
46
46
|
# Or more advanced queries? mms = MyModel?.find(:all, ["age=?", 32], :order=>"name", :limit=>10)
|
47
47
|
|
48
|
+
That's literally all you need to do to get started. No database install, no other setup required.
|
48
49
|
|
49
50
|
## Attributes and modifiers for models
|
50
51
|
|
51
52
|
NOTE: All objects will automatically have :id, :created, :updated attributes.
|
52
53
|
|
53
|
-
###
|
54
|
+
### has_strings
|
54
55
|
|
55
56
|
Add string attributes.
|
56
57
|
|
57
58
|
class MyModel < SimpleRecord::Base
|
58
|
-
|
59
|
+
has_strings :name
|
59
60
|
end
|
60
61
|
|
61
62
|
### has_ints, has_dates and has_booleans
|
62
63
|
|
63
|
-
|
64
|
+
This is required because SimpleDB only has strings so SimpleRecord needs to know how to convert, pad, offset, etc.
|
64
65
|
|
65
66
|
class MyModel < SimpleRecord::Base
|
66
|
-
|
67
|
+
has_strings :name
|
67
68
|
has_ints :age, :height
|
68
69
|
has_dates :birthday
|
69
70
|
has_booleans :is_nerd
|
@@ -75,7 +76,7 @@ Creates a many-to-one relationship. Can only have one per belongs_to call.
|
|
75
76
|
|
76
77
|
class MyModel < SimpleRecord::Base
|
77
78
|
belongs_to :school
|
78
|
-
|
79
|
+
has_strings :name
|
79
80
|
has_ints :age, :height
|
80
81
|
has_dates :birthday
|
81
82
|
has_booleans :is_nerd
|
@@ -194,11 +195,21 @@ This is most helpful on windows so Rails doesn't need sqlite or mysql gems/drive
|
|
194
195
|
|
195
196
|
Typical databases support BLOB's and/or CLOB's, but SimpleDB has a 1024 character per attribute maximum so larger
|
196
197
|
values should be stored in S3. Fortunately SimpleRecord takes care of this for you by defining has_clobs for a large
|
197
|
-
string value.
|
198
|
+
string value. There is no support for blobs yet.
|
198
199
|
|
199
200
|
has_clobs :my_clob
|
200
201
|
|
201
|
-
These clob values will be stored in s3 under a bucket named
|
202
|
+
These clob values will be stored in s3 under a bucket named "#{aws_access_key}_lobs"
|
203
|
+
OR "simple_record_#{aws_access_key}/lobs" if you set :new_bucket=>true in establish_connection (RECOMMENDED).
|
204
|
+
|
205
|
+
If you have more than one clob on an object and if it makes sense for performance reasons, you can set a configuration option on the class to store all clobs
|
206
|
+
as one item on s3 which means it will do a single put to s3 and a single get for all the clobs on the object.
|
207
|
+
This would generally be good for somewhat small clob values or when you know you will always be accessing
|
208
|
+
all the clobs on the object.
|
209
|
+
|
210
|
+
sr_config :single_clob=>true
|
211
|
+
|
212
|
+
Setting this will automatically use :new_bucket=>true as well.
|
202
213
|
|
203
214
|
## Tips and Tricks and Things to Know
|
204
215
|
|
@@ -236,20 +247,36 @@ or
|
|
236
247
|
|
237
248
|
o.something_id = x
|
238
249
|
|
239
|
-
|
250
|
+
Accessing the id can prevent a database call so if you only need the ID, then you
|
251
|
+
should use this.
|
252
|
+
|
253
|
+
## Batch Save
|
240
254
|
|
241
255
|
To do a batch save using SimpleDB's batch saving feature to improve performance, simply create your objects, add them to an array, then call:
|
242
256
|
|
243
257
|
MyClass.batch_save(object_list)
|
244
258
|
|
259
|
+
## Batch Delete
|
260
|
+
|
261
|
+
To do a batch delete using SimpleDB's batch delete feature to improve performance, simply create your objects, add them to an array, then call:
|
262
|
+
|
263
|
+
MyClass.batch_delete(object_list or list_of_ids)
|
264
|
+
|
265
|
+
## Operations across a Query
|
266
|
+
|
267
|
+
MyClass.delete_all(find_options)
|
268
|
+
MyClass.destroy_all(find_options)
|
269
|
+
|
270
|
+
find_options can include anything you'd add after a find(:all, find_options) including :conditions, :limit, etc.
|
271
|
+
|
245
272
|
## Caching
|
246
273
|
|
247
274
|
You can use any cache that supports the ActiveSupport::Cache::Store interface.
|
248
275
|
|
249
276
|
SimpleRecord::Base.cache_store = my_cache_store
|
250
277
|
|
251
|
-
If you want a simple in memory cache store, try: <http://gemcutter.org/gems/local_cache>.
|
252
|
-
|
278
|
+
If you want a simple in memory cache store that supports max cache size and expiration, try: <http://gemcutter.org/gems/local_cache>.
|
279
|
+
You can also use memcached or http://www.quetzall.com/cloudcache.
|
253
280
|
|
254
281
|
## Encryption
|
255
282
|
|
@@ -286,6 +313,11 @@ The :shards function should return a list of shard names, for example: ['CA', 'F
|
|
286
313
|
|
287
314
|
The :map function should return which shard name the object should be stored to.
|
288
315
|
|
316
|
+
When executing a find() operation, you can explicitly specify the shard(s) you'd like to find on. This is
|
317
|
+
particularly useful if you know in advance which shard the data will be in.
|
318
|
+
|
319
|
+
MyClass.find(:all, :conditions=>....., :shard=>["CA", "FL"])
|
320
|
+
|
289
321
|
You can see some [example classes here](https://github.com/appoxy/simple_record/blob/master/test/my_sharded_model.rb).
|
290
322
|
|
291
323
|
## Kudos
|
data/lib/simple_record.rb
CHANGED
@@ -434,6 +434,7 @@ module SimpleRecord
|
|
434
434
|
options[:domain] = sharded_domain
|
435
435
|
end
|
436
436
|
|
437
|
+
# todo: Instead of doing the domain_ok, below, pass in the new option to aws lib :create_domain=>true, does the same thing now
|
437
438
|
if super(options)
|
438
439
|
self.class.cache_results(self)
|
439
440
|
delete_niled(to_delete)
|
@@ -470,30 +471,51 @@ module SimpleRecord
|
|
470
471
|
def save_lobs(dirty=nil)
|
471
472
|
# puts 'dirty.inspect=' + dirty.inspect
|
472
473
|
dirty = @dirty if dirty.nil?
|
474
|
+
all_clobs = {}
|
475
|
+
dirty_clobs = {}
|
473
476
|
defined_attributes_local.each_pair do |k, v|
|
477
|
+
# collect up the clobs in case it's a single put
|
474
478
|
if v.type == :clob
|
475
|
-
|
479
|
+
val = @lobs[k]
|
480
|
+
all_clobs[k] = val
|
476
481
|
if dirty.include?(k.to_s)
|
477
|
-
|
478
|
-
val = @lobs[k]
|
479
|
-
# puts 'val=' + val.inspect
|
480
|
-
s3_bucket.put(s3_lob_id(k), val)
|
481
|
-
rescue Aws::AwsError => ex
|
482
|
-
if ex.include? /NoSuchBucket/
|
483
|
-
s3_bucket(true).put(s3_lob_id(k), val)
|
484
|
-
else
|
485
|
-
raise ex
|
486
|
-
end
|
487
|
-
end
|
488
|
-
SimpleRecord.stats.s3_puts += 1
|
482
|
+
dirty_clobs[k] = val
|
489
483
|
else
|
490
484
|
# puts 'NOT DIRTY'
|
491
485
|
end
|
492
486
|
|
493
487
|
end
|
494
488
|
end
|
489
|
+
if dirty_clobs.size > 0
|
490
|
+
if self.class.get_sr_config[:single_clob]
|
491
|
+
# all clobs in one chunk
|
492
|
+
# using json for now, could change later
|
493
|
+
val = all_clobs.to_json
|
494
|
+
puts 'val=' + val.inspect
|
495
|
+
put_lob(single_clob_id, val, :new_bucket=>true)
|
496
|
+
else
|
497
|
+
dirty_clobs.each_pair do |k, val|
|
498
|
+
put_lob(s3_lob_id(k), val)
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
end
|
504
|
+
|
505
|
+
def put_lob(k, val, options={})
|
506
|
+
begin
|
507
|
+
s3_bucket(false, options).put(k, val)
|
508
|
+
rescue Aws::AwsError => ex
|
509
|
+
if ex.include? /NoSuchBucket/
|
510
|
+
s3_bucket(true, options).put(k, val)
|
511
|
+
else
|
512
|
+
raise ex
|
513
|
+
end
|
514
|
+
end
|
515
|
+
SimpleRecord.stats.s3_puts += 1
|
495
516
|
end
|
496
517
|
|
518
|
+
|
497
519
|
def is_dirty?(name)
|
498
520
|
# todo: should change all the dirty stuff to symbols?
|
499
521
|
# puts '@dirty=' + @dirty.inspect
|
@@ -509,8 +531,15 @@ module SimpleRecord
|
|
509
531
|
Aws::S3.new(SimpleRecord.aws_access_key, SimpleRecord.aws_secret_key)
|
510
532
|
end
|
511
533
|
|
512
|
-
|
513
|
-
|
534
|
+
# options:
|
535
|
+
# :new_bucket => true/false. True if want to use new bucket. Defaults to false for backwards compatability.
|
536
|
+
def s3_bucket(create=false, options={})
|
537
|
+
s3.bucket(options[:new_bucket] || SimpleRecord.options[:new_bucket] ? s3_bucket_name2 : s3_bucket_name, create)
|
538
|
+
end
|
539
|
+
|
540
|
+
# this is the bucket that will be used going forward for anything related to s3
|
541
|
+
def s3_bucket_name2
|
542
|
+
"simple_record_#{SimpleRecord.aws_access_key}"
|
514
543
|
end
|
515
544
|
|
516
545
|
def s3_bucket_name
|
@@ -521,6 +550,10 @@ module SimpleRecord
|
|
521
550
|
self.id + "_" + name.to_s
|
522
551
|
end
|
523
552
|
|
553
|
+
def single_clob_id
|
554
|
+
"lobs/#{self.id}_single_clob"
|
555
|
+
end
|
556
|
+
|
524
557
|
def save!(options={})
|
525
558
|
save(options) || raise(RecordNotSaved)
|
526
559
|
end
|
@@ -639,6 +672,14 @@ module SimpleRecord
|
|
639
672
|
results
|
640
673
|
end
|
641
674
|
|
675
|
+
# Pass in an array of objects
|
676
|
+
def self.batch_delete(objects, options={})
|
677
|
+
if objects
|
678
|
+
# 25 item limit, we should maybe handle this limit in here.
|
679
|
+
connection.batch_delete_attributes @domain, objects.collect { |x| x.id }
|
680
|
+
end
|
681
|
+
end
|
682
|
+
|
642
683
|
#
|
643
684
|
# Usage: ClassName.delete id
|
644
685
|
#
|
@@ -646,9 +687,10 @@ module SimpleRecord
|
|
646
687
|
connection.delete_attributes(domain, id)
|
647
688
|
end
|
648
689
|
|
649
|
-
|
690
|
+
# Pass in the same OPTIONS you'd pass into a find(:all, OPTIONS)
|
691
|
+
def self.delete_all(options)
|
650
692
|
# could make this quicker by just getting item_names and deleting attributes rather than creating objects
|
651
|
-
obs = self.find(
|
693
|
+
obs = self.find(:all, options)
|
652
694
|
i = 0
|
653
695
|
obs.each do |a|
|
654
696
|
a.delete
|
@@ -657,8 +699,9 @@ module SimpleRecord
|
|
657
699
|
return i
|
658
700
|
end
|
659
701
|
|
660
|
-
|
661
|
-
|
702
|
+
# Pass in the same OPTIONS you'd pass into a find(:all, OPTIONS)
|
703
|
+
def self.destroy_all(options)
|
704
|
+
obs = self.find(:all, options)
|
662
705
|
i = 0
|
663
706
|
obs.each do |a|
|
664
707
|
a.destroy
|
@@ -681,7 +724,6 @@ module SimpleRecord
|
|
681
724
|
end
|
682
725
|
|
683
726
|
|
684
|
-
|
685
727
|
def delete_niled(to_delete)
|
686
728
|
# puts 'to_delete=' + to_delete.inspect
|
687
729
|
if to_delete.size > 0
|
@@ -631,10 +631,10 @@ module SimpleRecord
|
|
631
631
|
# This will currently return and's, or's and betweens. Doesn't hurt anything, but could remove.
|
632
632
|
def parse_condition_fields(conditions)
|
633
633
|
return nil unless conditions && conditions.present?
|
634
|
-
rx = /\b(\w*)[\s|>=|<=|!=|=|>|<|like]/
|
634
|
+
rx = /\b(\w*)[\s|>=|<=|!=|=|>|<|like|between]/
|
635
635
|
fields = conditions[0].scan(rx)
|
636
636
|
# puts 'condition_fields = ' + fields.inspect
|
637
|
-
fields
|
637
|
+
fields.flatten
|
638
638
|
end
|
639
639
|
|
640
640
|
end
|
@@ -22,6 +22,17 @@ module SimpleRecord
|
|
22
22
|
module ClassMethods
|
23
23
|
|
24
24
|
|
25
|
+
# Add configuration to this particular class.
|
26
|
+
# :single_clob=> true/false. If true will store all clobs as a single object in s3. Default is false.
|
27
|
+
def sr_config(options={})
|
28
|
+
get_sr_config
|
29
|
+
@sr_config.merge!(options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_sr_config
|
33
|
+
@sr_config ||= {}
|
34
|
+
end
|
35
|
+
|
25
36
|
def defined_attributes
|
26
37
|
@attributes ||= {}
|
27
38
|
@attributes
|
@@ -329,15 +340,34 @@ module SimpleRecord
|
|
329
340
|
end
|
330
341
|
# get it from s3
|
331
342
|
unless new_record?
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
343
|
+
if self.class.get_sr_config[:single_clob]
|
344
|
+
begin
|
345
|
+
single_clob = s3_bucket(false, :new_bucket=>true).get(single_clob_id)
|
346
|
+
single_clob = JSON.parse(single_clob)
|
347
|
+
puts "single_clob=" + single_clob.inspect
|
348
|
+
single_clob.each_pair do |name2,val|
|
349
|
+
@lobs[name2.to_sym] = val
|
350
|
+
end
|
351
|
+
ret = @lobs[name]
|
352
|
+
SimpleRecord.stats.s3_gets += 1
|
353
|
+
rescue Aws::AwsError => ex
|
354
|
+
if ex.include? /NoSuchKey/
|
355
|
+
ret = nil
|
356
|
+
else
|
357
|
+
raise ex
|
358
|
+
end
|
359
|
+
end
|
360
|
+
else
|
361
|
+
begin
|
362
|
+
ret = s3_bucket.get(s3_lob_id(name))
|
363
|
+
# puts 'got from s3 ' + ret.inspect
|
364
|
+
SimpleRecord.stats.s3_gets += 1
|
365
|
+
rescue Aws::AwsError => ex
|
366
|
+
if ex.include? /NoSuchKey/
|
367
|
+
ret = nil
|
368
|
+
else
|
369
|
+
raise ex
|
370
|
+
end
|
341
371
|
end
|
342
372
|
end
|
343
373
|
|
data/test/my_model.rb
CHANGED
@@ -8,7 +8,7 @@ class MyModel < MyBaseModel
|
|
8
8
|
has_booleans :cool
|
9
9
|
has_dates :birthday, :date1, :date2, :date3
|
10
10
|
|
11
|
-
has_clobs :clob1
|
11
|
+
has_clobs :clob1, :clob2
|
12
12
|
|
13
13
|
#callbacks
|
14
14
|
before_create :set_nickname
|
@@ -46,4 +46,15 @@ class MyModel < MyBaseModel
|
|
46
46
|
@@attributes
|
47
47
|
end
|
48
48
|
|
49
|
-
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
class SingleClobClass < SimpleRecord::Base
|
54
|
+
|
55
|
+
sr_config :single_clob=>true
|
56
|
+
|
57
|
+
has_strings :name
|
58
|
+
|
59
|
+
has_clobs :clob1, :clob2
|
60
|
+
end
|
data/test/test_lobs.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
require 'test/unit'
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
require_relative "../lib/simple_record"
|
3
|
+
require_relative "test_helpers"
|
4
|
+
require_relative "test_base"
|
5
5
|
require "yaml"
|
6
6
|
require 'aws'
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require 'active_support'
|
7
|
+
require_relative 'my_model'
|
8
|
+
require_relative 'my_child_model'
|
9
|
+
require_relative 'model_with_enc'
|
11
10
|
|
12
11
|
# Tests for SimpleRecord
|
13
12
|
#
|
@@ -15,36 +14,81 @@ require 'active_support'
|
|
15
14
|
class TestLobs < TestBase
|
16
15
|
|
17
16
|
|
18
|
-
def
|
17
|
+
def test_clobs
|
19
18
|
mm = MyModel.new
|
20
19
|
|
21
20
|
puts mm.clob1.inspect
|
22
21
|
assert mm.clob1.nil?
|
23
|
-
|
24
|
-
mm.name
|
25
|
-
mm.age
|
22
|
+
|
23
|
+
mm.name = "whatever"
|
24
|
+
mm.age = "1"
|
26
25
|
mm.clob1 = "0" * 2000
|
27
26
|
assert SimpleRecord.stats.s3_puts == 0
|
28
27
|
puts mm.inspect
|
29
28
|
mm.save
|
30
29
|
|
30
|
+
assert SimpleRecord.stats.s3_puts == 1
|
31
31
|
sleep 2
|
32
32
|
|
33
|
+
mm.clob1 = "1" * 2000
|
34
|
+
mm.clob2 = "2" * 2000
|
35
|
+
mm.save
|
36
|
+
assert SimpleRecord.stats.s3_puts == 3
|
37
|
+
|
33
38
|
mm2 = MyModel.find(mm.id)
|
34
39
|
assert mm.id == mm2.id
|
35
40
|
puts 'mm.clob1=' + mm.clob1.to_s
|
36
41
|
puts 'mm2.clob1=' + mm2.clob1.to_s
|
37
42
|
assert mm.clob1 == mm2.clob1
|
38
|
-
assert SimpleRecord.stats.s3_puts ==
|
43
|
+
assert SimpleRecord.stats.s3_puts == 3, "puts is #{SimpleRecord.stats.s3_puts}"
|
39
44
|
assert SimpleRecord.stats.s3_gets == 1, "gets is #{SimpleRecord.stats.s3_gets}"
|
40
45
|
mm2.clob1 # make sure it doesn't do another get
|
41
46
|
assert SimpleRecord.stats.s3_gets == 1
|
42
47
|
|
48
|
+
assert mm.clob2 == mm2.clob2
|
49
|
+
assert SimpleRecord.stats.s3_gets == 2
|
50
|
+
|
43
51
|
mm2.save
|
44
52
|
|
45
53
|
# shouldn't save twice if not dirty
|
46
|
-
|
54
|
+
assert SimpleRecord.stats.s3_puts == 3
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_single_clob
|
59
|
+
mm = SingleClobClass.new
|
60
|
+
|
61
|
+
puts mm.clob1.inspect
|
62
|
+
assert mm.clob1.nil?
|
63
|
+
|
64
|
+
mm.name = "whatever"
|
65
|
+
mm.clob1 = "0" * 2000
|
66
|
+
mm.clob2 = "2" * 2000
|
67
|
+
assert SimpleRecord.stats.s3_puts == 0
|
68
|
+
puts mm.inspect
|
69
|
+
mm.save
|
47
70
|
|
71
|
+
assert SimpleRecord.stats.s3_puts == 1
|
72
|
+
|
73
|
+
sleep 2
|
74
|
+
|
75
|
+
mm2 = SingleClobClass.find(mm.id)
|
76
|
+
assert mm.id == mm2.id
|
77
|
+
puts 'mm.clob1=' + mm.clob1.to_s
|
78
|
+
puts 'mm2.clob1=' + mm2.clob1.to_s
|
79
|
+
assert_equal mm.clob1, mm2.clob1
|
80
|
+
assert SimpleRecord.stats.s3_puts == 1, "puts is #{SimpleRecord.stats.s3_puts}"
|
81
|
+
assert SimpleRecord.stats.s3_gets == 1, "gets is #{SimpleRecord.stats.s3_gets}"
|
82
|
+
mm2.clob1 # make sure it doesn't do another get
|
83
|
+
assert SimpleRecord.stats.s3_gets == 1
|
84
|
+
|
85
|
+
assert mm.clob2 == mm2.clob2
|
86
|
+
assert SimpleRecord.stats.s3_gets == 1
|
87
|
+
|
88
|
+
mm2.save
|
89
|
+
|
90
|
+
# shouldn't save twice if not dirty
|
91
|
+
assert SimpleRecord.stats.s3_puts == 1
|
48
92
|
end
|
49
93
|
|
50
94
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 2
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 2.0.
|
8
|
+
- 1
|
9
|
+
version: 2.0.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Travis Reeder
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2010-12-
|
19
|
+
date: 2010-12-22 00:00:00 -08:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|