simple_record 2.0.5 → 2.1.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 +27 -1
- data/lib/simple_record.rb +165 -91
- data/lib/simple_record/active_sdb.rb +1 -1
- data/lib/simple_record/attributes.rb +3 -3
- data/lib/simple_record/callbacks.rb +45 -17
- data/lib/simple_record/sharding.rb +238 -238
- data/lib/simple_record/translations.rb +223 -223
- data/lib/simple_record/validations.rb +69 -0
- data/test/my_model.rb +71 -35
- data/test/my_sharded_model.rb +25 -20
- data/test/my_simple_model.rb +13 -0
- data/test/test_shards.rb +122 -119
- data/test/test_simple_record.rb +5 -22
- data/test/test_validations.rb +45 -0
- metadata +42 -53
- data/lib/simple_record/rails2.rb +0 -30
@@ -1,253 +1,253 @@
|
|
1
1
|
# This module defines all the methods that perform data translations for storage and retrieval.
|
2
2
|
module SimpleRecord
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
3
|
+
module Translations
|
4
|
+
|
5
|
+
@@offset = 9223372036854775808
|
6
|
+
@@padding = 20
|
7
|
+
@@date_format = "%Y-%m-%dT%H:%M:%S";
|
8
|
+
|
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
|
19
19
|
|
20
|
-
|
20
|
+
# Time to second precision
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def ruby_to_sdb(name, value)
|
23
|
+
return nil if value.nil?
|
24
|
+
name = name.to_s
|
25
25
|
# puts "Converting #{name} to sdb value=#{value}"
|
26
26
|
# puts "atts_local=" + defined_attributes_local.inspect
|
27
27
|
|
28
|
-
|
28
|
+
att_meta = get_att_meta(name)
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
if value.is_a? Array
|
31
|
+
ret = value.collect { |x| ruby_to_string_val(att_meta, x) }
|
32
|
+
else
|
33
|
+
ret = ruby_to_string_val(att_meta, value)
|
34
|
+
end
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
36
|
+
unless value.blank?
|
37
|
+
if att_meta.options
|
38
|
+
if att_meta.options[:encrypted]
|
39
39
|
# puts "ENCRYPTING #{name} value #{value}"
|
40
|
-
|
40
|
+
ret = Translations.encrypt(ret, att_meta.options[:encrypted])
|
41
41
|
# puts 'encrypted value=' + ret.to_s
|
42
|
-
|
43
|
-
|
42
|
+
end
|
43
|
+
if att_meta.options[:hashed]
|
44
44
|
# puts "hashing #{name}"
|
45
|
-
|
45
|
+
ret = Translations.pass_hash(ret)
|
46
46
|
# puts "hashed value=" + ret.inspect
|
47
|
-
|
48
|
-
|
49
|
-
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
50
|
|
51
|
-
|
51
|
+
return ret
|
52
52
|
|
53
|
-
|
53
|
+
end
|
54
54
|
|
55
55
|
|
56
|
-
|
57
|
-
|
56
|
+
# Convert value from SimpleDB String version to real ruby value.
|
57
|
+
def sdb_to_ruby(name, value)
|
58
58
|
# puts 'sdb_to_ruby arg=' + name.inspect + ' - ' + name.class.name + ' - value=' + value.to_s
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
if att_meta.options
|
63
|
-
if att_meta.options[:encrypted]
|
64
|
-
value = Translations.decrypt(value, att_meta.options[:encrypted])
|
65
|
-
end
|
66
|
-
if att_meta.options[:hashed]
|
67
|
-
return PasswordHashed.new(value)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
59
|
+
return nil if value.nil?
|
60
|
+
att_meta = get_att_meta(name)
|
71
61
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
62
|
+
if att_meta.options
|
63
|
+
if att_meta.options[:encrypted]
|
64
|
+
value = Translations.decrypt(value, att_meta.options[:encrypted])
|
65
|
+
end
|
66
|
+
if att_meta.options[:hashed]
|
67
|
+
return PasswordHashed.new(value)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
if !has_id_on_end(name) && att_meta.type == :belongs_to
|
73
|
+
class_name = att_meta.options[:class_name] || name.to_s[0...1].capitalize + name.to_s[1...name.to_s.length]
|
74
|
+
# Camelize classnames with underscores (ie my_model.rb --> MyModel)
|
75
|
+
class_name = class_name.camelize
|
76
|
+
# puts "attr=" + @attributes[arg_id].inspect
|
77
|
+
# puts 'val=' + @attributes[arg_id][0].inspect unless @attributes[arg_id].nil?
|
78
|
+
ret = nil
|
79
|
+
arg_id = name.to_s + '_id'
|
80
|
+
arg_id_val = send("#{arg_id}")
|
81
|
+
if arg_id_val
|
82
|
+
if !cache_store.nil?
|
83
83
|
# arg_id_val = @attributes[arg_id][0]
|
84
|
-
|
84
|
+
cache_key = self.class.cache_key(class_name, arg_id_val)
|
85
85
|
# puts 'cache_key=' + cache_key
|
86
|
-
|
86
|
+
ret = cache_store.read(cache_key)
|
87
87
|
# puts 'belongs_to incache=' + ret.inspect
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
end
|
89
|
+
if ret.nil?
|
90
|
+
to_eval = "#{class_name}.find('#{arg_id_val}')"
|
91
91
|
# puts 'to eval=' + to_eval
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
101
|
-
|
102
|
-
end
|
103
|
-
end
|
104
|
-
value = ret
|
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
|
92
|
+
begin
|
93
|
+
ret = eval(to_eval) # (defined? #{arg}_id)
|
94
|
+
rescue SimpleRecord::ActiveSdb::ActiveSdbError => ex
|
95
|
+
if ex.message.include? "Couldn't find"
|
96
|
+
ret = RemoteNil.new
|
97
|
+
else
|
98
|
+
raise ex
|
99
|
+
end
|
111
100
|
end
|
112
|
-
value
|
113
|
-
end
|
114
101
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
value = ret
|
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)
|
124
110
|
end
|
111
|
+
end
|
112
|
+
value
|
113
|
+
end
|
114
|
+
|
115
|
+
def string_val_to_ruby(att_meta, value)
|
116
|
+
if att_meta.type == :int
|
117
|
+
value = Translations.un_offset_int(value)
|
118
|
+
elsif att_meta.type == :date
|
119
|
+
value = to_date(value)
|
120
|
+
elsif att_meta.type == :boolean
|
121
|
+
value = to_bool(value)
|
122
|
+
end
|
123
|
+
value
|
124
|
+
end
|
125
125
|
|
126
126
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
end
|
151
|
-
return x_str
|
152
|
-
elsif x.is_a? Float
|
153
|
-
from_float(x)
|
154
|
-
else
|
155
|
-
return x
|
156
|
-
end
|
127
|
+
def self.pad_and_offset(x, att_meta=nil) # Change name to something more appropriate like ruby_to_sdb
|
128
|
+
# todo: add Float, etc
|
129
|
+
# puts 'padding=' + x.class.name + " -- " + x.inspect
|
130
|
+
if x.kind_of? Integer
|
131
|
+
x += @@offset
|
132
|
+
x_str = x.to_s
|
133
|
+
# pad
|
134
|
+
x_str = '0' + x_str while x_str.size < 20
|
135
|
+
return x_str
|
136
|
+
elsif x.respond_to?(:iso8601)
|
137
|
+
# puts x.class.name + ' responds to iso8601'
|
138
|
+
#
|
139
|
+
# There is an issue here where Time.iso8601 on an incomparable value to DateTime.iso8601.
|
140
|
+
# Amazon suggests: 2008-02-10T16:52:01.000-05:00
|
141
|
+
# "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
|
142
|
+
#
|
143
|
+
if x.is_a? DateTime
|
144
|
+
x_str = x.getutc.strftime(@@date_format)
|
145
|
+
elsif x.is_a? Time
|
146
|
+
x_str = x.getutc.strftime(@@date_format)
|
147
|
+
elsif x.is_a? Date
|
148
|
+
x_str = x.strftime(@@date_format)
|
149
|
+
|
157
150
|
end
|
151
|
+
return x_str
|
152
|
+
elsif x.is_a? Float
|
153
|
+
from_float(x)
|
154
|
+
else
|
155
|
+
return x
|
156
|
+
end
|
157
|
+
end
|
158
158
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
159
|
+
# This conversion to a string is based on: http://tools.ietf.org/html/draft-wood-ldapext-float-00
|
160
|
+
# Java code sample is here: http://code.google.com/p/typica/source/browse/trunk/java/com/xerox/amazonws/simpledb/DataUtils.java
|
161
|
+
def self.from_float(x)
|
162
|
+
return x
|
163
163
|
# if x == 0.0
|
164
164
|
# return "3 000 0.0000000000000000"
|
165
165
|
# end
|
166
|
-
|
166
|
+
end
|
167
167
|
|
168
168
|
|
169
|
-
|
170
|
-
|
169
|
+
def wrap_if_required(arg, value, sdb_val)
|
170
|
+
return nil if value.nil?
|
171
171
|
|
172
|
-
|
173
|
-
|
174
|
-
|
172
|
+
att_meta = defined_attributes_local[arg.to_sym]
|
173
|
+
if att_meta && att_meta.options
|
174
|
+
if att_meta.options[:hashed]
|
175
175
|
# puts 'wrapping ' + arg_s
|
176
|
-
|
177
|
-
end
|
178
|
-
end
|
179
|
-
value
|
176
|
+
return PasswordHashed.new(sdb_val)
|
180
177
|
end
|
178
|
+
end
|
179
|
+
value
|
180
|
+
end
|
181
181
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
182
|
+
def to_date(x)
|
183
|
+
if x.is_a?(String)
|
184
|
+
DateTime.parse(x)
|
185
|
+
else
|
186
|
+
x
|
187
|
+
end
|
188
|
+
end
|
189
189
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
190
|
+
def to_bool(x)
|
191
|
+
if x.is_a?(String)
|
192
|
+
x == "true" || x == "1"
|
193
|
+
else
|
194
|
+
x
|
195
|
+
end
|
196
|
+
end
|
197
197
|
|
198
|
-
|
199
|
-
|
200
|
-
|
198
|
+
def self.un_offset_int(x)
|
199
|
+
if x.is_a?(String)
|
200
|
+
x2 = x.to_i
|
201
201
|
# puts 'to_i=' + x2.to_s
|
202
|
-
|
202
|
+
x2 -= @@offset
|
203
203
|
# puts 'after subtracting offset='+ x2.to_s
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
204
|
+
x2
|
205
|
+
else
|
206
|
+
x
|
207
|
+
end
|
208
|
+
end
|
209
209
|
|
210
|
-
|
211
|
-
|
210
|
+
def unpad(i, attributes)
|
211
|
+
if !attributes[i].nil?
|
212
212
|
# puts 'before=' + self[i].inspect
|
213
|
-
|
214
|
-
|
213
|
+
attributes[i].collect! { |x|
|
214
|
+
un_offset_int(x)
|
215
215
|
|
216
|
-
|
217
|
-
|
218
|
-
|
216
|
+
}
|
217
|
+
end
|
218
|
+
end
|
219
219
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
end
|
225
|
-
end
|
220
|
+
def unpad_self
|
221
|
+
defined_attributes_local.each_pair do |name, att_meta|
|
222
|
+
if att_meta.type == :int
|
223
|
+
unpad(name, @attributes)
|
226
224
|
end
|
225
|
+
end
|
226
|
+
end
|
227
227
|
|
228
228
|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
229
|
+
def self.encrypt(value, key=nil)
|
230
|
+
key = key || get_encryption_key()
|
231
|
+
raise SimpleRecordError, "Encryption key must be defined on the attribute." if key.nil?
|
232
|
+
encrypted_value = SimpleRecord::Encryptor.encrypt(:value => value, :key => key)
|
233
|
+
encoded_value = Base64.encode64(encrypted_value)
|
234
|
+
encoded_value
|
235
|
+
end
|
236
236
|
|
237
237
|
|
238
|
-
|
238
|
+
def self.decrypt(value, key=nil)
|
239
239
|
# puts "decrypt orig value #{value} "
|
240
|
-
|
241
|
-
|
242
|
-
|
240
|
+
unencoded_value = Base64.decode64(value)
|
241
|
+
raise SimpleRecordError, "Encryption key must be defined on the attribute." if key.nil?
|
242
|
+
key = key || get_encryption_key()
|
243
243
|
# puts "decrypting #{unencoded_value} "
|
244
|
-
|
244
|
+
decrypted_value = SimpleRecord::Encryptor.decrypt(:value => unencoded_value, :key => key)
|
245
245
|
# "decrypted #{unencoded_value} to #{decrypted_value}"
|
246
|
-
|
247
|
-
|
246
|
+
decrypted_value
|
247
|
+
end
|
248
248
|
|
249
249
|
|
250
|
-
|
250
|
+
def pad_and_offset_ints_to_sdb()
|
251
251
|
|
252
252
|
# defined_attributes_local.each_pair do |name, att_meta|
|
253
253
|
# if att_meta.type == :int && !self[name.to_s].nil?
|
@@ -256,51 +256,51 @@ module SimpleRecord
|
|
256
256
|
# @attributes[name.to_s] = arr
|
257
257
|
# end
|
258
258
|
# end
|
259
|
-
|
259
|
+
end
|
260
260
|
|
261
|
-
|
261
|
+
def convert_dates_to_sdb()
|
262
262
|
|
263
263
|
# defined_attributes_local.each_pair do |name, att_meta|
|
264
264
|
# puts 'int encoding: ' + i.to_s
|
265
265
|
|
266
266
|
# end
|
267
|
-
|
268
|
-
|
269
|
-
def self.pass_hash(value)
|
270
|
-
hashed = Password::create_hash(value)
|
271
|
-
encoded_value = Base64.encode64(hashed)
|
272
|
-
encoded_value
|
273
|
-
end
|
267
|
+
end
|
274
268
|
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
269
|
+
def self.pass_hash(value)
|
270
|
+
hashed = Password::create_hash(value)
|
271
|
+
encoded_value = Base64.encode64(hashed)
|
272
|
+
encoded_value
|
273
|
+
end
|
279
274
|
|
275
|
+
def self.pass_hash_check(value, value_to_compare)
|
276
|
+
unencoded_value = Base64.decode64(value)
|
277
|
+
return Password::check(value_to_compare, unencoded_value)
|
280
278
|
end
|
281
279
|
|
280
|
+
end
|
282
281
|
|
283
|
-
class PasswordHashed
|
284
282
|
|
285
|
-
|
286
|
-
@value = value
|
287
|
-
end
|
283
|
+
class PasswordHashed
|
288
284
|
|
289
|
-
|
290
|
-
|
291
|
-
|
285
|
+
def initialize(value)
|
286
|
+
@value = value
|
287
|
+
end
|
292
288
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
return val.hashed_value == self.hashed_value
|
297
|
-
end
|
298
|
-
return Translations.pass_hash_check(@value, val)
|
299
|
-
end
|
289
|
+
def hashed_value
|
290
|
+
@value
|
291
|
+
end
|
300
292
|
|
301
|
-
|
302
|
-
|
303
|
-
|
293
|
+
# This allows you to compare an unhashed string to the hashed one.
|
294
|
+
def ==(val)
|
295
|
+
if val.is_a?(PasswordHashed)
|
296
|
+
return val.hashed_value == self.hashed_value
|
297
|
+
end
|
298
|
+
return Translations.pass_hash_check(@value, val)
|
299
|
+
end
|
300
|
+
|
301
|
+
def to_s
|
302
|
+
@value
|
304
303
|
end
|
304
|
+
end
|
305
305
|
|
306
306
|
end
|