simple_record 1.5.4 → 1.5.6

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/lib/simple_record.rb CHANGED
@@ -41,24 +41,25 @@ require File.expand_path(File.dirname(__FILE__) + "/simple_record/rails2")
41
41
  require File.expand_path(File.dirname(__FILE__) + "/simple_record/results_array")
42
42
  require File.expand_path(File.dirname(__FILE__) + "/simple_record/stats")
43
43
  require File.expand_path(File.dirname(__FILE__) + "/simple_record/translations")
44
+ require_relative 'simple_record/sharding'
44
45
 
45
46
 
46
47
  module SimpleRecord
47
48
 
48
- @@options = {}
49
- @@stats = SimpleRecord::Stats.new
50
- @@logging = false
51
- @@s3 = nil
49
+ @@options = {}
50
+ @@stats = SimpleRecord::Stats.new
51
+ @@logging = false
52
+ @@s3 = nil
52
53
  @@auto_close_s3 = false
53
- @@logger = Logger.new(STDOUT)
54
- @@logger.level = Logger::INFO
54
+ @@logger = Logger.new(STDOUT)
55
+ @@logger.level = Logger::INFO
55
56
 
56
57
  class << self;
57
58
  attr_accessor :aws_access_key, :aws_secret_key
58
59
 
59
60
  # Deprecated
60
61
  def enable_logging
61
- @@logging = true
62
+ @@logging = true
62
63
  @@logger.level = Logger::DEBUG
63
64
  end
64
65
 
@@ -127,7 +128,7 @@ module SimpleRecord
127
128
  if options[:connection_mode] == :per_thread
128
129
  @@auto_close_s3 = true
129
130
  # todo: should we init this only when needed?
130
- @@s3 = Aws::S3.new(SimpleRecord.aws_access_key, SimpleRecord.aws_secret_key, {:connection_mode=>:per_thread})
131
+ @@s3 = Aws::S3.new(SimpleRecord.aws_access_key, SimpleRecord.aws_secret_key, {:connection_mode=>:per_thread})
131
132
  end
132
133
  end
133
134
 
@@ -139,7 +140,7 @@ module SimpleRecord
139
140
  @@s3.close_connection if @@auto_close_s3
140
141
  end
141
142
 
142
- # If you'd like to specify the s3 connection to use for LOBs, you can pass it in here.
143
+ # If you'd like to specify the s3 connection to use for LOBs, you can pass it in here.
143
144
  # We recommend that this connection matches the type of connection you're using for SimpleDB,
144
145
  # at least if you're using per_thread connection mode.
145
146
  def s3=(s3)
@@ -172,6 +173,8 @@ module SimpleRecord
172
173
  include SimpleRecord::Translations
173
174
  # include SimpleRecord::Attributes
174
175
  extend SimpleRecord::Attributes
176
+ extend SimpleRecord::Sharding::ClassMethods
177
+ include SimpleRecord::Sharding
175
178
  include SimpleRecord::Callbacks
176
179
  include SimpleRecord::Json
177
180
  include SimpleRecord::Logging
@@ -197,13 +200,13 @@ module SimpleRecord
197
200
  #we have to handle the virtuals.
198
201
  Attributes.handle_virtuals(attrs)
199
202
 
200
- @errors=SimpleRecord_errors.new if not (defined?(ActiveModel))
201
- @dirty = {}
203
+ @errors=SimpleRecord_errors.new if not (defined?(ActiveModel))
204
+ @dirty = {}
202
205
 
203
- @attributes = {} # sdb values
206
+ @attributes = {} # sdb values
204
207
  @attributes_rb = {} # ruby values
205
- @lobs = {}
206
- @new_record = true
208
+ @lobs = {}
209
+ @new_record = true
207
210
 
208
211
  end
209
212
 
@@ -232,7 +235,6 @@ module SimpleRecord
232
235
  end
233
236
 
234
237
 
235
-
236
238
  def defined_attributes_local
237
239
  # todo: store this somewhere so it doesn't keep going through this
238
240
  ret = self.class.defined_attributes
@@ -240,8 +242,6 @@ module SimpleRecord
240
242
  end
241
243
 
242
244
 
243
-
244
-
245
245
  class << self;
246
246
  attr_accessor :domain_prefix
247
247
  end
@@ -297,7 +297,7 @@ module SimpleRecord
297
297
 
298
298
  def get_attribute_sdb(name)
299
299
  name = name.to_sym
300
- ret = strip_array(@attributes[sdb_att_name(name)])
300
+ ret = strip_array(@attributes[sdb_att_name(name)])
301
301
  return ret
302
302
  end
303
303
 
@@ -307,7 +307,7 @@ module SimpleRecord
307
307
  end
308
308
 
309
309
  def get_att_meta(name)
310
- name_s = name.to_s
310
+ name_s = name.to_s
311
311
  att_meta = defined_attributes_local[name.to_sym]
312
312
  if att_meta.nil? && has_id_on_end(name_s)
313
313
  att_meta = defined_attributes_local[name_s[0..-4].to_sym]
@@ -339,7 +339,7 @@ module SimpleRecord
339
339
 
340
340
  def make_dirty(arg, value)
341
341
  sdb_att_name = sdb_att_name(arg)
342
- arg = arg.to_s
342
+ arg = arg.to_s
343
343
 
344
344
  # puts "Marking #{arg} dirty with #{value}" if SimpleRecord.logging?
345
345
  if @dirty.include?(sdb_att_name)
@@ -422,7 +422,7 @@ module SimpleRecord
422
422
  return true if @dirty.size == 0 # Nothing to save so skip it
423
423
  end
424
424
  is_create = self[:id].nil?
425
- ok = pre_save(options)
425
+ ok = pre_save(options) # Validates and sets ID
426
426
  if ok
427
427
  begin
428
428
  dirty = @dirty
@@ -432,13 +432,14 @@ module SimpleRecord
432
432
  return true if @dirty.size == 0 # This should probably never happen because after pre_save, created/updated dates are changed
433
433
  options[:dirty_atts] = @dirty
434
434
  end
435
- to_delete = get_atts_to_delete
435
+ to_delete = get_atts_to_delete
436
436
  SimpleRecord.stats.saves += 1
437
- # puts 'SELF BEFORE super=' + self.inspect
438
- # puts 'dirty before2=' + @dirty.inspect
437
+
438
+ if self.class.is_sharded?
439
+ options[:domain] = sharded_domain
440
+ end
441
+
439
442
  if super(options)
440
- # puts 'dirty super=' + @dirty.inspect
441
- # puts 'SELF AFTER super=' + self.inspect
442
443
  self.class.cache_results(self)
443
444
  delete_niled(to_delete)
444
445
  save_lobs(dirty)
@@ -564,7 +565,7 @@ module SimpleRecord
564
565
  def pre_save(options)
565
566
 
566
567
  is_create = self[:id].nil?
567
- ok = run_before_validation && (is_create ? run_before_validation_on_create : run_before_validation_on_update)
568
+ ok = run_before_validation && (is_create ? run_before_validation_on_create : run_before_validation_on_update)
568
569
  return false unless ok
569
570
 
570
571
  validate()
@@ -594,6 +595,7 @@ module SimpleRecord
594
595
  # Now translate all fields into SimpleDB friendly strings
595
596
  # convert_all_atts_to_sdb()
596
597
  end
598
+ prepare_for_update
597
599
  ok
598
600
  end
599
601
 
@@ -652,7 +654,7 @@ module SimpleRecord
652
654
  def self.delete_all(*params)
653
655
  # could make this quicker by just getting item_names and deleting attributes rather than creating objects
654
656
  obs = self.find(params)
655
- i = 0
657
+ i = 0
656
658
  obs.each do |a|
657
659
  a.delete
658
660
  i+=1
@@ -662,7 +664,7 @@ module SimpleRecord
662
664
 
663
665
  def self.destroy_all(*params)
664
666
  obs = self.find(params)
665
- i = 0
667
+ i = 0
666
668
  obs.each do |a|
667
669
  a.destroy
668
670
  i+=1
@@ -672,7 +674,11 @@ module SimpleRecord
672
674
 
673
675
  def delete()
674
676
  # TODO: DELETE CLOBS, etc from s3
675
- super
677
+ options = {}
678
+ if self.class.is_sharded?
679
+ options[:domain] = sharded_domain
680
+ end
681
+ super(options)
676
682
  end
677
683
 
678
684
  def destroy
@@ -685,8 +691,8 @@ module SimpleRecord
685
691
  def get_attribute(name)
686
692
  # puts "GET #{arg}"
687
693
  # Check if this arg is already converted
688
- name_s = name.to_s
689
- name = name.to_sym
694
+ name_s = name.to_s
695
+ name = name.to_sym
690
696
  att_meta = get_att_meta(name)
691
697
  # puts "att_meta for #{name}: " + att_meta.inspect
692
698
  if att_meta && att_meta.type == :clob
@@ -702,7 +708,7 @@ module SimpleRecord
702
708
  # get it from s3
703
709
  unless new_record?
704
710
  begin
705
- ret = s3_bucket.get(s3_lob_id(name))
711
+ ret = s3_bucket.get(s3_lob_id(name))
706
712
  # puts 'got from s3 ' + ret.inspect
707
713
  SimpleRecord.stats.s3_gets += 1
708
714
  rescue Aws::AwsError => ex
@@ -725,8 +731,8 @@ module SimpleRecord
725
731
  ret = @attributes_rb[name_s] # instance_variable_get(instance_var)
726
732
  return ret unless ret.nil?
727
733
  return nil if ret.is_a? RemoteNil
728
- ret = get_attribute_sdb(name)
729
- ret = sdb_to_ruby(name, ret)
734
+ ret = get_attribute_sdb(name)
735
+ ret = sdb_to_ruby(name, ret)
730
736
  @attributes_rb[name_s] = ret
731
737
  return ret
732
738
  end
@@ -736,22 +742,22 @@ module SimpleRecord
736
742
  def set(name, value, dirtify=true)
737
743
  # puts "SET #{name}=#{value.inspect}" if SimpleRecord.logging?
738
744
  # puts "self=" + self.inspect
739
- attname = name.to_s # default attname
740
- name = name.to_sym
741
- att_meta = get_att_meta(name)
745
+ attname = name.to_s # default attname
746
+ name = name.to_sym
747
+ att_meta = get_att_meta(name)
742
748
  store_rb_val = false
743
749
  if att_meta.nil?
744
750
  # check if it ends with id and see if att_meta is there
745
751
  ends_with = name.to_s[-3, 3]
746
752
  if ends_with == "_id"
747
753
  # puts 'ends with id'
748
- n2 = name.to_s[0, name.length-3]
754
+ n2 = name.to_s[0, name.length-3]
749
755
  # puts 'n2=' + n2
750
756
  att_meta = defined_attributes_local[n2.to_sym]
751
757
  # puts 'defined_attributes_local=' + defined_attributes_local.inspect
752
- attname = name.to_s
758
+ attname = name.to_s
753
759
  attvalue = value
754
- name = n2.to_sym
760
+ name = n2.to_sym
755
761
  end
756
762
  return if att_meta.nil?
757
763
  else
@@ -761,8 +767,8 @@ module SimpleRecord
761
767
  att_name = name.to_s
762
768
  attvalue = value
763
769
  else
764
- attname = name.to_s + '_id'
765
- attvalue = value.nil? ? nil : value.id
770
+ attname = name.to_s + '_id'
771
+ attvalue = value.nil? ? nil : value.id
766
772
  store_rb_val = true
767
773
  end
768
774
  elsif att_meta.type == :clob
@@ -770,7 +776,7 @@ module SimpleRecord
770
776
  @lobs[name] = value
771
777
  return
772
778
  else
773
- attname = name.to_s
779
+ attname = name.to_s
774
780
  attvalue = att_meta.init_value(value)
775
781
  # attvalue = value
776
782
  #puts 'converted ' + value.inspect + ' to ' + attvalue.inspect
@@ -779,7 +785,7 @@ module SimpleRecord
779
785
  attvalue = strip_array(attvalue)
780
786
  make_dirty(name, attvalue) if dirtify
781
787
  # puts "ARG=#{attname.to_s} setting to #{attvalue}"
782
- sdb_val = ruby_to_sdb(name, attvalue)
788
+ sdb_val = ruby_to_sdb(name, attvalue)
783
789
  # puts "sdb_val=" + sdb_val.to_s
784
790
  @attributes[attname] = sdb_val
785
791
  # attvalue = wrap_if_required(name, attvalue, sdb_val)
@@ -834,7 +840,7 @@ module SimpleRecord
834
840
  if $&
835
841
  before=$`
836
842
  middle=$&
837
- after=$'
843
+ after =$'
838
844
 
839
845
  before =~ /'$/ #is there already a quote immediately before the match?
840
846
  unless $&
@@ -868,34 +874,43 @@ module SimpleRecord
868
874
  # :consistent_read => true/false -- as per http://developer.amazonwebservices.com/connect/entry.jspa?externalID=3572
869
875
  def self.find(*params)
870
876
  #puts 'params=' + params.inspect
871
- q_type = :all
872
- select_attributes=[]
873
877
 
878
+ q_type = :all
879
+ select_attributes=[]
874
880
  if params.size > 0
875
881
  q_type = params[0]
876
882
  end
883
+ options = {}
884
+ if params.size > 1
885
+ options = params[1]
886
+ end
887
+
888
+ if !options[:shard_find] && is_sharded?
889
+ # then break off and get results across all shards
890
+ return find_sharded(*params)
891
+ end
877
892
 
878
893
  # Pad and Offset number attributes
879
- options = {}
880
894
  params_dup = params.dup
881
895
  if params.size > 1
882
896
  options = params[1]
883
897
  #puts 'options=' + options.inspect
884
898
  #puts 'after collect=' + options.inspect
885
899
  convert_condition_params(options)
886
- per_token = options[:per_token]
900
+ per_token = options[:per_token]
887
901
  consistent_read = options[:consistent_read]
888
902
  if per_token || consistent_read then
889
- op_dup = options.dup
890
- op_dup[:limit] = per_token # simpledb uses Limit as a paging thing, not what is normal
903
+ op_dup = options.dup
904
+ op_dup[:limit] = per_token # simpledb uses Limit as a paging thing, not what is normal
891
905
  op_dup[:consistent_read] = consistent_read
892
- params_dup[1] = op_dup
906
+ params_dup[1] = op_dup
893
907
  end
894
908
  end
895
909
  # puts 'params2=' + params.inspect
896
910
 
897
- ret = q_type == :all ? [] : nil
911
+ ret = q_type == :all ? [] : nil
898
912
  begin
913
+
899
914
  results=find_with_metadata(*params_dup)
900
915
  # puts "RESULT=" + results.inspect
901
916
  write_usage(:select, domain, q_type, options, results)
@@ -951,14 +966,14 @@ module SimpleRecord
951
966
  def self.paginate(options={})
952
967
  # options = args.pop
953
968
  # puts 'paginate options=' + options.inspect if SimpleRecord.logging?
954
- page = options[:page] || 1
955
- per_page = options[:per_page] || 50
969
+ page = options[:page] || 1
970
+ per_page = options[:per_page] || 50
956
971
  # total = options[:total_entries].to_i
957
- options[:page] = page.to_i # makes sure it's to_i
972
+ options[:page] = page.to_i # makes sure it's to_i
958
973
  options[:per_page] = per_page.to_i
959
- options[:limit] = options[:page] * options[:per_page]
974
+ options[:limit] = options[:page] * options[:per_page]
960
975
  # puts 'paging options=' + options.inspect
961
- fr = find(:all, options)
976
+ fr = find(:all, options)
962
977
  return fr
963
978
 
964
979
  end
@@ -982,15 +997,15 @@ module SimpleRecord
982
997
  # todo: cache each result
983
998
  results.each do |item|
984
999
  class_name = item.class.name
985
- id = item.id
986
- cache_key = self.cache_key(class_name, id)
1000
+ id = item.id
1001
+ cache_key = self.cache_key(class_name, id)
987
1002
  #puts 'caching result at ' + cache_key + ': ' + results.inspect
988
1003
  cache_store.write(cache_key, item, :expires_in =>30)
989
1004
  end
990
1005
  else
991
1006
  class_name = results.class.name
992
- id = results.id
993
- cache_key = self.cache_key(class_name, id)
1007
+ id = results.id
1008
+ cache_key = self.cache_key(class_name, id)
994
1009
  #puts 'caching result at ' + cache_key + ': ' + results.inspect
995
1010
  cache_store.write(cache_key, results, :expires_in =>30)
996
1011
  end
@@ -1051,8 +1066,8 @@ module SimpleRecord
1051
1066
 
1052
1067
  class Activerecordtosdb_subrecord_array
1053
1068
  def initialize(subname, referencename, referencevalue)
1054
- @subname=subname.classify
1055
- @referencename=referencename.tableize.singularize + "_id"
1069
+ @subname =subname.classify
1070
+ @referencename =referencename.tableize.singularize + "_id"
1056
1071
  @referencevalue=referencevalue
1057
1072
  end
1058
1073
 
@@ -1104,7 +1119,7 @@ module SimpleRecord
1104
1119
 
1105
1120
  def create(*params)
1106
1121
  params[0][@referencename]=@referencevalue
1107
- record = eval(@subname).new(*params)
1122
+ record = eval(@subname).new(*params)
1108
1123
  record.save
1109
1124
  end
1110
1125
 
@@ -966,9 +966,9 @@ module SimpleRecord
966
966
  # sandy.reload
967
967
  # puts sandy.inspect #=> #<Client:0xb7761d28 @attributes={}, @new_record=false>
968
968
  #
969
- def delete
969
+ def delete(options={})
970
970
  raise_on_id_absence
971
- connection.delete_attributes(domain, id)
971
+ connection.delete_attributes(options[:domain] || domain, id)
972
972
  end
973
973
 
974
974
  # Item ID
@@ -5,7 +5,7 @@ module SimpleRecord
5
5
  class ResultsArray
6
6
  include Enumerable
7
7
 
8
- attr_reader :next_token, :clz, :params, :items, :i, :box_usage, :request_id
8
+ attr_reader :next_token, :clz, :params, :items, :index, :box_usage, :request_id
9
9
 
10
10
 
11
11
  def initialize(clz=nil, params=[], results=nil, next_token=nil)
@@ -27,6 +27,7 @@ module SimpleRecord
27
27
  load_to(@options[:per_page] * @options[:page])
28
28
  @start_at = @options[:per_page] * (@options[:page] - 1)
29
29
  end
30
+ @index = 0
30
31
  # puts 'RESULTS_ARRAY=' + self.inspect
31
32
  end
32
33
 
@@ -96,6 +97,7 @@ module SimpleRecord
96
97
  params_for_count[1] = params_for_count[1].dup # for deep clone
97
98
  params_for_count[1].delete(:limit)
98
99
  params_for_count[1].delete(:per_token)
100
+ params_for_count[1][:called_by] = :results_array
99
101
 
100
102
  # puts '@params2=' + @params.inspect
101
103
  # puts 'params_for_count=' + params_for_count.inspect
@@ -127,6 +129,7 @@ module SimpleRecord
127
129
  # puts "i=" + i.to_s
128
130
  yield v
129
131
  i += 1
132
+ @index += 1
130
133
  if !limit.nil? && i >= limit
131
134
  return
132
135
  end
@@ -194,6 +197,7 @@ module SimpleRecord
194
197
  def load_next_token_set
195
198
  options = @params[1]
196
199
  options[:next_token] = @next_token
200
+ options[:called_by] = :results_array
197
201
  res = @clz.find(*@params)
198
202
  @currentset_items = res.items # get the real items array from the ResultsArray
199
203
  @currentset_items.each do |item|
@@ -0,0 +1,273 @@
1
+ module SimpleRecord
2
+
3
+ module Sharding
4
+
5
+ def self.included(base)
6
+ # base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def shard(options=nil)
12
+ @sharding_options = options
13
+ end
14
+
15
+ def sharding_options
16
+ @sharding_options
17
+ end
18
+
19
+ def is_sharded?
20
+ @sharding_options
21
+ end
22
+
23
+ def find_sharded(*params)
24
+ puts 'find_sharded ' + params.inspect
25
+
26
+ options = params.size > 1 ? params[1] : {}
27
+
28
+ domains = sharded_domains
29
+ puts "sharded_domains=" + domains.inspect
30
+
31
+ single = false
32
+ case params.first
33
+ when nil then
34
+ raise "Invalid parameters passed to find: nil."
35
+ when :all, :first, :count
36
+ # nada
37
+ else # single id
38
+ unless params.first.is_a?(Array)
39
+ single = true
40
+ end
41
+ end
42
+
43
+ results = ShardedResults.new(params)
44
+ domains.each do |d|
45
+ p2 = params.dup
46
+ op2 = options.dup
47
+ op2[:from] = d
48
+ op2[:shard_find] = true
49
+ p2[1] = op2
50
+ rs = find(*p2)
51
+ if params.first == :first || single
52
+ return rs if rs
53
+ else
54
+ results.add_results rs
55
+ end
56
+ end
57
+ puts 'results=' + results.inspect
58
+ if params.first == :first || single
59
+ # Then we found nothing by this point so return nil
60
+ return nil
61
+ elsif params.first == :count
62
+ return results.sum_count
63
+ end
64
+ results
65
+
66
+ end
67
+
68
+ def shards
69
+ send(sharding_options[:shards])
70
+ end
71
+
72
+ def sharded_domains
73
+ sharded_domains = []
74
+ shard_names = shards
75
+ shard_names.each do |s|
76
+ sharded_domains << "#{domain}_#{s}"
77
+ end
78
+ sharded_domains
79
+ end
80
+ end
81
+
82
+
83
+ def sharded_domain
84
+ # puts 'getting sharded_domain'
85
+ options = self.class.sharding_options
86
+ # val = self.send(options[:on])
87
+ # puts "val=" + val.inspect
88
+ # shards = options[:shards] # is user passed in static array of shards
89
+ # if options[:shards].is_a?(Symbol)
90
+ # shards = self.send(shards)
91
+ # end
92
+ sharded_domain = "#{domain}_#{self.send(options[:map])}"
93
+ puts "sharded_domain=" + sharded_domain.inspect
94
+ sharded_domain
95
+ end
96
+
97
+ class ShardedResults
98
+ include Enumerable
99
+
100
+ def initialize(params)
101
+ @params = params
102
+ @options = params.size > 1 ? params[1] : {}
103
+ @results_arrays = []
104
+ end
105
+
106
+ def add_results(rs)
107
+ # puts 'adding results=' + rs.inspect
108
+ @results_arrays << rs
109
+ end
110
+
111
+ # only used for count queries
112
+ def sum_count
113
+ x = 0
114
+ @results_arrays.each do |rs|
115
+ x += rs if rs
116
+ end
117
+ x
118
+ end
119
+
120
+ def <<(val)
121
+ raise "Not supported."
122
+ end
123
+
124
+ def element_at(index)
125
+ @results_arrays.each do |rs|
126
+ if rs.size > index
127
+ return rs[index]
128
+ end
129
+ index -= rs.size
130
+ end
131
+ end
132
+
133
+ def [](*i)
134
+ if i.size == 1
135
+ # puts '[] i=' + i.to_s
136
+ index = i[0]
137
+ return element_at(index)
138
+ else
139
+ offset = i[0]
140
+ rows = i[1]
141
+ ret = []
142
+ x = offset
143
+ while x < (offset+rows)
144
+ ret << element_at(x)
145
+ x+=1
146
+ end
147
+ ret
148
+ end
149
+ end
150
+
151
+ def first
152
+ @results_arrays.first.first
153
+ end
154
+
155
+ def last
156
+ @results_arrays.last.last
157
+ end
158
+
159
+ def empty?
160
+ @results_arrays.each do |rs|
161
+ return false if !rs.empty?
162
+ end
163
+ true
164
+ end
165
+
166
+ def include?(obj)
167
+ @results_arrays.each do |rs|
168
+ x = rs.include?(obj)
169
+ return true if x
170
+ end
171
+ false
172
+ end
173
+
174
+ def size
175
+ return @size if @size
176
+ s = 0
177
+ @results_arrays.each do |rs|
178
+ # puts 'rs=' + rs.inspect
179
+ # puts 'rs.size=' + rs.size.inspect
180
+ s += rs.size
181
+ end
182
+ @size = s
183
+ s
184
+ end
185
+
186
+ def length
187
+ return size
188
+ end
189
+
190
+ def each(&blk)
191
+ i = 0
192
+ @results_arrays.each do |rs|
193
+ rs.each(&blk)
194
+ i+=1
195
+ end
196
+ end
197
+
198
+ # for will_paginate support
199
+ def total_pages
200
+ # puts 'total_pages'
201
+ # puts @params[1][:per_page].to_s
202
+ return 1 if @params[1][:per_page].nil?
203
+ ret = (size / @params[1][:per_page].to_f).ceil
204
+ #puts 'ret=' + ret.to_s
205
+ ret
206
+ end
207
+
208
+ def current_page
209
+ return query_options[:page] || 1
210
+ end
211
+
212
+ def query_options
213
+ return @options
214
+ end
215
+
216
+ def total_entries
217
+ return size
218
+ end
219
+
220
+ # Helper method that is true when someone tries to fetch a page with a
221
+ # larger number than the last page. Can be used in combination with flashes
222
+ # and redirecting.
223
+ def out_of_bounds?
224
+ current_page > total_pages
225
+ end
226
+
227
+ # Current offset of the paginated collection. If we're on the first page,
228
+ # it is always 0. If we're on the 2nd page and there are 30 entries per page,
229
+ # the offset is 30. This property is useful if you want to render ordinals
230
+ # side by side with records in the view: simply start with offset + 1.
231
+ def offset
232
+ (current_page - 1) * per_page
233
+ end
234
+
235
+ # current_page - 1 or nil if there is no previous page
236
+ def previous_page
237
+ current_page > 1 ? (current_page - 1) : nil
238
+ end
239
+
240
+ # current_page + 1 or nil if there is no next page
241
+ def next_page
242
+ current_page < total_pages ? (current_page + 1) : nil
243
+ end
244
+
245
+
246
+ def delete(item)
247
+ raise "Not supported"
248
+ end
249
+
250
+ def delete_at(index)
251
+ raise "Not supported"
252
+ end
253
+
254
+ end
255
+
256
+ # Some hashing algorithms
257
+ module Hashing
258
+ def self.sdbm_hash(str, len=str.length)
259
+ # puts 'sdbm_hash ' + str.inspect
260
+ hash = 0
261
+ len.times { |i|
262
+ c = str[i]
263
+ # puts "c=" + c.class.name + "--" + c.inspect + " -- " + c.ord.inspect
264
+ c = c.ord
265
+ hash = c + (hash << 6) + (hash << 16) - hash
266
+ }
267
+ # puts "hash=" + hash.inspect
268
+ return hash
269
+ end
270
+ end
271
+ end
272
+
273
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/simple_record")
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/simple_record")
4
+
5
+ class MyShardedModel < SimpleRecord::Base
6
+
7
+ shard :shards=>:my_shards, :map=>:my_mapping_function
8
+
9
+ has_strings :name
10
+
11
+ def self.my_shards
12
+ Array(0...4)
13
+ end
14
+
15
+ def my_mapping_function
16
+ shard_num = SimpleRecord::Sharding::Hashing.sdbm_hash(self.id) % 4
17
+ puts "shard_num=" + shard_num.inspect
18
+ shard_num
19
+ end
20
+
21
+
22
+
23
+ end
24
+
25
+
26
+ class MyShardedByFieldModel < SimpleRecord::Base
27
+
28
+ shard :shards=>:my_shards, :map=>:my_mapping_function
29
+
30
+ has_strings :name, :state
31
+
32
+ def self.my_shards
33
+ ['AL', 'CA', 'FL', 'NY']
34
+ end
35
+
36
+ def my_mapping_function
37
+ state
38
+ end
39
+
40
+
41
+ end
data/test/test_base.rb CHANGED
@@ -19,6 +19,16 @@ class TestBase < Test::Unit::TestCase
19
19
  SimpleRecord.close_connection
20
20
  end
21
21
 
22
+ def delete_all(clz)
23
+ puts 'delete_all ' + clz.name
24
+ obs = clz.find(:all)
25
+ obs.each do |o|
26
+ o.delete
27
+ end
28
+ # @@sdb.select("select * from #{domain}") do |o|
29
+ # o.delete
30
+ # end
31
+ end
22
32
 
23
33
  def reset_connection
24
34
  puts 'reset_connection'
@@ -0,0 +1,127 @@
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")
5
+ require "yaml"
6
+ require 'aws'
7
+ require_relative 'my_sharded_model'
8
+
9
+ # Tests for SimpleRecord
10
+ #
11
+ class TestShards < TestBase
12
+
13
+ def setup
14
+ super
15
+ delete_all MyShardedModel
16
+ end
17
+
18
+ def teardown
19
+ super
20
+
21
+ end
22
+
23
+ # We'll want to shard based on ID's, user decides how many shards and some mapping function will
24
+ # be used to select the shard.
25
+ def test_id_sharding
26
+
27
+ mm = MyShardedModel.new(:name=>"single")
28
+ mm.save
29
+ sleep 1
30
+ puts 'finding by id'
31
+ mm2 = MyShardedModel.find(mm.id)
32
+ p mm2
33
+ assert_equal mm.id, mm2.id
34
+ puts 'deleting'
35
+ mm2.delete
36
+ sleep 1
37
+ mm3 = MyShardedModel.find(mm.id)
38
+ assert_nil mm3
39
+
40
+ puts "saving 20 now"
41
+ saved = []
42
+ 20.times do |i|
43
+ mm = MyShardedModel.new(:name=>"name #{i}")
44
+ mm.save
45
+ saved << mm
46
+ end
47
+
48
+ # todo: assert that we're actually sharding
49
+
50
+ puts "finding them all"
51
+ found = []
52
+ rs = MyShardedModel.find(:all)
53
+ rs.each do |m|
54
+ p m
55
+ found << m
56
+ end
57
+ saved.each do |so|
58
+ assert(found.find { |m1| m1.id == so.id })
59
+ end
60
+
61
+ puts "deleting all of them"
62
+ found.each do |fo|
63
+ fo.delete
64
+ end
65
+
66
+ puts "Now ensure that all are deleted"
67
+ rs = MyShardedModel.find(:all)
68
+ assert rs.size == 0
69
+
70
+ end
71
+
72
+ def test_field_sharding
73
+
74
+ states = MyShardedByFieldModel.shards
75
+ puts "states=" + states.inspect
76
+
77
+ mm = MyShardedByFieldModel.new(:name=>"single", :state=>"CA")
78
+ mm.save
79
+ sleep 1
80
+ puts 'finding by id'
81
+ mm2 = MyShardedByFieldModel.find(mm.id)
82
+ p mm2
83
+ assert_equal mm.id, mm2.id
84
+ puts 'deleting'
85
+ mm2.delete
86
+ sleep 1
87
+ mm3 = MyShardedByFieldModel.find(mm.id)
88
+ assert_nil mm3
89
+
90
+ puts "saving 20 now"
91
+ saved = []
92
+ 20.times do |i|
93
+ mm = MyShardedByFieldModel.new(:name=>"name #{i}", :state=>states[i % states.size])
94
+ mm.save
95
+ p mm
96
+ saved << mm
97
+ end
98
+
99
+ # todo: assert that we're actually sharding
100
+
101
+ puts "finding them all"
102
+ found = []
103
+ rs = MyShardedByFieldModel.find(:all)
104
+ rs.each do |m|
105
+ p m
106
+ found << m
107
+ end
108
+ saved.each do |so|
109
+ assert(found.find { |m1| m1.id == so.id })
110
+ end
111
+
112
+ puts "deleting all of them"
113
+ found.each do |fo|
114
+ fo.delete
115
+ end
116
+ sleep 1
117
+
118
+ puts "Now ensure that all are deleted"
119
+ rs = MyShardedByFieldModel.find(:all)
120
+ assert rs.size == 0
121
+ end
122
+
123
+ def test_time_sharding
124
+
125
+ end
126
+
127
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 1
7
7
  - 5
8
- - 4
9
- version: 1.5.4
8
+ - 6
9
+ version: 1.5.6
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-11-25 00:00:00 -08:00
19
+ date: 2010-12-01 00:00:00 -08:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -53,6 +53,7 @@ files:
53
53
  - lib/simple_record/password.rb
54
54
  - lib/simple_record/rails2.rb
55
55
  - lib/simple_record/results_array.rb
56
+ - lib/simple_record/sharding.rb
56
57
  - lib/simple_record/stats.rb
57
58
  - lib/simple_record/translations.rb
58
59
  - README.markdown
@@ -61,6 +62,7 @@ files:
61
62
  - test/my_base_model.rb
62
63
  - test/my_child_model.rb
63
64
  - test/my_model.rb
65
+ - test/my_sharded_model.rb
64
66
  - test/paging_array_test.rb
65
67
  - test/simple_record_test.rb
66
68
  - test/temp.rb
@@ -76,6 +78,7 @@ files:
76
78
  - test/test_pagination.rb
77
79
  - test/test_rails3.rb
78
80
  - test/test_results_array.rb
81
+ - test/test_shards.rb
79
82
  - test/test_usage.rb
80
83
  has_rdoc: true
81
84
  homepage: http://github.com/appoxy/simple_record/
@@ -115,6 +118,7 @@ test_files:
115
118
  - test/my_base_model.rb
116
119
  - test/my_child_model.rb
117
120
  - test/my_model.rb
121
+ - test/my_sharded_model.rb
118
122
  - test/paging_array_test.rb
119
123
  - test/simple_record_test.rb
120
124
  - test/temp.rb
@@ -130,4 +134,5 @@ test_files:
130
134
  - test/test_pagination.rb
131
135
  - test/test_rails3.rb
132
136
  - test/test_results_array.rb
137
+ - test/test_shards.rb
133
138
  - test/test_usage.rb