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 +18 -1
- data/lib/simple_record.rb +5 -130
- data/lib/simple_record/attributes.rb +299 -152
- data/lib/simple_record/sharding.rb +14 -5
- data/lib/simple_record/translations.rb +41 -23
- data/test/test_base.rb +1 -1
- data/test/test_shards.rb +18 -0
- data/test/{simple_record_test.rb → test_simple_record.rb} +24 -1
- metadata +7 -7
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
|
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.
|
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
|
-
|
27
|
-
|
28
|
-
end
|
22
|
+
module ClassMethods
|
23
|
+
|
29
24
|
|
30
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
68
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
90
|
-
|
91
|
-
|
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
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
119
|
-
|
120
|
-
|
103
|
+
def has_booleans(*args)
|
104
|
+
has_attributes(*args)
|
105
|
+
are_booleans(*args)
|
121
106
|
end
|
122
|
-
end
|
123
107
|
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
122
|
+
def are_dates(*args)
|
123
|
+
args.each do |arg|
|
124
|
+
defined_attributes[arg].type = :date
|
125
|
+
end
|
126
|
+
end
|
136
127
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
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
|
-
|
182
|
-
|
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
|
-
|
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
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
29
|
-
|
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 <<
|
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
|
6
|
-
@@padding
|
7
|
-
@@date_format = "%Y-%m-%dT%H:%M:%S";
|
5
|
+
@@offset = 9223372036854775808
|
6
|
+
@@padding = 20
|
7
|
+
@@date_format = "%Y-%m-%dT%H:%M:%S";
|
8
8
|
|
9
|
-
def
|
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
|
-
|
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
|
21
|
-
ret =
|
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
|
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
|
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
|
72
|
-
arg_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
|
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
|
-
|
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
|
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
|
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
|
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
|
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("~/.
|
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
|
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
|
-
-
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version:
|
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-
|
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
|