simple_record 1.5.8 → 2.0.0

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