simple_record 1.5.8 → 2.0.0

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
@@ -118,6 +118,10 @@ To find all:
118
118
 
119
119
  Company.find_all_by_name_and_division("Appoxy", "West")
120
120
 
121
+ Consistent read:
122
+
123
+ Company.find(:all, :conditions => ["created > ?", 10.days.ago], :order=>"name", :limit=>50, :consistent_read=>true)
124
+
121
125
  There are so many different combinations of the above for querying that I can't put them all here,
122
126
  but this should get you started.
123
127
 
@@ -244,7 +248,7 @@ You can use any cache that supports the ActiveSupport::Cache::Store interface.
244
248
 
245
249
  SimpleRecord::Base.cache_store = my_cache_store
246
250
 
247
- If you want a simple in memory cache store, try: http://gemcutter.org/gems/local_cache . It supports max cache size and
251
+ If you want a simple in memory cache store, try: <http://gemcutter.org/gems/local_cache>. It supports max cache size and
248
252
  timeouts. You can also use memcached or http://www.quetzall.com/cloudcache.
249
253
 
250
254
  ## Encryption
@@ -271,6 +275,19 @@ ob2.password == "mypassword"
271
275
 
272
276
  This will actually be compared by hashing "mypassword" first.
273
277
 
278
+ ## Sharding
279
+
280
+ Sharding allows you to partition your data for a single class across multiple domains allowing increased write throughput,
281
+ faster queries and more space (multiply your 10GB per domain limit). And it's very easy to implement with SimpleRecord.
282
+
283
+ shard :shards=>:my_shards_function, :map=>:my_mapping_function
284
+
285
+ The :shards function should return a list of shard names, for example: ['CA', 'FL', 'HI', ...] or [1,2,3,4,...]
286
+
287
+ The :map function should return which shard name the object should be stored to.
288
+
289
+ You can see some [example classes here](https://github.com/appoxy/simple_record/blob/master/test/my_sharded_model.rb).
290
+
274
291
  ## Kudos
275
292
 
276
293
  Special thanks to Garrett Cox for creating Activerecord2sdb which SimpleRecord is based on:
data/lib/simple_record.rb CHANGED
@@ -172,7 +172,8 @@ module SimpleRecord
172
172
 
173
173
  include SimpleRecord::Translations
174
174
  # include SimpleRecord::Attributes
175
- extend SimpleRecord::Attributes
175
+ extend SimpleRecord::Attributes::ClassMethods
176
+ include SimpleRecord::Attributes
176
177
  extend SimpleRecord::Sharding::ClassMethods
177
178
  include SimpleRecord::Sharding
178
179
  include SimpleRecord::Callbacks
@@ -295,12 +296,6 @@ module SimpleRecord
295
296
  domain_name_for_class
296
297
  end
297
298
 
298
- def get_attribute_sdb(name)
299
- name = name.to_sym
300
- ret = strip_array(@attributes[sdb_att_name(name)])
301
- return ret
302
- end
303
-
304
299
  def has_id_on_end(name_s)
305
300
  name_s = name_s.to_s
306
301
  name_s.length > 3 && name_s[-3..-1] == "_id"
@@ -324,7 +319,7 @@ module SimpleRecord
324
319
  end
325
320
 
326
321
  def strip_array(arg)
327
- if arg.class==Array
322
+ if arg.is_a? Array
328
323
  if arg.length==1
329
324
  ret = arg[0]
330
325
  else
@@ -686,122 +681,6 @@ module SimpleRecord
686
681
  end
687
682
 
688
683
 
689
- # Since SimpleDB supports multiple attributes per value, the values are an array.
690
- # This method will return the value unwrapped if it's the only, otherwise it will return the array.
691
- def get_attribute(name)
692
- # puts "GET #{arg}"
693
- # Check if this arg is already converted
694
- name_s = name.to_s
695
- name = name.to_sym
696
- att_meta = get_att_meta(name)
697
- # puts "att_meta for #{name}: " + att_meta.inspect
698
- if att_meta && att_meta.type == :clob
699
- ret = @lobs[name]
700
- # puts 'get_attribute clob ' + ret.inspect
701
- if ret
702
- if ret.is_a? RemoteNil
703
- return nil
704
- else
705
- return ret
706
- end
707
- end
708
- # get it from s3
709
- unless new_record?
710
- begin
711
- ret = s3_bucket.get(s3_lob_id(name))
712
- # puts 'got from s3 ' + ret.inspect
713
- SimpleRecord.stats.s3_gets += 1
714
- rescue Aws::AwsError => ex
715
- if ex.include? /NoSuchKey/
716
- ret = nil
717
- else
718
- raise ex
719
- end
720
- end
721
-
722
- if ret.nil?
723
- ret = RemoteNil.new
724
- end
725
- end
726
- @lobs[name] = ret
727
- return nil if ret.is_a? RemoteNil
728
- return ret
729
- else
730
- @attributes_rb = {} unless @attributes_rb # was getting errors after upgrade.
731
- ret = @attributes_rb[name_s] # instance_variable_get(instance_var)
732
- return ret unless ret.nil?
733
- return nil if ret.is_a? RemoteNil
734
- ret = get_attribute_sdb(name)
735
- ret = sdb_to_ruby(name, ret)
736
- @attributes_rb[name_s] = ret
737
- return ret
738
- end
739
-
740
- end
741
-
742
- def set(name, value, dirtify=true)
743
- # puts "SET #{name}=#{value.inspect}" if SimpleRecord.logging?
744
- # puts "self=" + self.inspect
745
- attname = name.to_s # default attname
746
- name = name.to_sym
747
- att_meta = get_att_meta(name)
748
- store_rb_val = false
749
- if att_meta.nil?
750
- # check if it ends with id and see if att_meta is there
751
- ends_with = name.to_s[-3, 3]
752
- if ends_with == "_id"
753
- # puts 'ends with id'
754
- n2 = name.to_s[0, name.length-3]
755
- # puts 'n2=' + n2
756
- att_meta = defined_attributes_local[n2.to_sym]
757
- # puts 'defined_attributes_local=' + defined_attributes_local.inspect
758
- attname = name.to_s
759
- attvalue = value
760
- name = n2.to_sym
761
- end
762
- return if att_meta.nil?
763
- else
764
- if att_meta.type == :belongs_to
765
- ends_with = name.to_s[-3, 3]
766
- if ends_with == "_id"
767
- att_name = name.to_s
768
- attvalue = value
769
- else
770
- attname = name.to_s + '_id'
771
- attvalue = value.nil? ? nil : value.id
772
- store_rb_val = true
773
- end
774
- elsif att_meta.type == :clob
775
- make_dirty(name, value) if dirtify
776
- @lobs[name] = value
777
- return
778
- else
779
- attname = name.to_s
780
- attvalue = att_meta.init_value(value)
781
- # attvalue = value
782
- #puts 'converted ' + value.inspect + ' to ' + attvalue.inspect
783
- end
784
- end
785
- attvalue = strip_array(attvalue)
786
- make_dirty(name, attvalue) if dirtify
787
- # puts "ARG=#{attname.to_s} setting to #{attvalue}"
788
- sdb_val = ruby_to_sdb(name, attvalue)
789
- # puts "sdb_val=" + sdb_val.to_s
790
- @attributes[attname] = sdb_val
791
- # attvalue = wrap_if_required(name, attvalue, sdb_val)
792
- # puts 'attvalue2=' + attvalue.to_s
793
-
794
- if store_rb_val
795
- @attributes_rb[name.to_s] = value
796
- else
797
- @attributes_rb.delete(name.to_s)
798
- end
799
-
800
- end
801
-
802
- def set_attribute_sdb(name, val)
803
- @attributes[sdb_att_name(name)] = val
804
- end
805
684
 
806
685
  def delete_niled(to_delete)
807
686
  # puts 'to_delete=' + to_delete.inspect
@@ -872,6 +751,8 @@ module SimpleRecord
872
751
  # Extra options:
873
752
  # :per_token => the number of results to return per next_token, max is 2500.
874
753
  # :consistent_read => true/false -- as per http://developer.amazonwebservices.com/connect/entry.jspa?externalID=3572
754
+ # :retries => maximum number of times to retry this query on an error response.
755
+ # :shard => shard name or array of shard names to use on this query.
875
756
  def self.find(*params)
876
757
  #puts 'params=' + params.inspect
877
758
 
@@ -1054,12 +935,6 @@ module SimpleRecord
1054
935
  id.hash
1055
936
  end
1056
937
 
1057
- private
1058
- def set_attributes(atts)
1059
- atts.each_pair do |k, v|
1060
- set(k, v)
1061
- end
1062
- end
1063
938
 
1064
939
  end
1065
940
 
@@ -18,207 +18,213 @@ module SimpleRecord
18
18
 
19
19
  end
20
20
 
21
- def defined_attributes
22
- @attributes ||= {}
23
- @attributes
24
- end
25
21
 
26
- def has_attributes(*args)
27
- has_attributes2(args)
28
- end
22
+ module ClassMethods
23
+
29
24
 
30
- def has_attributes2(args, options_for_all={})
25
+ def defined_attributes
26
+ @attributes ||= {}
27
+ @attributes
28
+ end
29
+
30
+ def has_attributes(*args)
31
+ has_attributes2(args)
32
+ end
33
+
34
+ def has_attributes2(args, options_for_all={})
31
35
  # puts 'args=' + args.inspect
32
36
  # puts 'options_for_all = ' + options_for_all.inspect
33
- args.each do |arg|
34
- arg_options = {}
35
- if arg.is_a?(Hash)
36
- # then attribute may have extra options
37
- arg_options = arg
38
- arg = arg_options[:name].to_sym
39
- end
40
- type = options_for_all[:type] || :string
41
- attr = Attribute.new(type, arg_options)
42
- defined_attributes[arg] = attr if defined_attributes[arg].nil?
43
-
44
- # define reader method
45
- arg_s = arg.to_s # to get rid of all the to_s calls
46
- send(:define_method, arg) do
47
- ret = get_attribute(arg)
48
- return ret
37
+ args.each do |arg|
38
+ arg_options = {}
39
+ if arg.is_a?(Hash)
40
+ # then attribute may have extra options
41
+ arg_options = arg
42
+ arg = arg_options[:name].to_sym
43
+ end
44
+ type = options_for_all[:type] || :string
45
+ attr = Attribute.new(type, arg_options)
46
+ defined_attributes[arg] = attr if defined_attributes[arg].nil?
47
+
48
+ # define reader method
49
+ arg_s = arg.to_s # to get rid of all the to_s calls
50
+ send(:define_method, arg) do
51
+ ret = get_attribute(arg)
52
+ return ret
53
+ end
54
+
55
+ # define writer method
56
+ send(:define_method, arg_s+"=") do |value|
57
+ set(arg, value)
58
+ end
59
+
60
+ define_dirty_methods(arg_s)
49
61
  end
62
+ end
50
63
 
51
- # define writer method
52
- send(:define_method, arg_s+"=") do |value|
53
- set(arg, value)
64
+ def define_dirty_methods(arg_s)
65
+ # Now for dirty methods: http://api.rubyonrails.org/classes/ActiveRecord/Dirty.html
66
+ # define changed? method
67
+ send(:define_method, arg_s + "_changed?") do
68
+ @dirty.has_key?(sdb_att_name(arg_s))
54
69
  end
55
70
 
56
- define_dirty_methods(arg_s)
57
- end
58
- end
71
+ # define change method
72
+ send(:define_method, arg_s + "_change") do
73
+ old_val = @dirty[sdb_att_name(arg_s)]
74
+ [old_val, get_attribute(arg_s)]
75
+ end
59
76
 
60
- def define_dirty_methods(arg_s)
61
- # Now for dirty methods: http://api.rubyonrails.org/classes/ActiveRecord/Dirty.html
62
- # define changed? method
63
- send(:define_method, arg_s + "_changed?") do
64
- @dirty.has_key?(sdb_att_name(arg_s))
77
+ # define was method
78
+ send(:define_method, arg_s + "_was") do
79
+ old_val = @dirty[sdb_att_name(arg_s)]
80
+ old_val
81
+ end
65
82
  end
66
83
 
67
- # define change method
68
- send(:define_method, arg_s + "_change") do
69
- old_val = @dirty[sdb_att_name(arg_s)]
70
- [old_val, get_attribute(arg_s)]
84
+ def has_strings(*args)
85
+ has_attributes(*args)
71
86
  end
72
87
 
73
- # define was method
74
- send(:define_method, arg_s + "_was") do
75
- old_val = @dirty[sdb_att_name(arg_s)]
76
- old_val
88
+ def has_ints(*args)
89
+ has_attributes(*args)
90
+ are_ints(*args)
77
91
  end
78
- end
79
-
80
- def has_strings(*args)
81
- has_attributes(*args)
82
- end
83
-
84
- def has_ints(*args)
85
- has_attributes(*args)
86
- are_ints(*args)
87
- end
88
92
 
89
- def has_floats(*args)
90
- has_attributes(*args)
91
- are_floats(*args)
92
- end
93
-
94
- def has_dates(*args)
95
- has_attributes(*args)
96
- are_dates(*args)
97
- end
98
-
99
- def has_booleans(*args)
100
- has_attributes(*args)
101
- are_booleans(*args)
102
- end
103
-
104
- def are_ints(*args)
105
- # puts 'calling are_ints: ' + args.inspect
106
- args.each do |arg|
107
- defined_attributes[arg].type = :int
93
+ def has_floats(*args)
94
+ has_attributes(*args)
95
+ are_floats(*args)
108
96
  end
109
- end
110
97
 
111
- def are_floats(*args)
112
- # puts 'calling are_ints: ' + args.inspect
113
- args.each do |arg|
114
- defined_attributes[arg].type = :float
98
+ def has_dates(*args)
99
+ has_attributes(*args)
100
+ are_dates(*args)
115
101
  end
116
- end
117
102
 
118
- def are_dates(*args)
119
- args.each do |arg|
120
- defined_attributes[arg].type = :date
103
+ def has_booleans(*args)
104
+ has_attributes(*args)
105
+ are_booleans(*args)
121
106
  end
122
- end
123
107
 
124
- def are_booleans(*args)
125
- args.each do |arg|
126
- defined_attributes[arg].type = :boolean
108
+ def are_ints(*args)
109
+ # puts 'calling are_ints: ' + args.inspect
110
+ args.each do |arg|
111
+ defined_attributes[arg].type = :int
112
+ end
127
113
  end
128
- end
129
114
 
130
- def has_clobs(*args)
131
- has_attributes2(args, :type=>:clob)
132
-
133
- end
115
+ def are_floats(*args)
116
+ # puts 'calling are_ints: ' + args.inspect
117
+ args.each do |arg|
118
+ defined_attributes[arg].type = :float
119
+ end
120
+ end
134
121
 
135
- @@virtuals=[]
122
+ def are_dates(*args)
123
+ args.each do |arg|
124
+ defined_attributes[arg].type = :date
125
+ end
126
+ end
136
127
 
137
- def has_virtuals(*args)
138
- @@virtuals = args
139
- args.each do |arg|
140
- #we just create the accessor functions here, the actual instance variable is created during initialize
141
- attr_accessor(arg)
128
+ def are_booleans(*args)
129
+ args.each do |arg|
130
+ defined_attributes[arg].type = :boolean
131
+ end
142
132
  end
143
- end
144
133
 
145
- # One belongs_to association per call. Call multiple times if there are more than one.
146
- #
147
- # This method will also create an {association)_id method that will return the ID of the foreign object
148
- # without actually materializing it.
149
- #
150
- # options:
151
- # :class_name=>"User" - to change the default class to use
152
- def belongs_to(association_id, options = {})
153
- arg = association_id
154
- arg_s = arg.to_s
155
- arg_id = arg.to_s + '_id'
156
- attribute = Attribute.new(:belongs_to, options)
157
- defined_attributes[arg] = attribute
134
+ def has_clobs(*args)
135
+ has_attributes2(args, :type=>:clob)
158
136
 
159
- # todo: should also handle foreign_key http://74.125.95.132/search?q=cache:KqLkxuXiBBQJ:wiki.rubyonrails.org/rails/show/belongs_to+rails+belongs_to&hl=en&ct=clnk&cd=1&gl=us
160
- # puts "arg_id=#{arg}_id"
161
- # puts "is defined? " + eval("(defined? #{arg}_id)").to_s
162
- # puts 'atts=' + @attributes.inspect
137
+ end
163
138
 
164
- # Define reader method
165
- send(:define_method, arg) do
166
- return get_attribute(arg)
139
+ def has_virtuals(*args)
140
+ @@virtuals = args
141
+ args.each do |arg|
142
+ #we just create the accessor functions here, the actual instance variable is created during initialize
143
+ attr_accessor(arg)
144
+ end
167
145
  end
168
146
 
147
+ # One belongs_to association per call. Call multiple times if there are more than one.
148
+ #
149
+ # This method will also create an {association)_id method that will return the ID of the foreign object
150
+ # without actually materializing it.
151
+ #
152
+ # options:
153
+ # :class_name=>"User" - to change the default class to use
154
+ def belongs_to(association_id, options = {})
155
+ arg = association_id
156
+ arg_s = arg.to_s
157
+ arg_id = arg.to_s + '_id'
158
+ attribute = Attribute.new(:belongs_to, options)
159
+ defined_attributes[arg] = attribute
160
+
161
+ # todo: should also handle foreign_key http://74.125.95.132/search?q=cache:KqLkxuXiBBQJ:wiki.rubyonrails.org/rails/show/belongs_to+rails+belongs_to&hl=en&ct=clnk&cd=1&gl=us
162
+ # puts "arg_id=#{arg}_id"
163
+ # puts "is defined? " + eval("(defined? #{arg}_id)").to_s
164
+ # puts 'atts=' + @attributes.inspect
165
+
166
+ # Define reader method
167
+ send(:define_method, arg) do
168
+ return get_attribute(arg)
169
+ end
169
170
 
170
- # Define writer method
171
- send(:define_method, arg.to_s + "=") do |value|
172
- set(arg, value)
173
- end
174
171
 
172
+ # Define writer method
173
+ send(:define_method, arg.to_s + "=") do |value|
174
+ set(arg, value)
175
+ end
175
176
 
176
- # Define ID reader method for reading the associated objects id without getting the entire object
177
- send(:define_method, arg_id) do
178
- get_attribute_sdb(arg_s)
179
- end
180
177
 
181
- # Define writer method for setting the _id directly without the associated object
182
- send(:define_method, arg_id + "=") do |value|
178
+ # Define ID reader method for reading the associated objects id without getting the entire object
179
+ send(:define_method, arg_id) do
180
+ get_attribute_sdb(arg_s)
181
+ end
182
+
183
+ # Define writer method for setting the _id directly without the associated object
184
+ send(:define_method, arg_id + "=") do |value|
183
185
  # rb_att_name = arg_s # n2 = name.to_s[0, name.length-3]
184
- set(arg_id, value)
186
+ set(arg_id, value)
185
187
  # if value.nil?
186
188
  # self[arg_id] = nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
187
189
  # else
188
190
  # self[arg_id] = value
189
191
  # end
192
+ end
193
+
194
+ send(:define_method, "create_"+arg.to_s) do |*params|
195
+ newsubrecord=eval(arg.to_s.classify).new(*params)
196
+ newsubrecord.save
197
+ arg_id = arg.to_s + '_id'
198
+ self[arg_id]=newsubrecord.id
199
+ end
200
+
201
+ define_dirty_methods(arg_s)
190
202
  end
191
203
 
192
- send(:define_method, "create_"+arg.to_s) do |*params|
193
- newsubrecord=eval(arg.to_s.classify).new(*params)
194
- newsubrecord.save
195
- arg_id = arg.to_s + '_id'
196
- self[arg_id]=newsubrecord.id
204
+ def has_many(*args)
205
+ args.each do |arg|
206
+ #okay, this creates an instance method with the pluralized name of the symbol passed to belongs_to
207
+ send(:define_method, arg) do
208
+ #when called, the method creates a new, very temporary instance of the Activerecordtosdb_subrecord class
209
+ #It is passed the three initializers it needs:
210
+ #note the first parameter is just a string by time new gets it, like "user"
211
+ #the second and third parameters are still a variable when new gets it, like user_id
212
+ return eval(%{Activerecordtosdb_subrecord_array.new('#{arg}', self.class.name ,id)})
213
+ end
214
+ end
215
+ #Disclaimer: this whole funciton just seems crazy to me, and a bit inefficient. But it was the clearest way I could think to do it code wise.
216
+ #It's bad programming form (imo) to have a class method require something that isn't passed to it through it's variables.
217
+ #I couldn't pass the id when calling find, since the original find doesn't work that way, so I was left with this.
197
218
  end
198
219
 
199
- define_dirty_methods(arg_s)
200
- end
220
+ def has_one(*args)
201
221
 
202
- def has_many(*args)
203
- args.each do |arg|
204
- #okay, this creates an instance method with the pluralized name of the symbol passed to belongs_to
205
- send(:define_method, arg) do
206
- #when called, the method creates a new, very temporary instance of the Activerecordtosdb_subrecord class
207
- #It is passed the three initializers it needs:
208
- #note the first parameter is just a string by time new gets it, like "user"
209
- #the second and third parameters are still a variable when new gets it, like user_id
210
- return eval(%{Activerecordtosdb_subrecord_array.new('#{arg}', self.class.name ,id)})
211
- end
212
222
  end
213
- #Disclaimer: this whole funciton just seems crazy to me, and a bit inefficient. But it was the clearest way I could think to do it code wise.
214
- #It's bad programming form (imo) to have a class method require something that isn't passed to it through it's variables.
215
- #I couldn't pass the id when calling find, since the original find doesn't work that way, so I was left with this.
216
- end
217
223
 
218
- def has_one(*args)
219
224
 
220
225
  end
221
226
 
227
+ @@virtuals=[]
222
228
 
223
229
  def self.handle_virtuals(attrs)
224
230
  @@virtuals.each do |virtual|
@@ -229,12 +235,149 @@ module SimpleRecord
229
235
  end
230
236
  end
231
237
 
238
+
239
+ def set(name, value, dirtify=true)
240
+ # puts "SET #{name}=#{value.inspect}" if SimpleRecord.logging?
241
+ # puts "self=" + self.inspect
242
+ attname = name.to_s # default attname
243
+ name = name.to_sym
244
+ att_meta = get_att_meta(name)
245
+ store_rb_val = false
246
+ if att_meta.nil?
247
+ # check if it ends with id and see if att_meta is there
248
+ ends_with = name.to_s[-3, 3]
249
+ if ends_with == "_id"
250
+ # puts 'ends with id'
251
+ n2 = name.to_s[0, name.length-3]
252
+ # puts 'n2=' + n2
253
+ att_meta = defined_attributes_local[n2.to_sym]
254
+ # puts 'defined_attributes_local=' + defined_attributes_local.inspect
255
+ attname = name.to_s
256
+ attvalue = value
257
+ name = n2.to_sym
258
+ end
259
+ return if att_meta.nil?
260
+ else
261
+ if att_meta.type == :belongs_to
262
+ ends_with = name.to_s[-3, 3]
263
+ if ends_with == "_id"
264
+ att_name = name.to_s
265
+ attvalue = value
266
+ else
267
+ attname = name.to_s + '_id'
268
+ attvalue = value.nil? ? nil : value.id
269
+ store_rb_val = true
270
+ end
271
+ elsif att_meta.type == :clob
272
+ make_dirty(name, value) if dirtify
273
+ @lobs[name] = value
274
+ return
275
+ else
276
+ attname = name.to_s
277
+ attvalue = att_meta.init_value(value)
278
+ # attvalue = value
279
+ #puts 'converted ' + value.inspect + ' to ' + attvalue.inspect
280
+ end
281
+ end
282
+ attvalue = strip_array(attvalue)
283
+ make_dirty(name, attvalue) if dirtify
284
+ # puts "ARG=#{attname.to_s} setting to #{attvalue}"
285
+ sdb_val = ruby_to_sdb(name, attvalue)
286
+ # puts "sdb_val=" + sdb_val.to_s
287
+ @attributes[attname] = sdb_val
288
+ # attvalue = wrap_if_required(name, attvalue, sdb_val)
289
+ # puts 'attvalue2=' + attvalue.to_s
290
+
291
+ if store_rb_val
292
+ @attributes_rb[name.to_s] = value
293
+ else
294
+ @attributes_rb.delete(name.to_s)
295
+ end
296
+
297
+ end
298
+
299
+
300
+ def set_attribute_sdb(name, val)
301
+ @attributes[sdb_att_name(name)] = val
302
+ end
303
+
304
+
305
+ def get_attribute_sdb(name)
306
+ name = name.to_sym
307
+ ret = strip_array(@attributes[sdb_att_name(name)])
308
+ return ret
309
+ end
310
+
311
+ # Since SimpleDB supports multiple attributes per value, the values are an array.
312
+ # This method will return the value unwrapped if it's the only, otherwise it will return the array.
313
+ def get_attribute(name)
314
+ # puts "get_attribute #{name}"
315
+ # Check if this arg is already converted
316
+ name_s = name.to_s
317
+ name = name.to_sym
318
+ att_meta = get_att_meta(name)
319
+ # puts "att_meta for #{name}: " + att_meta.inspect
320
+ if att_meta && att_meta.type == :clob
321
+ ret = @lobs[name]
322
+ # puts 'get_attribute clob ' + ret.inspect
323
+ if ret
324
+ if ret.is_a? RemoteNil
325
+ return nil
326
+ else
327
+ return ret
328
+ end
329
+ end
330
+ # get it from s3
331
+ 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
341
+ end
342
+ end
343
+
344
+ if ret.nil?
345
+ ret = RemoteNil.new
346
+ end
347
+ end
348
+ @lobs[name] = ret
349
+ return nil if ret.is_a? RemoteNil
350
+ return ret
351
+ else
352
+ @attributes_rb = {} unless @attributes_rb # was getting errors after upgrade.
353
+ ret = @attributes_rb[name_s] # instance_variable_get(instance_var)
354
+ return ret unless ret.nil?
355
+ return nil if ret.is_a? RemoteNil
356
+ ret = get_attribute_sdb(name)
357
+ # p ret
358
+ ret = sdb_to_ruby(name, ret)
359
+ # p ret
360
+ @attributes_rb[name_s] = ret
361
+ return ret
362
+ end
363
+
364
+ end
365
+
366
+
367
+ private
368
+ def set_attributes(atts)
369
+ atts.each_pair do |k, v|
370
+ set(k, v)
371
+ end
372
+ end
373
+
374
+
232
375
  # Holds information about an attribute
233
376
  class Attribute
234
377
  attr_accessor :type, :options
235
378
 
236
379
  def initialize(type, options=nil)
237
- @type = type
380
+ @type = type
238
381
  @options = options
239
382
  end
240
383
 
@@ -243,7 +386,11 @@ module SimpleRecord
243
386
  ret = value
244
387
  case self.type
245
388
  when :int
246
- ret = value.to_i
389
+ if value.is_a? Array
390
+ ret = value.collect { |x| x.to_i }
391
+ else
392
+ ret = value.to_i
393
+ end
247
394
  end
248
395
  ret
249
396
  end
@@ -25,8 +25,13 @@ module SimpleRecord
25
25
 
26
26
  options = params.size > 1 ? params[1] : {}
27
27
 
28
- domains = sharded_domains
29
- puts "sharded_domains=" + domains.inspect
28
+ if options[:shard] # User specified shard.
29
+ shard = options[:shard]
30
+ domains = shard.is_a?(Array) ? (shard.collect { |x| prefix_shard_name(x) }) : [ prefix_shard_name(shard)]
31
+ else
32
+ domains = sharded_domains
33
+ end
34
+ # puts "sharded_domains=" + domains.inspect
30
35
 
31
36
  single = false
32
37
  case params.first
@@ -69,17 +74,21 @@ module SimpleRecord
69
74
  send(sharding_options[:shards])
70
75
  end
71
76
 
77
+ def prefix_shard_name(s)
78
+ "#{domain}_#{s}"
79
+ end
80
+
81
+
72
82
  def sharded_domains
73
83
  sharded_domains = []
74
84
  shard_names = shards
75
85
  shard_names.each do |s|
76
- sharded_domains << "#{domain}_#{s}"
86
+ sharded_domains << prefix_shard_name(s)
77
87
  end
78
88
  sharded_domains
79
89
  end
80
90
  end
81
91
 
82
-
83
92
  def sharded_domain
84
93
  # puts 'getting sharded_domain'
85
94
  options = self.class.sharding_options
@@ -90,7 +99,7 @@ module SimpleRecord
90
99
  # shards = self.send(shards)
91
100
  # end
92
101
  sharded_domain = "#{domain}_#{self.send(options[:map])}"
93
- puts "sharded_domain=" + sharded_domain.inspect
102
+ # puts "sharded_domain=" + sharded_domain.inspect
94
103
  sharded_domain
95
104
  end
96
105
 
@@ -2,30 +2,37 @@
2
2
  module SimpleRecord
3
3
  module Translations
4
4
 
5
- @@offset = 9223372036854775808
6
- @@padding = 20
7
- @@date_format = "%Y-%m-%dT%H:%M:%S"; # Time to second precision
5
+ @@offset = 9223372036854775808
6
+ @@padding = 20
7
+ @@date_format = "%Y-%m-%dT%H:%M:%S";
8
8
 
9
- def ruby_to_sdb(name, value)
9
+ def ruby_to_string_val(att_meta, value)
10
+ if att_meta.type == :int
11
+ ret = Translations.pad_and_offset(value, att_meta)
12
+ elsif att_meta.type == :date
13
+ ret = Translations.pad_and_offset(value, att_meta)
14
+ else
15
+ ret = value.to_s
16
+ end
17
+ ret
18
+ end
10
19
 
11
- return nil if value.nil?
20
+ # Time to second precision
12
21
 
22
+ def ruby_to_sdb(name, value)
23
+ return nil if value.nil?
13
24
  name = name.to_s
14
-
15
25
  # puts "Converting #{name} to sdb value=#{value}"
16
26
  # puts "atts_local=" + defined_attributes_local.inspect
17
27
 
18
28
  att_meta = get_att_meta(name)
19
29
 
20
- if att_meta.type == :int
21
- ret = Translations.pad_and_offset(value, att_meta)
22
- elsif att_meta.type == :date
23
- ret = Translations.pad_and_offset(value, att_meta)
30
+ if value.is_a? Array
31
+ ret = value.collect { |x| ruby_to_string_val(att_meta, x) }
24
32
  else
25
- ret = value.to_s
33
+ ret = ruby_to_string_val(att_meta, value)
26
34
  end
27
35
 
28
-
29
36
  unless value.blank?
30
37
  if att_meta.options
31
38
  if att_meta.options[:encrypted]
@@ -41,7 +48,7 @@ module SimpleRecord
41
48
  end
42
49
  end
43
50
 
44
- return ret.to_s
51
+ return ret
45
52
 
46
53
  end
47
54
 
@@ -68,15 +75,15 @@ module SimpleRecord
68
75
  class_name = class_name.camelize
69
76
  # puts "attr=" + @attributes[arg_id].inspect
70
77
  # puts 'val=' + @attributes[arg_id][0].inspect unless @attributes[arg_id].nil?
71
- ret = nil
72
- arg_id = name.to_s + '_id'
78
+ ret = nil
79
+ arg_id = name.to_s + '_id'
73
80
  arg_id_val = send("#{arg_id}")
74
81
  if arg_id_val
75
82
  if !cache_store.nil?
76
83
  # arg_id_val = @attributes[arg_id][0]
77
84
  cache_key = self.class.cache_key(class_name, arg_id_val)
78
85
  # puts 'cache_key=' + cache_key
79
- ret = cache_store.read(cache_key)
86
+ ret = cache_store.read(cache_key)
80
87
  # puts 'belongs_to incache=' + ret.inspect
81
88
  end
82
89
  if ret.nil?
@@ -95,7 +102,18 @@ module SimpleRecord
95
102
  end
96
103
  end
97
104
  value = ret
98
- elsif att_meta.type == :int
105
+ else
106
+ if value.is_a? Array
107
+ value = value.collect { |x| string_val_to_ruby(att_meta, x) }
108
+ else
109
+ value = string_val_to_ruby(att_meta, value)
110
+ end
111
+ end
112
+ value
113
+ end
114
+
115
+ def string_val_to_ruby(att_meta, value)
116
+ if att_meta.type == :int
99
117
  value = Translations.un_offset_int(value)
100
118
  elsif att_meta.type == :date
101
119
  value = to_date(value)
@@ -110,7 +128,7 @@ module SimpleRecord
110
128
  # todo: add Float, etc
111
129
  # puts 'padding=' + x.class.name + " -- " + x.inspect
112
130
  if x.kind_of? Integer
113
- x += @@offset
131
+ x += @@offset
114
132
  x_str = x.to_s
115
133
  # pad
116
134
  x_str = '0' + x_str while x_str.size < 20
@@ -139,7 +157,7 @@ module SimpleRecord
139
157
  end
140
158
 
141
159
  # This conversion to a string is based on: http://tools.ietf.org/html/draft-wood-ldapext-float-00
142
- # Java code sample is here: http://code.google.com/p/typica/source/browse/trunk/java/com/xerox/amazonws/simpledb/DataUtils.java
160
+ # Java code sample is here: http://code.google.com/p/typica/source/browse/trunk/java/com/xerox/amazonws/simpledb/DataUtils.java
143
161
  def self.from_float(x)
144
162
  return x
145
163
  # if x == 0.0
@@ -192,7 +210,7 @@ module SimpleRecord
192
210
  def unpad(i, attributes)
193
211
  if !attributes[i].nil?
194
212
  # puts 'before=' + self[i].inspect
195
- attributes[i].collect!{ |x|
213
+ attributes[i].collect! { |x|
196
214
  un_offset_int(x)
197
215
 
198
216
  }
@@ -212,7 +230,7 @@ module SimpleRecord
212
230
  key = key || get_encryption_key()
213
231
  raise SimpleRecordError, "Encryption key must be defined on the attribute." if key.nil?
214
232
  encrypted_value = SimpleRecord::Encryptor.encrypt(:value => value, :key => key)
215
- encoded_value = Base64.encode64(encrypted_value)
233
+ encoded_value = Base64.encode64(encrypted_value)
216
234
  encoded_value
217
235
  end
218
236
 
@@ -221,7 +239,7 @@ module SimpleRecord
221
239
  # puts "decrypt orig value #{value} "
222
240
  unencoded_value = Base64.decode64(value)
223
241
  raise SimpleRecordError, "Encryption key must be defined on the attribute." if key.nil?
224
- key = key || get_encryption_key()
242
+ key = key || get_encryption_key()
225
243
  # puts "decrypting #{unencoded_value} "
226
244
  decrypted_value = SimpleRecord::Encryptor.decrypt(:value => unencoded_value, :key => key)
227
245
  # "decrypted #{unencoded_value} to #{decrypted_value}"
@@ -249,7 +267,7 @@ module SimpleRecord
249
267
  end
250
268
 
251
269
  def self.pass_hash(value)
252
- hashed = Password::create_hash(value)
270
+ hashed = Password::create_hash(value)
253
271
  encoded_value = Base64.encode64(hashed)
254
272
  encoded_value
255
273
  end
data/test/test_base.rb CHANGED
@@ -32,7 +32,7 @@ class TestBase < Test::Unit::TestCase
32
32
 
33
33
  def reset_connection
34
34
  puts 'reset_connection'
35
- @config = YAML::load(File.open(File.expand_path("~/.test-configs/simple_record.yml")))
35
+ @config = YAML::load(File.open(File.expand_path("~/.test_configs/simple_record.yml")))
36
36
  #puts 'inspecting config = ' + @config.inspect
37
37
 
38
38
  SimpleRecord.enable_logging
data/test/test_shards.rb CHANGED
@@ -13,6 +13,7 @@ class TestShards < TestBase
13
13
  def setup
14
14
  super
15
15
  delete_all MyShardedModel
16
+ delete_all MyShardedByFieldModel
16
17
  end
17
18
 
18
19
  def teardown
@@ -96,6 +97,7 @@ class TestShards < TestBase
96
97
  saved << mm
97
98
  end
98
99
 
100
+ sleep 1
99
101
  # todo: assert that we're actually sharding
100
102
 
101
103
  puts "finding them all"
@@ -109,6 +111,22 @@ class TestShards < TestBase
109
111
  assert(found.find { |m1| m1.id == so.id })
110
112
  end
111
113
 
114
+ rs = MyShardedByFieldModel.find(:all)
115
+ rs.each do |m|
116
+ p m
117
+ found << m
118
+ end
119
+ saved.each do |so|
120
+ assert(found.find { |m1| m1.id == so.id })
121
+ end
122
+
123
+ # Try to find on a specific known shard
124
+ selects = SimpleRecord.stats.selects
125
+ cali_models = MyShardedByFieldModel.find(:all, :shard => "CA")
126
+ puts 'cali_models=' + cali_models.inspect
127
+ assert_equal(5, cali_models.size)
128
+ assert_equal(selects + 1, SimpleRecord.stats.selects)
129
+
112
130
  puts "deleting all of them"
113
131
  found.each do |fo|
114
132
  fo.delete
@@ -11,7 +11,7 @@ require_relative 'model_with_enc'
11
11
  # Tests for SimpleRecord
12
12
  #
13
13
 
14
- class SimpleRecordTest < TestBase
14
+ class TestSimpleRecord < TestBase
15
15
 
16
16
 
17
17
  def test_save_get
@@ -682,6 +682,29 @@ class SimpleRecordTest < TestBase
682
682
  assert mms.box_usage && mms.box_usage > 0
683
683
  assert mms.request_id
684
684
 
685
+ end
686
+
687
+ def test_multi_value_attributes
688
+
689
+ val = ['a', 'b', 'c']
690
+ val2 = [1, 2, 3]
691
+
692
+ mm = MyModel.new
693
+ mm.name = val
694
+ mm.age = val2
695
+ assert_equal val, mm.name
696
+ assert_equal val2, mm.age
697
+ mm.save
698
+
699
+ sleep 1
700
+ mm = MyModel.find(mm.id)
701
+ # Values are not returned in order
702
+ assert_equal val, mm.name.sort
703
+ assert_equal val2, mm.age.sort
704
+
705
+
706
+
707
+
685
708
  end
686
709
 
687
710
  end
metadata CHANGED
@@ -3,10 +3,10 @@ name: simple_record
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
- - 1
7
- - 5
8
- - 8
9
- version: 1.5.8
6
+ - 2
7
+ - 0
8
+ - 0
9
+ version: 2.0.0
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-03 00:00:00 -08:00
19
+ date: 2010-12-07 00:00:00 -08:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency
@@ -64,7 +64,6 @@ files:
64
64
  - test/my_model.rb
65
65
  - test/my_sharded_model.rb
66
66
  - test/paging_array_test.rb
67
- - test/simple_record_test.rb
68
67
  - test/temp.rb
69
68
  - test/temp_test.rb
70
69
  - test/test_base.rb
@@ -79,6 +78,7 @@ files:
79
78
  - test/test_rails3.rb
80
79
  - test/test_results_array.rb
81
80
  - test/test_shards.rb
81
+ - test/test_simple_record.rb
82
82
  - test/test_usage.rb
83
83
  has_rdoc: true
84
84
  homepage: http://github.com/appoxy/simple_record/
@@ -120,7 +120,6 @@ test_files:
120
120
  - test/my_model.rb
121
121
  - test/my_sharded_model.rb
122
122
  - test/paging_array_test.rb
123
- - test/simple_record_test.rb
124
123
  - test/temp.rb
125
124
  - test/temp_test.rb
126
125
  - test/test_base.rb
@@ -135,4 +134,5 @@ test_files:
135
134
  - test/test_rails3.rb
136
135
  - test/test_results_array.rb
137
136
  - test/test_shards.rb
137
+ - test/test_simple_record.rb
138
138
  - test/test_usage.rb