simple_record 1.5.4 → 1.5.6

Sign up to get free protection for your applications and to get access to all the features.
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