simple_record 2.0.0 → 2.0.1
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 +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: [
|
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
|