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