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 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
- has_attributes :name
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
- ### has_attributes
54
+ ### has_strings
54
55
 
55
56
  Add string attributes.
56
57
 
57
58
  class MyModel < SimpleRecord::Base
58
- has_attributes :name
59
+ has_strings :name
59
60
  end
60
61
 
61
62
  ### has_ints, has_dates and has_booleans
62
63
 
63
- Lets simple_record know that certain attributes defined in has_attributes should be treated as integers, dates or booleans. This is required because SimpleDB only has strings so SimpleRecord needs to know how to convert, pad, offset, etc.
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
- has_attributes :name
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
- has_attributes :name
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: "#{aws_access_key}_lobs"
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
- ### Batch Save
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>. It supports max cache size and
252
- timeouts. You can also use memcached or http://www.quetzall.com/cloudcache.
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
- # puts 'storing clob '
479
+ val = @lobs[k]
480
+ all_clobs[k] = val
476
481
  if dirty.include?(k.to_s)
477
- begin
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
- def s3_bucket(create=false)
513
- s3.bucket(s3_bucket_name, create)
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
- def self.delete_all(*params)
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(params)
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
- def self.destroy_all(*params)
661
- obs = self.find(params)
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[0]
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
- begin
333
- ret = s3_bucket.get(s3_lob_id(name))
334
- # puts 'got from s3 ' + ret.inspect
335
- SimpleRecord.stats.s3_gets += 1
336
- rescue Aws::AwsError => ex
337
- if ex.include? /NoSuchKey/
338
- ret = nil
339
- else
340
- raise ex
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
- require File.join(File.dirname(__FILE__), "/../lib/simple_record")
3
- require File.join(File.dirname(__FILE__), "./test_helpers")
4
- require File.join(File.dirname(__FILE__), "./test_base")
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
- require 'my_model'
8
- require 'my_child_model'
9
- require 'model_with_enc'
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 test_blobs
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 = "whatever"
25
- mm.age = "1"
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 == 1, "puts is #{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
- assert SimpleRecord.stats.s3_puts == 1
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
- - 0
9
- version: 2.0.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-07 00:00:00 -08:00
19
+ date: 2010-12-22 00:00:00 -08:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency