simple_record 2.0.2 → 2.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +26 -2
- data/lib/simple_record/attributes.rb +7 -5
- data/lib/simple_record.rb +869 -831
- data/test/test_base.rb +5 -3
- data/test/test_global_options.rb +31 -11
- metadata +3 -3
data/lib/simple_record.rb
CHANGED
@@ -46,412 +46,434 @@ require_relative 'simple_record/sharding'
|
|
46
46
|
|
47
47
|
module SimpleRecord
|
48
48
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
49
|
+
@@options = {}
|
50
|
+
@@stats = SimpleRecord::Stats.new
|
51
|
+
@@logging = false
|
52
|
+
@@s3 = nil
|
53
|
+
@@auto_close_s3 = false
|
54
|
+
@@logger = Logger.new(STDOUT)
|
55
|
+
@@logger.level = Logger::INFO
|
56
|
+
|
57
|
+
class << self;
|
58
|
+
attr_accessor :aws_access_key, :aws_secret_key
|
59
|
+
|
60
|
+
# Deprecated
|
61
|
+
def enable_logging
|
62
|
+
@@logging = true
|
63
|
+
@@logger.level = Logger::DEBUG
|
64
|
+
end
|
56
65
|
|
57
|
-
|
58
|
-
|
66
|
+
# Deprecated
|
67
|
+
def disable_logging
|
68
|
+
@@logging = false
|
69
|
+
end
|
59
70
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
71
|
+
# Deprecated
|
72
|
+
def logging?
|
73
|
+
@@logging
|
74
|
+
end
|
65
75
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
76
|
+
def logger
|
77
|
+
@@logger
|
78
|
+
end
|
70
79
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
80
|
+
# This can be used to log queries and what not to a file.
|
81
|
+
# Params:
|
82
|
+
# :select=>{:filename=>"file_to_write_to", :format=>"csv"}
|
83
|
+
def log_usage(types={})
|
84
|
+
@usage_logging_options = {} unless @usage_logging_options
|
85
|
+
return if types.nil?
|
86
|
+
types.each_pair do |type, options|
|
87
|
+
options[:lines_between_flushes] = 100 unless options[:lines_between_flushes]
|
88
|
+
@usage_logging_options[type] = options
|
89
|
+
end
|
90
|
+
#puts 'SimpleRecord.usage_logging_options=' + SimpleRecord.usage_logging_options.inspect
|
91
|
+
end
|
75
92
|
|
76
|
-
|
77
|
-
|
78
|
-
|
93
|
+
def close_usage_log(type)
|
94
|
+
return unless @usage_logging_options[type]
|
95
|
+
@usage_logging_options[type][:file].close if @usage_logging_options[type][:file]
|
96
|
+
end
|
79
97
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
def log_usage(types={})
|
84
|
-
@usage_logging_options = {} unless @usage_logging_options
|
85
|
-
return if types.nil?
|
86
|
-
types.each_pair do |type, options|
|
87
|
-
options[:lines_between_flushes] = 100 unless options[:lines_between_flushes]
|
88
|
-
@usage_logging_options[type] = options
|
89
|
-
end
|
90
|
-
#puts 'SimpleRecord.usage_logging_options=' + SimpleRecord.usage_logging_options.inspect
|
91
|
-
end
|
98
|
+
def usage_logging_options
|
99
|
+
@usage_logging_options
|
100
|
+
end
|
92
101
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
102
|
+
def stats
|
103
|
+
@@stats
|
104
|
+
end
|
97
105
|
|
98
|
-
def usage_logging_options
|
99
|
-
@usage_logging_options
|
100
|
-
end
|
101
106
|
|
102
|
-
|
103
|
-
|
104
|
-
|
107
|
+
# Create a new handle to an Sdb account. All handles share the same per process or per thread
|
108
|
+
# HTTP connection to Amazon Sdb. Each handle is for a specific account.
|
109
|
+
# The +params+ are passed through as-is to Aws::SdbInterface.new
|
110
|
+
# Params:
|
111
|
+
# { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
|
112
|
+
# :port => 443 # Amazon service port: 80(default) or 443
|
113
|
+
# :protocol => 'https' # Amazon service protocol: 'http'(default) or 'https'
|
114
|
+
# :signature_version => '0' # The signature version : '0' or '1'(default)
|
115
|
+
# :connection_mode => :default # options are
|
116
|
+
# :default (will use best known safe (as in won't need explicit close) option, may change in the future)
|
117
|
+
# :per_request (opens and closes a connection on every request to SDB)
|
118
|
+
# :single (one thread across entire app)
|
119
|
+
# :per_thread (one connection per thread)
|
120
|
+
# :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET)
|
121
|
+
# :logger => Logger Object # Logger instance: logs to STDOUT if omitted
|
122
|
+
def establish_connection(aws_access_key=nil, aws_secret_key=nil, options={})
|
123
|
+
@aws_access_key = aws_access_key
|
124
|
+
@aws_secret_key = aws_secret_key
|
125
|
+
@@options.merge!(options)
|
126
|
+
#puts 'SimpleRecord.establish_connection with options: ' + @@options.inspect
|
127
|
+
SimpleRecord::ActiveSdb.establish_connection(aws_access_key, aws_secret_key, @@options)
|
128
|
+
if options[:connection_mode] == :per_thread
|
129
|
+
@@auto_close_s3 = true
|
130
|
+
# todo: should we init this only when needed?
|
131
|
+
end
|
132
|
+
s3_ops = {:connection_mode=>options[:connection_mode] || :default}
|
133
|
+
@@s3 = Aws::S3.new(SimpleRecord.aws_access_key, SimpleRecord.aws_secret_key, s3_ops)
|
134
|
+
|
135
|
+
if options[:created_col]
|
136
|
+
SimpleRecord::Base.has_dates options[:created_col]
|
137
|
+
end
|
138
|
+
if options[:updated_col]
|
139
|
+
SimpleRecord::Base.has_dates options[:updated_col]
|
140
|
+
end
|
105
141
|
|
106
142
|
|
107
|
-
|
108
|
-
# HTTP connection to Amazon Sdb. Each handle is for a specific account.
|
109
|
-
# The +params+ are passed through as-is to Aws::SdbInterface.new
|
110
|
-
# Params:
|
111
|
-
# { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
|
112
|
-
# :port => 443 # Amazon service port: 80(default) or 443
|
113
|
-
# :protocol => 'https' # Amazon service protocol: 'http'(default) or 'https'
|
114
|
-
# :signature_version => '0' # The signature version : '0' or '1'(default)
|
115
|
-
# :connection_mode => :default # options are
|
116
|
-
# :default (will use best known safe (as in won't need explicit close) option, may change in the future)
|
117
|
-
# :per_request (opens and closes a connection on every request to SDB)
|
118
|
-
# :single (one thread across entire app)
|
119
|
-
# :per_thread (one connection per thread)
|
120
|
-
# :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET)
|
121
|
-
# :logger => Logger Object # Logger instance: logs to STDOUT if omitted
|
122
|
-
def establish_connection(aws_access_key=nil, aws_secret_key=nil, options={})
|
123
|
-
@aws_access_key = aws_access_key
|
124
|
-
@aws_secret_key = aws_secret_key
|
125
|
-
@@options.merge!(options)
|
126
|
-
#puts 'SimpleRecord.establish_connection with options: ' + @@options.inspect
|
127
|
-
SimpleRecord::ActiveSdb.establish_connection(aws_access_key, aws_secret_key, @@options)
|
128
|
-
if options[:connection_mode] == :per_thread
|
129
|
-
@@auto_close_s3 = true
|
130
|
-
# todo: should we init this only when needed?
|
131
|
-
end
|
132
|
-
s3_ops = {:connection_mode=>options[:connection_mode] || :default}
|
133
|
-
@@s3 = Aws::S3.new(SimpleRecord.aws_access_key, SimpleRecord.aws_secret_key, s3_ops)
|
134
|
-
end
|
135
|
-
|
136
|
-
# Call this to close the connection to SimpleDB.
|
137
|
-
# If you're using this in Rails with per_thread connection mode, you should do this in
|
138
|
-
# an after_filter for each request.
|
139
|
-
def close_connection()
|
140
|
-
SimpleRecord::ActiveSdb.close_connection
|
141
|
-
@@s3.close_connection if @@auto_close_s3
|
142
|
-
end
|
143
|
+
end
|
143
144
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
145
|
+
# Call this to close the connection to SimpleDB.
|
146
|
+
# If you're using this in Rails with per_thread connection mode, you should do this in
|
147
|
+
# an after_filter for each request.
|
148
|
+
def close_connection()
|
149
|
+
SimpleRecord::ActiveSdb.close_connection
|
150
|
+
@@s3.close_connection if @@auto_close_s3
|
151
|
+
end
|
150
152
|
|
151
|
-
|
152
|
-
|
153
|
-
|
153
|
+
# If you'd like to specify the s3 connection to use for LOBs, you can pass it in here.
|
154
|
+
# We recommend that this connection matches the type of connection you're using for SimpleDB,
|
155
|
+
# at least if you're using per_thread connection mode.
|
156
|
+
def s3=(s3)
|
157
|
+
@@s3 = s3
|
158
|
+
end
|
154
159
|
|
155
|
-
|
156
|
-
|
157
|
-
|
160
|
+
def s3
|
161
|
+
@@s3
|
162
|
+
end
|
158
163
|
|
164
|
+
def options
|
165
|
+
@@options
|
159
166
|
end
|
160
167
|
|
161
|
-
|
168
|
+
end
|
169
|
+
|
170
|
+
class Base < SimpleRecord::ActiveSdb::Base
|
162
171
|
|
163
172
|
|
164
173
|
# puts 'Is ActiveModel defined? ' + defined?(ActiveModel).inspect
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
174
|
+
if defined?(ActiveModel)
|
175
|
+
extend ActiveModel::Naming
|
176
|
+
include ActiveModel::Conversion
|
177
|
+
include ActiveModel::Validations
|
178
|
+
else
|
179
|
+
attr_accessor :errors
|
180
|
+
include SimpleRecord::Rails2
|
181
|
+
end
|
173
182
|
|
174
|
-
|
183
|
+
include SimpleRecord::Translations
|
175
184
|
# include SimpleRecord::Attributes
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
185
|
+
extend SimpleRecord::Attributes::ClassMethods
|
186
|
+
include SimpleRecord::Attributes
|
187
|
+
extend SimpleRecord::Sharding::ClassMethods
|
188
|
+
include SimpleRecord::Sharding
|
189
|
+
include SimpleRecord::Callbacks
|
190
|
+
include SimpleRecord::Json
|
191
|
+
include SimpleRecord::Logging
|
192
|
+
extend SimpleRecord::Logging::ClassMethods
|
184
193
|
|
185
|
-
|
194
|
+
def self.extended(base)
|
186
195
|
|
187
|
-
|
196
|
+
end
|
188
197
|
|
189
|
-
|
190
|
-
|
198
|
+
def initialize(attrs={})
|
199
|
+
# todo: Need to deal with objects passed in. iterate through belongs_to perhaps and if in attrs, set the objects id rather than the object itself
|
191
200
|
|
192
|
-
|
201
|
+
initialize_base(attrs)
|
193
202
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
203
|
+
# Convert attributes to sdb values
|
204
|
+
attrs.each_pair do |name, value|
|
205
|
+
set(name, value, true)
|
206
|
+
end
|
207
|
+
end
|
199
208
|
|
200
|
-
|
209
|
+
def initialize_base(attrs={})
|
201
210
|
|
202
|
-
|
203
|
-
|
211
|
+
#we have to handle the virtuals.
|
212
|
+
Attributes.handle_virtuals(attrs)
|
204
213
|
|
205
|
-
|
206
|
-
@dirty = {}
|
214
|
+
clear_errors
|
207
215
|
|
208
|
-
|
209
|
-
@attributes_rb = {} # ruby values
|
210
|
-
@lobs = {}
|
211
|
-
@new_record = true
|
216
|
+
@dirty = {}
|
212
217
|
|
213
|
-
|
218
|
+
@attributes = {} # sdb values
|
219
|
+
@attributes_rb = {} # ruby values
|
220
|
+
@lobs = {}
|
221
|
+
@new_record = true
|
214
222
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
223
|
+
end
|
224
|
+
|
225
|
+
def initialize_from_db(attrs={})
|
226
|
+
initialize_base(attrs)
|
227
|
+
attrs.each_pair do |k, v|
|
228
|
+
@attributes[k.to_s] = v
|
229
|
+
end
|
230
|
+
end
|
221
231
|
|
222
232
|
|
223
|
-
|
224
|
-
|
225
|
-
|
233
|
+
def self.inherited(base)
|
234
|
+
# puts 'SimpleRecord::Base is inherited by ' + base.inspect
|
235
|
+
Callbacks.setup_callbacks(base)
|
226
236
|
|
227
237
|
# base.has_strings :id
|
228
|
-
|
229
|
-
|
230
|
-
|
238
|
+
base.has_dates :created, :updated
|
239
|
+
base.before_create :set_created, :set_updated
|
240
|
+
base.before_update :set_updated
|
231
241
|
|
232
|
-
|
242
|
+
end
|
233
243
|
|
234
244
|
|
235
|
-
|
236
|
-
|
237
|
-
|
245
|
+
def persisted?
|
246
|
+
!@new_record && !destroyed?
|
247
|
+
end
|
238
248
|
|
249
|
+
def destroyed?
|
250
|
+
@deleted
|
251
|
+
end
|
239
252
|
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
253
|
+
def defined_attributes_local
|
254
|
+
# todo: store this somewhere so it doesn't keep going through this
|
255
|
+
ret = self.class.defined_attributes
|
256
|
+
ret.merge!(self.class.superclass.defined_attributes) if self.class.superclass.respond_to?(:defined_attributes)
|
257
|
+
end
|
245
258
|
|
246
259
|
|
247
|
-
|
248
|
-
|
249
|
-
|
260
|
+
class << self;
|
261
|
+
attr_accessor :domain_prefix
|
262
|
+
end
|
250
263
|
|
251
|
-
|
264
|
+
#@domain_name_for_class = nil
|
252
265
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
266
|
+
@@cache_store = nil
|
267
|
+
# Set the cache to use
|
268
|
+
def self.cache_store=(cache)
|
269
|
+
@@cache_store = cache
|
270
|
+
end
|
258
271
|
|
259
|
-
|
260
|
-
|
261
|
-
|
272
|
+
def self.cache_store
|
273
|
+
return @@cache_store
|
274
|
+
end
|
262
275
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
276
|
+
# If you want a domain prefix for all your models, set it here.
|
277
|
+
def self.set_domain_prefix(prefix)
|
278
|
+
#puts 'set_domain_prefix=' + prefix
|
279
|
+
self.domain_prefix = prefix
|
280
|
+
end
|
268
281
|
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
282
|
+
# Same as set_table_name
|
283
|
+
def self.set_table_name(table_name)
|
284
|
+
set_domain_name table_name
|
285
|
+
end
|
273
286
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
287
|
+
# Sets the domain name for this class
|
288
|
+
def self.set_domain_name(table_name)
|
289
|
+
super
|
290
|
+
end
|
278
291
|
|
279
292
|
|
280
|
-
|
281
|
-
|
282
|
-
|
293
|
+
def domain
|
294
|
+
self.class.domain
|
295
|
+
end
|
283
296
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
297
|
+
def self.domain
|
298
|
+
unless @domain
|
299
|
+
# This strips off the module if there is one.
|
300
|
+
n2 = name.split('::').last || name
|
288
301
|
# puts 'n2=' + n2
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
end
|
294
|
-
set_domain_name @domain
|
295
|
-
end
|
296
|
-
domain_name_for_class = (SimpleRecord::Base.domain_prefix || "") + @domain.to_s
|
297
|
-
domain_name_for_class
|
302
|
+
if n2.respond_to?(:tableize)
|
303
|
+
@domain = n2.tableize
|
304
|
+
else
|
305
|
+
@domain = n2.downcase
|
298
306
|
end
|
307
|
+
set_domain_name @domain
|
308
|
+
end
|
309
|
+
domain_name_for_class = (SimpleRecord::Base.domain_prefix || "") + @domain.to_s
|
310
|
+
domain_name_for_class
|
311
|
+
end
|
299
312
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
313
|
+
def has_id_on_end(name_s)
|
314
|
+
name_s = name_s.to_s
|
315
|
+
name_s.length > 3 && name_s[-3..-1] == "_id"
|
316
|
+
end
|
304
317
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
318
|
+
def get_att_meta(name)
|
319
|
+
name_s = name.to_s
|
320
|
+
att_meta = defined_attributes_local[name.to_sym]
|
321
|
+
if att_meta.nil? && has_id_on_end(name_s)
|
322
|
+
att_meta = defined_attributes_local[name_s[0..-4].to_sym]
|
323
|
+
end
|
324
|
+
return att_meta
|
325
|
+
end
|
313
326
|
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
327
|
+
def sdb_att_name(name)
|
328
|
+
att_meta = get_att_meta(name)
|
329
|
+
if att_meta.type == :belongs_to && !has_id_on_end(name.to_s)
|
330
|
+
return "#{name}_id"
|
331
|
+
end
|
332
|
+
name.to_s
|
333
|
+
end
|
321
334
|
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
end
|
329
|
-
else
|
330
|
-
ret = arg
|
331
|
-
end
|
332
|
-
return ret
|
335
|
+
def strip_array(arg)
|
336
|
+
if arg.is_a? Array
|
337
|
+
if arg.length==1
|
338
|
+
ret = arg[0]
|
339
|
+
else
|
340
|
+
ret = arg
|
333
341
|
end
|
342
|
+
else
|
343
|
+
ret = arg
|
344
|
+
end
|
345
|
+
return ret
|
346
|
+
end
|
334
347
|
|
335
348
|
|
336
|
-
|
337
|
-
|
338
|
-
|
349
|
+
def make_dirty(arg, value)
|
350
|
+
sdb_att_name = sdb_att_name(arg)
|
351
|
+
arg = arg.to_s
|
339
352
|
|
340
353
|
# puts "Marking #{arg} dirty with #{value}" if SimpleRecord.logging?
|
341
|
-
|
342
|
-
|
354
|
+
if @dirty.include?(sdb_att_name)
|
355
|
+
old = @dirty[sdb_att_name]
|
343
356
|
# puts "#{sdb_att_name} was already dirty #{old}"
|
344
|
-
|
345
|
-
|
346
|
-
|
357
|
+
@dirty.delete(sdb_att_name) if value == old
|
358
|
+
else
|
359
|
+
old = get_attribute(arg)
|
347
360
|
# puts "dirtifying #{sdb_att_name} old=#{old.inspect} to new=#{value.inspect}" if SimpleRecord.logging?
|
348
|
-
|
349
|
-
|
350
|
-
|
361
|
+
@dirty[sdb_att_name] = old if value != old
|
362
|
+
end
|
363
|
+
end
|
351
364
|
|
352
|
-
|
353
|
-
|
354
|
-
|
365
|
+
def clear_errors
|
366
|
+
# @errors=SimpleRecord_errors.new
|
367
|
+
if not (defined?(ActiveModel))
|
368
|
+
@errors=SimpleRecord_errors.new
|
369
|
+
else
|
370
|
+
@errors = ActiveModel::Errors.new(self)
|
371
|
+
end
|
372
|
+
end
|
355
373
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
374
|
+
def []=(attribute, values)
|
375
|
+
make_dirty(attribute, values)
|
376
|
+
super
|
377
|
+
end
|
360
378
|
|
361
|
-
|
362
|
-
|
363
|
-
|
379
|
+
def [](attribute)
|
380
|
+
super
|
381
|
+
end
|
364
382
|
|
365
383
|
|
366
|
-
|
367
|
-
|
368
|
-
|
384
|
+
def set_created
|
385
|
+
set(SimpleRecord.options[:created_col] || :created, Time.now)
|
386
|
+
end
|
369
387
|
|
370
|
-
|
371
|
-
|
372
|
-
|
388
|
+
def set_updated
|
389
|
+
set(SimpleRecord.options[:updated_col] || :updated, Time.now)
|
390
|
+
end
|
373
391
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
392
|
+
# an aliased method since many people use created_at/updated_at naming convention
|
393
|
+
def created_at
|
394
|
+
self.created
|
395
|
+
end
|
378
396
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
397
|
+
# an aliased method since many people use created_at/updated_at naming convention
|
398
|
+
def updated_at
|
399
|
+
self.updated
|
400
|
+
end
|
383
401
|
|
384
|
-
|
385
|
-
|
386
|
-
|
402
|
+
def read_attribute_for_validation(key)
|
403
|
+
@attributes[key.to_s]
|
404
|
+
end
|
387
405
|
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
self.class.create_domain(dom)
|
392
|
-
return true
|
393
|
-
end
|
394
|
-
return false
|
395
|
-
end
|
406
|
+
def cache_store
|
407
|
+
@@cache_store
|
408
|
+
end
|
396
409
|
|
410
|
+
def domain_ok(ex, options={})
|
411
|
+
if (ex.message().index("NoSuchDomain") != nil)
|
412
|
+
dom = options[:domain] || domain
|
413
|
+
self.class.create_domain(dom)
|
414
|
+
return true
|
415
|
+
end
|
416
|
+
return false
|
417
|
+
end
|
397
418
|
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
419
|
+
|
420
|
+
def new_record?
|
421
|
+
# todo: new_record in activesdb should align with how we're defining a new record here, ie: if id is nil
|
422
|
+
super
|
423
|
+
end
|
402
424
|
|
403
425
|
|
404
|
-
|
426
|
+
@create_domain_called = false
|
405
427
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
428
|
+
# Options:
|
429
|
+
# - :except => Array of attributes to NOT save
|
430
|
+
# - :dirty => true - Will only store attributes that were modified. To make it save regardless and have it update the :updated value, include this and set it to false.
|
431
|
+
# - :domain => Explicitly define domain to use.
|
432
|
+
#
|
433
|
+
def save(options={})
|
412
434
|
# puts 'SAVING: ' + self.inspect if SimpleRecord.logging?
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
435
|
+
# todo: Clean out undefined values in @attributes (in case someone set the attributes hash with values that they hadn't defined)
|
436
|
+
clear_errors
|
437
|
+
# todo: decide whether this should go before pre_save or after pre_save? pre_save dirties "updated" and perhaps other items due to callbacks
|
438
|
+
if options[:dirty]
|
417
439
|
# puts '@dirtyA=' + @dirty.inspect
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
440
|
+
return true if @dirty.size == 0 # Nothing to save so skip it
|
441
|
+
end
|
442
|
+
is_create = self[:id].nil?
|
443
|
+
ok = pre_save(options) # Validates and sets ID
|
444
|
+
if ok
|
445
|
+
begin
|
446
|
+
dirty = @dirty
|
425
447
|
# puts 'dirty before=' + @dirty.inspect
|
426
|
-
|
448
|
+
if options[:dirty]
|
427
449
|
# puts '@dirty=' + @dirty.inspect
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
450
|
+
return true if @dirty.size == 0 # This should probably never happen because after pre_save, created/updated dates are changed
|
451
|
+
options[:dirty_atts] = @dirty
|
452
|
+
end
|
453
|
+
to_delete = get_atts_to_delete
|
454
|
+
SimpleRecord.stats.saves += 1
|
455
|
+
|
456
|
+
if self.class.is_sharded?
|
457
|
+
options[:domain] = sharded_domain
|
458
|
+
end
|
459
|
+
|
460
|
+
if super(options)
|
461
|
+
self.class.cache_results(self)
|
462
|
+
delete_niled(to_delete)
|
463
|
+
save_lobs(dirty)
|
464
|
+
after_save_cleanup
|
465
|
+
if (is_create ? run_after_create : run_after_update) && run_after_save
|
444
466
|
# puts 'all good?'
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
467
|
+
return true
|
468
|
+
else
|
469
|
+
return false
|
470
|
+
end
|
471
|
+
else
|
472
|
+
return false
|
473
|
+
end
|
474
|
+
rescue Aws::AwsError => ex
|
475
|
+
# puts "RESCUED in save: " + $!
|
476
|
+
# Domain is created in aws lib now using :create_domain=>true
|
455
477
|
# if (domain_ok(ex, options))
|
456
478
|
# if !@create_domain_called
|
457
479
|
# @create_domain_called = true
|
@@ -462,201 +484,218 @@ module SimpleRecord
|
|
462
484
|
# else
|
463
485
|
# raise $!
|
464
486
|
# end
|
465
|
-
|
466
|
-
end
|
467
|
-
else
|
468
|
-
#@debug = "not saved"
|
469
|
-
return false
|
470
|
-
end
|
487
|
+
raise ex
|
471
488
|
end
|
489
|
+
else
|
490
|
+
#@debug = "not saved"
|
491
|
+
return false
|
492
|
+
end
|
493
|
+
end
|
472
494
|
|
473
|
-
|
495
|
+
def save_lobs(dirty=nil)
|
474
496
|
# puts 'dirty.inspect=' + dirty.inspect
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
497
|
+
dirty = @dirty if dirty.nil?
|
498
|
+
all_clobs = {}
|
499
|
+
dirty_clobs = {}
|
500
|
+
defined_attributes_local.each_pair do |k, v|
|
501
|
+
# collect up the clobs in case it's a single put
|
502
|
+
if v.type == :clob
|
503
|
+
val = @lobs[k]
|
504
|
+
all_clobs[k] = val
|
505
|
+
if dirty.include?(k.to_s)
|
506
|
+
dirty_clobs[k] = val
|
507
|
+
else
|
486
508
|
# puts 'NOT DIRTY'
|
487
|
-
|
509
|
+
end
|
488
510
|
|
489
|
-
end
|
490
|
-
end
|
491
|
-
if dirty_clobs.size > 0
|
492
|
-
if self.class.get_sr_config[:single_clob]
|
493
|
-
# all clobs in one chunk
|
494
|
-
# using json for now, could change later
|
495
|
-
val = all_clobs.to_json
|
496
|
-
puts 'val=' + val.inspect
|
497
|
-
put_lob(single_clob_id, val, :new_bucket=>true)
|
498
|
-
else
|
499
|
-
dirty_clobs.each_pair do |k, val|
|
500
|
-
put_lob(s3_lob_id(k), val)
|
501
|
-
end
|
502
|
-
end
|
503
|
-
end
|
504
511
|
end
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
end
|
518
|
-
end
|
512
|
+
end
|
513
|
+
if dirty_clobs.size > 0
|
514
|
+
if self.class.get_sr_config[:single_clob]
|
515
|
+
# all clobs in one chunk
|
516
|
+
# using json for now, could change later
|
517
|
+
val = all_clobs.to_json
|
518
|
+
puts 'val=' + val.inspect
|
519
|
+
put_lob(single_clob_id, val, :s3_bucket=>:new)
|
520
|
+
else
|
521
|
+
dirty_clobs.each_pair do |k, val|
|
522
|
+
put_lob(s3_lob_id(k), val)
|
523
|
+
end
|
519
524
|
end
|
525
|
+
end
|
526
|
+
end
|
520
527
|
|
528
|
+
def delete_lobs
|
529
|
+
defined_attributes_local.each_pair do |k, v|
|
530
|
+
if v.type == :clob
|
531
|
+
if self.class.get_sr_config[:single_clob]
|
532
|
+
s3_bucket(false, :s3_bucket=>:new).delete_key(single_clob_id)
|
533
|
+
SimpleRecord.stats.s3_deletes += 1
|
534
|
+
return
|
535
|
+
else
|
536
|
+
s3_bucket.delete_key(s3_lob_id(k))
|
537
|
+
SimpleRecord.stats.s3_deletes += 1
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|
521
542
|
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
end
|
532
|
-
SimpleRecord.stats.s3_puts += 1
|
543
|
+
|
544
|
+
def put_lob(k, val, options={})
|
545
|
+
begin
|
546
|
+
s3_bucket(false, options).put(k, val)
|
547
|
+
rescue Aws::AwsError => ex
|
548
|
+
if ex.include? /NoSuchBucket/
|
549
|
+
s3_bucket(true, options).put(k, val)
|
550
|
+
else
|
551
|
+
raise ex
|
533
552
|
end
|
553
|
+
end
|
554
|
+
SimpleRecord.stats.s3_puts += 1
|
555
|
+
end
|
534
556
|
|
535
557
|
|
536
|
-
|
537
|
-
|
558
|
+
def is_dirty?(name)
|
559
|
+
# todo: should change all the dirty stuff to symbols?
|
538
560
|
# puts '@dirty=' + @dirty.inspect
|
539
561
|
# puts 'name=' +name.to_s
|
540
|
-
|
541
|
-
|
562
|
+
@dirty.include? name.to_s
|
563
|
+
end
|
542
564
|
|
543
|
-
|
565
|
+
def s3
|
544
566
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
567
|
+
return SimpleRecord.s3 if SimpleRecord.s3
|
568
|
+
# todo: should optimize this somehow, like use the same connection_mode as used in SR
|
569
|
+
# or keep open while looping in ResultsArray.
|
570
|
+
Aws::S3.new(SimpleRecord.aws_access_key, SimpleRecord.aws_secret_key)
|
571
|
+
end
|
550
572
|
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
573
|
+
# options:
|
574
|
+
# :s3_bucket => :old/:new/"#{any_bucket_name}". :new if want to use new bucket. Defaults to :old for backwards compatability.
|
575
|
+
def s3_bucket(create=false, options={})
|
576
|
+
s3.bucket(s3_bucket_name(options[:s3_bucket]), create)
|
577
|
+
end
|
556
578
|
|
579
|
+
def s3_bucket_name(s3_bucket_option=:old)
|
580
|
+
if s3_bucket_option == :new || SimpleRecord.options[:s3_bucket] == :new
|
557
581
|
# this is the bucket that will be used going forward for anything related to s3
|
558
|
-
|
559
|
-
|
560
|
-
|
582
|
+
ret = "simple_record_#{SimpleRecord.aws_access_key}"
|
583
|
+
elsif !SimpleRecord.options[:s3_bucket].nil? && SimpleRecord.options[:s3_bucket] != :old
|
584
|
+
ret = SimpleRecord.options[:s3_bucket]
|
585
|
+
else
|
586
|
+
ret = SimpleRecord.aws_access_key + "_lobs"
|
587
|
+
end
|
588
|
+
ret
|
589
|
+
end
|
561
590
|
|
562
|
-
|
563
|
-
|
564
|
-
|
591
|
+
def s3_lob_id(name)
|
592
|
+
# if s3_bucket is not nil and not :old, then we use the new key.
|
593
|
+
if !SimpleRecord.options[:s3_bucket].nil? && SimpleRecord.options[:s3_bucket] != :old
|
594
|
+
"lobs/#{self.id}_#{name}"
|
595
|
+
else
|
596
|
+
self.id + "_" + name.to_s
|
597
|
+
end
|
598
|
+
end
|
565
599
|
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
else
|
570
|
-
self.id + "_" + name.to_s
|
571
|
-
end
|
572
|
-
end
|
600
|
+
def single_clob_id
|
601
|
+
"lobs/#{self.id}_single_clob"
|
602
|
+
end
|
573
603
|
|
574
|
-
|
575
|
-
|
576
|
-
|
604
|
+
def save!(options={})
|
605
|
+
save(options) || raise(RecordNotSaved)
|
606
|
+
end
|
577
607
|
|
578
|
-
|
579
|
-
|
580
|
-
|
608
|
+
def self.create(attributes={})
|
609
|
+
# puts "About to create in domain #{domain}"
|
610
|
+
super
|
611
|
+
end
|
581
612
|
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
613
|
+
def self.create!(attributes={})
|
614
|
+
item = self.new(attributes)
|
615
|
+
item.save!
|
616
|
+
item
|
617
|
+
end
|
618
|
+
|
619
|
+
def save_with_validation!(options={})
|
620
|
+
if valid?
|
621
|
+
save
|
622
|
+
else
|
623
|
+
raise RecordInvalid.new(self)
|
624
|
+
end
|
625
|
+
end
|
589
626
|
|
590
627
|
|
591
|
-
|
592
|
-
|
628
|
+
def self.get_encryption_key()
|
629
|
+
key = SimpleRecord.options[:encryption_key]
|
593
630
|
# if key.nil?
|
594
631
|
# puts 'WARNING: Encrypting attributes with your AWS Access Key. You should use your own :encryption_key so it doesn\'t change'
|
595
632
|
# key = connection.aws_access_key_id # default to aws access key. NOT recommended in case you start using a new key
|
596
633
|
# end
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
def validate
|
602
|
-
true
|
603
|
-
end
|
604
|
-
|
605
|
-
def validate_on_create
|
606
|
-
true
|
607
|
-
end
|
608
|
-
|
609
|
-
def validate_on_update
|
610
|
-
true
|
611
|
-
end
|
634
|
+
return key
|
635
|
+
end
|
612
636
|
|
637
|
+
def validate
|
638
|
+
true
|
639
|
+
end
|
613
640
|
|
614
|
-
|
641
|
+
def validate_on_create
|
642
|
+
true
|
643
|
+
end
|
615
644
|
|
616
|
-
|
617
|
-
|
618
|
-
|
645
|
+
def validate_on_update
|
646
|
+
true
|
647
|
+
end
|
619
648
|
|
620
|
-
validate()
|
621
649
|
|
622
|
-
|
623
|
-
# puts 'AFTER VALIDATIONS, ERRORS=' + errors.inspect
|
624
|
-
if (!errors.nil? && errors.size > 0)
|
625
|
-
# puts 'THERE ARE ERRORS, returning false'
|
626
|
-
return false
|
627
|
-
end
|
650
|
+
def pre_save(options)
|
628
651
|
|
629
|
-
|
630
|
-
|
652
|
+
is_create = self[:id].nil?
|
653
|
+
ok = run_before_validation && (is_create ? run_before_validation_on_create : run_before_validation_on_update)
|
654
|
+
return false unless ok
|
631
655
|
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
656
|
+
# validate()
|
657
|
+
# is_create ? validate_on_create : validate_on_update
|
658
|
+
if !valid?
|
659
|
+
return false
|
660
|
+
end
|
661
|
+
#
|
662
|
+
## puts 'AFTER VALIDATIONS, ERRORS=' + errors.inspect
|
663
|
+
# if (!errors.nil? && errors.size > 0)
|
664
|
+
## puts 'THERE ARE ERRORS, returning false'
|
665
|
+
# return false
|
666
|
+
# end
|
667
|
+
|
668
|
+
ok = run_after_validation && (is_create ? run_after_validation_on_create : run_after_validation_on_update)
|
669
|
+
return false unless ok
|
670
|
+
|
671
|
+
ok = respond_to?('before_save') ? before_save : true
|
672
|
+
if ok
|
673
|
+
if is_create && respond_to?('before_create')
|
674
|
+
ok = before_create
|
675
|
+
elsif !is_create && respond_to?('before_update')
|
676
|
+
ok = before_update
|
677
|
+
end
|
678
|
+
end
|
679
|
+
if ok
|
680
|
+
ok = run_before_save && (is_create ? run_before_create : run_before_update)
|
681
|
+
end
|
682
|
+
if ok
|
683
|
+
# Now translate all fields into SimpleDB friendly strings
|
645
684
|
# convert_all_atts_to_sdb()
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
685
|
+
end
|
686
|
+
prepare_for_update
|
687
|
+
ok
|
688
|
+
end
|
650
689
|
|
651
690
|
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
691
|
+
def get_atts_to_delete
|
692
|
+
to_delete = []
|
693
|
+
changes.each_pair do |key, v|
|
694
|
+
if v[1].nil?
|
695
|
+
to_delete << key
|
696
|
+
@attributes.delete(key)
|
697
|
+
end
|
698
|
+
end
|
660
699
|
# @attributes.each do |key, value|
|
661
700
|
## puts 'key=' + key.inspect + ' value=' + value.inspect
|
662
701
|
# if value.nil? || (value.is_a?(Array) && value.size == 0) || (value.is_a?(Array) && value.size == 1 && value[0] == nil)
|
@@ -664,437 +703,436 @@ module SimpleRecord
|
|
664
703
|
# @attributes.delete(key)
|
665
704
|
# end
|
666
705
|
# end
|
667
|
-
|
668
|
-
|
706
|
+
return to_delete
|
707
|
+
end
|
669
708
|
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
709
|
+
# Run pre_save on each object, then runs batch_put_attributes
|
710
|
+
# Returns
|
711
|
+
def self.batch_save(objects, options={})
|
712
|
+
options[:create_domain] = true if options[:create_domain].nil?
|
713
|
+
results = []
|
714
|
+
to_save = []
|
715
|
+
if objects && objects.size > 0
|
716
|
+
objects.each do |o|
|
717
|
+
ok = o.pre_save(options)
|
718
|
+
raise "Pre save failed on object [" + o.inspect + "]" if !ok
|
719
|
+
results << ok
|
720
|
+
next if !ok # todo: this shouldn't be here should it? raises above
|
721
|
+
o.pre_save2
|
722
|
+
to_save << Aws::SdbInterface::Item.new(o.id, o.attributes, true)
|
723
|
+
if to_save.size == 25 # Max amount SDB will accept
|
724
|
+
connection.batch_put_attributes(domain, to_save, options)
|
725
|
+
to_save.clear
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
connection.batch_put_attributes(domain, to_save, options) if to_save.size > 0
|
730
|
+
objects.each do |o|
|
731
|
+
o.save_lobs(nil)
|
732
|
+
end
|
733
|
+
results
|
734
|
+
end
|
696
735
|
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
736
|
+
# Pass in an array of objects
|
737
|
+
def self.batch_delete(objects, options={})
|
738
|
+
if objects
|
739
|
+
# 25 item limit, we should maybe handle this limit in here.
|
740
|
+
connection.batch_delete_attributes @domain, objects.collect { |x| x.id }
|
741
|
+
end
|
742
|
+
end
|
704
743
|
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
744
|
+
#
|
745
|
+
# Usage: ClassName.delete id
|
746
|
+
#
|
747
|
+
def self.delete(id)
|
748
|
+
connection.delete_attributes(domain, id)
|
749
|
+
@deleted = true
|
750
|
+
end
|
711
751
|
|
712
|
-
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
|
752
|
+
# Pass in the same OPTIONS you'd pass into a find(:all, OPTIONS)
|
753
|
+
def self.delete_all(options)
|
754
|
+
# could make this quicker by just getting item_names and deleting attributes rather than creating objects
|
755
|
+
obs = self.find(:all, options)
|
756
|
+
i = 0
|
757
|
+
obs.each do |a|
|
758
|
+
a.delete
|
759
|
+
i+=1
|
760
|
+
end
|
761
|
+
return i
|
762
|
+
end
|
723
763
|
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
764
|
+
# Pass in the same OPTIONS you'd pass into a find(:all, OPTIONS)
|
765
|
+
def self.destroy_all(options)
|
766
|
+
obs = self.find(:all, options)
|
767
|
+
i = 0
|
768
|
+
obs.each do |a|
|
769
|
+
a.destroy
|
770
|
+
i+=1
|
771
|
+
end
|
772
|
+
return i
|
773
|
+
end
|
734
774
|
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
775
|
+
def delete(options={})
|
776
|
+
if self.class.is_sharded?
|
777
|
+
options[:domain] = sharded_domain
|
778
|
+
end
|
779
|
+
super(options)
|
740
780
|
|
741
|
-
|
742
|
-
|
743
|
-
|
781
|
+
# delete lobs now too
|
782
|
+
delete_lobs
|
783
|
+
end
|
744
784
|
|
745
|
-
|
746
|
-
|
747
|
-
|
785
|
+
def destroy
|
786
|
+
return run_before_destroy && delete && run_after_destroy
|
787
|
+
end
|
748
788
|
|
749
789
|
|
750
|
-
|
790
|
+
def delete_niled(to_delete)
|
751
791
|
# puts 'to_delete=' + to_delete.inspect
|
752
|
-
|
792
|
+
if to_delete.size > 0
|
753
793
|
# puts 'Deleting attributes=' + to_delete.inspect
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
794
|
+
SimpleRecord.stats.deletes += 1
|
795
|
+
delete_attributes to_delete
|
796
|
+
to_delete.each do |att|
|
797
|
+
att_meta = get_att_meta(att)
|
798
|
+
if att_meta.type == :clob
|
799
|
+
s3_bucket.key(s3_lob_id(att)).delete
|
800
|
+
end
|
801
|
+
end
|
802
|
+
end
|
803
|
+
end
|
764
804
|
|
765
|
-
|
766
|
-
|
767
|
-
|
805
|
+
def reload
|
806
|
+
super()
|
807
|
+
end
|
768
808
|
|
769
809
|
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
|
810
|
+
def update_attributes(atts)
|
811
|
+
set_attributes(atts)
|
812
|
+
save
|
813
|
+
end
|
774
814
|
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
|
815
|
+
def update_attributes!(atts)
|
816
|
+
set_attributes(atts)
|
817
|
+
save!
|
818
|
+
end
|
779
819
|
|
780
820
|
|
781
|
-
|
782
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
before =~ /'$/ #is there already a quote immediately before the match?
|
790
|
-
unless $&
|
791
|
-
return "#{before}'#{middle}'#{quote_regexp(after, re)}" #if not, put quotes around the match
|
792
|
-
else
|
793
|
-
return "#{before}#{middle}#{quote_regexp(after, re)}" #if so, assume it is quoted already and move on
|
794
|
-
end
|
795
|
-
else
|
796
|
-
#no match, just return the string
|
797
|
-
return a
|
798
|
-
end
|
799
|
-
end
|
821
|
+
def self.quote_regexp(a, re)
|
822
|
+
a =~ re
|
823
|
+
#was there a match?
|
824
|
+
if $&
|
825
|
+
before=$`
|
826
|
+
middle=$&
|
827
|
+
after =$'
|
800
828
|
|
801
|
-
|
802
|
-
|
803
|
-
|
829
|
+
before =~ /'$/ #is there already a quote immediately before the match?
|
830
|
+
unless $&
|
831
|
+
return "#{before}'#{middle}'#{quote_regexp(after, re)}" #if not, put quotes around the match
|
832
|
+
else
|
833
|
+
return "#{before}#{middle}#{quote_regexp(after, re)}" #if so, assume it is quoted already and move on
|
804
834
|
end
|
835
|
+
else
|
836
|
+
#no match, just return the string
|
837
|
+
return a
|
838
|
+
end
|
839
|
+
end
|
805
840
|
|
806
|
-
|
807
|
-
|
808
|
-
|
809
|
-
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
841
|
+
@@regex_no_id = /.*Couldn't find.*with ID.*/
|
842
|
+
|
843
|
+
#
|
844
|
+
# Usage:
|
845
|
+
# Find by ID:
|
846
|
+
# MyModel.find(ID)
|
847
|
+
#
|
848
|
+
# Query example:
|
849
|
+
# MyModel.find(:all, :conditions=>["name = ?", name], :order=>"created desc", :limit=>10)
|
850
|
+
#
|
851
|
+
# Extra options:
|
852
|
+
# :per_token => the number of results to return per next_token, max is 2500.
|
853
|
+
# :consistent_read => true/false -- as per http://developer.amazonwebservices.com/connect/entry.jspa?externalID=3572
|
854
|
+
# :retries => maximum number of times to retry this query on an error response.
|
855
|
+
# :shard => shard name or array of shard names to use on this query.
|
856
|
+
def self.find(*params)
|
857
|
+
#puts 'params=' + params.inspect
|
858
|
+
|
859
|
+
q_type = :all
|
860
|
+
select_attributes=[]
|
861
|
+
if params.size > 0
|
862
|
+
q_type = params[0]
|
863
|
+
end
|
864
|
+
options = {}
|
865
|
+
if params.size > 1
|
866
|
+
options = params[1]
|
867
|
+
end
|
868
|
+
|
869
|
+
if !options[:shard_find] && is_sharded?
|
870
|
+
# then break off and get results across all shards
|
871
|
+
return find_sharded(*params)
|
872
|
+
end
|
873
|
+
|
874
|
+
# Pad and Offset number attributes
|
875
|
+
params_dup = params.dup
|
876
|
+
if params.size > 1
|
877
|
+
options = params[1]
|
878
|
+
#puts 'options=' + options.inspect
|
879
|
+
#puts 'after collect=' + options.inspect
|
880
|
+
convert_condition_params(options)
|
881
|
+
per_token = options[:per_token]
|
882
|
+
consistent_read = options[:consistent_read]
|
883
|
+
if per_token || consistent_read then
|
884
|
+
op_dup = options.dup
|
885
|
+
op_dup[:limit] = per_token # simpledb uses Limit as a paging thing, not what is normal
|
886
|
+
op_dup[:consistent_read] = consistent_read
|
887
|
+
params_dup[1] = op_dup
|
888
|
+
end
|
889
|
+
end
|
855
890
|
# puts 'params2=' + params.inspect
|
856
891
|
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
results=find_with_metadata(*params_dup)
|
892
|
+
ret = q_type == :all ? [] : nil
|
893
|
+
begin
|
894
|
+
results=find_with_metadata(*params_dup)
|
861
895
|
# puts "RESULT=" + results.inspect
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
896
|
+
write_usage(:select, domain, q_type, options, results)
|
897
|
+
#puts 'params3=' + params.inspect
|
898
|
+
SimpleRecord.stats.selects += 1
|
899
|
+
if q_type == :count
|
900
|
+
ret = results[:count]
|
901
|
+
elsif q_type == :first
|
902
|
+
ret = results[:items].first
|
903
|
+
# todo: we should store request_id and box_usage with the object maybe?
|
904
|
+
cache_results(ret)
|
905
|
+
elsif results[:single]
|
906
|
+
ret = results[:single]
|
907
|
+
cache_results(ret)
|
908
|
+
else
|
909
|
+
if results[:items] #.is_a?(Array)
|
910
|
+
cache_results(results[:items])
|
911
|
+
ret = SimpleRecord::ResultsArray.new(self, params, results, next_token)
|
912
|
+
end
|
913
|
+
end
|
914
|
+
rescue Aws::AwsError, SimpleRecord::ActiveSdb::ActiveSdbError => ex
|
881
915
|
# puts "RESCUED: " + ex.message
|
882
|
-
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
end
|
889
|
-
end
|
890
|
-
# puts 'single2=' + ret.inspect
|
891
|
-
return ret
|
916
|
+
if (ex.message().index("NoSuchDomain") != nil)
|
917
|
+
# this is ok
|
918
|
+
elsif (ex.message() =~ @@regex_no_id)
|
919
|
+
ret = nil
|
920
|
+
else
|
921
|
+
raise ex
|
892
922
|
end
|
923
|
+
end
|
924
|
+
# puts 'single2=' + ret.inspect
|
925
|
+
return ret
|
926
|
+
end
|
893
927
|
|
894
|
-
|
895
|
-
|
896
|
-
|
928
|
+
def self.select(*params)
|
929
|
+
return find(*params)
|
930
|
+
end
|
897
931
|
|
898
|
-
|
899
|
-
|
900
|
-
|
932
|
+
def self.all(*args)
|
933
|
+
find(:all, *args)
|
934
|
+
end
|
901
935
|
|
902
|
-
|
903
|
-
|
904
|
-
|
936
|
+
def self.first(*args)
|
937
|
+
find(:first, *args)
|
938
|
+
end
|
905
939
|
|
906
|
-
|
907
|
-
|
908
|
-
|
940
|
+
def self.count(*args)
|
941
|
+
find(:count, *args)
|
942
|
+
end
|
909
943
|
|
910
|
-
|
911
|
-
|
912
|
-
|
944
|
+
# This gets less and less efficient the higher the page since SimpleDB has no way
|
945
|
+
# to start at a specific row. So it will iterate from the first record and pull out the specific pages.
|
946
|
+
def self.paginate(options={})
|
913
947
|
# options = args.pop
|
914
948
|
# puts 'paginate options=' + options.inspect if SimpleRecord.logging?
|
915
|
-
|
916
|
-
|
949
|
+
page = options[:page] || 1
|
950
|
+
per_page = options[:per_page] || 50
|
917
951
|
# total = options[:total_entries].to_i
|
918
|
-
|
919
|
-
|
920
|
-
|
952
|
+
options[:page] = page.to_i # makes sure it's to_i
|
953
|
+
options[:per_page] = per_page.to_i
|
954
|
+
options[:limit] = options[:page] * options[:per_page]
|
921
955
|
# puts 'paging options=' + options.inspect
|
922
|
-
|
923
|
-
|
956
|
+
fr = find(:all, options)
|
957
|
+
return fr
|
924
958
|
|
925
|
-
|
959
|
+
end
|
926
960
|
|
927
961
|
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
962
|
+
def self.convert_condition_params(options)
|
963
|
+
return if options.nil?
|
964
|
+
conditions = options[:conditions]
|
965
|
+
if !conditions.nil? && conditions.size > 1
|
966
|
+
# all after first are values
|
967
|
+
conditions.collect! { |x|
|
968
|
+
Translations.pad_and_offset(x)
|
969
|
+
}
|
970
|
+
end
|
937
971
|
|
938
|
-
|
972
|
+
end
|
939
973
|
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
end
|
958
|
-
end
|
974
|
+
def self.cache_results(results)
|
975
|
+
if !cache_store.nil? && !results.nil?
|
976
|
+
if results.is_a?(Array)
|
977
|
+
# todo: cache each result
|
978
|
+
results.each do |item|
|
979
|
+
class_name = item.class.name
|
980
|
+
id = item.id
|
981
|
+
cache_key = self.cache_key(class_name, id)
|
982
|
+
#puts 'caching result at ' + cache_key + ': ' + results.inspect
|
983
|
+
cache_store.write(cache_key, item, :expires_in =>30)
|
984
|
+
end
|
985
|
+
else
|
986
|
+
class_name = results.class.name
|
987
|
+
id = results.id
|
988
|
+
cache_key = self.cache_key(class_name, id)
|
989
|
+
#puts 'caching result at ' + cache_key + ': ' + results.inspect
|
990
|
+
cache_store.write(cache_key, results, :expires_in =>30)
|
959
991
|
end
|
992
|
+
end
|
993
|
+
end
|
960
994
|
|
961
|
-
|
962
|
-
|
963
|
-
|
995
|
+
def self.cache_key(class_name, id)
|
996
|
+
return class_name + "/" + id.to_s
|
997
|
+
end
|
964
998
|
|
965
|
-
|
999
|
+
@@debug=""
|
966
1000
|
|
967
|
-
|
968
|
-
|
969
|
-
|
1001
|
+
def self.debug
|
1002
|
+
@@debug
|
1003
|
+
end
|
970
1004
|
|
971
|
-
|
972
|
-
|
973
|
-
|
1005
|
+
def self.sanitize_sql(*params)
|
1006
|
+
return ActiveRecord::Base.sanitize_sql(*params)
|
1007
|
+
end
|
974
1008
|
|
975
|
-
|
976
|
-
|
977
|
-
|
1009
|
+
def self.table_name
|
1010
|
+
return domain
|
1011
|
+
end
|
978
1012
|
|
979
|
-
|
980
|
-
|
981
|
-
|
1013
|
+
def changed
|
1014
|
+
return @dirty.keys
|
1015
|
+
end
|
982
1016
|
|
983
|
-
|
984
|
-
|
985
|
-
|
1017
|
+
def changed?
|
1018
|
+
return @dirty.size > 0
|
1019
|
+
end
|
986
1020
|
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
|
1021
|
+
def changes
|
1022
|
+
ret = {}
|
1023
|
+
#puts 'in CHANGES=' + @dirty.inspect
|
1024
|
+
@dirty.each_pair { |key, value| ret[key] = [value, get_attribute(key)] }
|
1025
|
+
return ret
|
1026
|
+
end
|
993
1027
|
|
994
|
-
|
995
|
-
|
996
|
-
|
1028
|
+
def after_save_cleanup
|
1029
|
+
@dirty = {}
|
1030
|
+
end
|
997
1031
|
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1032
|
+
def hash
|
1033
|
+
# same as ActiveRecord
|
1034
|
+
id.hash
|
1035
|
+
end
|
1002
1036
|
|
1003
1037
|
|
1004
|
-
|
1038
|
+
end
|
1005
1039
|
|
1006
1040
|
|
1007
|
-
|
1008
|
-
|
1009
|
-
|
1010
|
-
|
1011
|
-
|
1012
|
-
|
1041
|
+
class Activerecordtosdb_subrecord_array
|
1042
|
+
def initialize(subname, referencename, referencevalue)
|
1043
|
+
@subname =subname.classify
|
1044
|
+
@referencename =referencename.tableize.singularize + "_id"
|
1045
|
+
@referencevalue=referencevalue
|
1046
|
+
end
|
1013
1047
|
|
1014
|
-
|
1048
|
+
# Performance optimization if you know the array should be empty
|
1015
1049
|
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1050
|
+
def init_empty
|
1051
|
+
@records = []
|
1052
|
+
end
|
1019
1053
|
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
1023
|
-
|
1024
|
-
|
1025
|
-
|
1054
|
+
def load
|
1055
|
+
if @records.nil?
|
1056
|
+
@records = find_all
|
1057
|
+
end
|
1058
|
+
return @records
|
1059
|
+
end
|
1026
1060
|
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1061
|
+
def [](key)
|
1062
|
+
return load[key]
|
1063
|
+
end
|
1030
1064
|
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1065
|
+
def first
|
1066
|
+
load[0]
|
1067
|
+
end
|
1034
1068
|
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1069
|
+
def <<(ob)
|
1070
|
+
return load << ob
|
1071
|
+
end
|
1038
1072
|
|
1039
|
-
|
1040
|
-
|
1041
|
-
|
1073
|
+
def count
|
1074
|
+
return load.count
|
1075
|
+
end
|
1042
1076
|
|
1043
|
-
|
1044
|
-
|
1045
|
-
|
1077
|
+
def size
|
1078
|
+
return count
|
1079
|
+
end
|
1046
1080
|
|
1047
|
-
|
1048
|
-
|
1049
|
-
|
1081
|
+
def each(*params, &block)
|
1082
|
+
return load.each(*params) { |record| block.call(record) }
|
1083
|
+
end
|
1050
1084
|
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1085
|
+
def find_all(*params)
|
1086
|
+
find(:all, *params)
|
1087
|
+
end
|
1054
1088
|
|
1055
|
-
|
1056
|
-
|
1057
|
-
|
1058
|
-
end
|
1089
|
+
def empty?
|
1090
|
+
return load.empty?
|
1091
|
+
end
|
1059
1092
|
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
end
|
1093
|
+
def build(*params)
|
1094
|
+
params[0][@referencename]=@referencevalue
|
1095
|
+
eval(@subname).new(*params)
|
1096
|
+
end
|
1065
1097
|
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
query[0]=:all
|
1072
|
-
end
|
1073
|
-
end
|
1098
|
+
def create(*params)
|
1099
|
+
params[0][@referencename]=@referencevalue
|
1100
|
+
record = eval(@subname).new(*params)
|
1101
|
+
record.save
|
1102
|
+
end
|
1074
1103
|
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
|
1082
|
-
|
1083
|
-
end
|
1084
|
-
else
|
1085
|
-
query[1][:conditions]=["#{@referencename} = ?", @referencevalue]
|
1086
|
-
#query[1][:conditions]="id='#{@id}'"
|
1087
|
-
end
|
1104
|
+
def find(*params)
|
1105
|
+
query=[:first, {}]
|
1106
|
+
#{:conditions=>"id=>1"}
|
1107
|
+
if params[0]
|
1108
|
+
if params[0]==:all
|
1109
|
+
query[0]=:all
|
1110
|
+
end
|
1111
|
+
end
|
1088
1112
|
|
1089
|
-
|
1113
|
+
if params[1]
|
1114
|
+
query[1]=params[1]
|
1115
|
+
if query[1][:conditions]
|
1116
|
+
query[1][:conditions]=SimpleRecord::Base.sanitize_sql(query[1][:conditions])+" AND "+ SimpleRecord::Base.sanitize_sql(["#{@referencename} = ?", @referencevalue])
|
1117
|
+
#query[1][:conditions]=Activerecordtosdb.sanitize_sql(query[1][:conditions])+" AND id='#{@id}'"
|
1118
|
+
else
|
1119
|
+
query[1][:conditions]=["#{@referencename} = ?", @referencevalue]
|
1120
|
+
#query[1][:conditions]="id='#{@id}'"
|
1090
1121
|
end
|
1122
|
+
else
|
1123
|
+
query[1][:conditions]=["#{@referencename} = ?", @referencevalue]
|
1124
|
+
#query[1][:conditions]="id='#{@id}'"
|
1125
|
+
end
|
1091
1126
|
|
1127
|
+
return eval(@subname).find(*query)
|
1092
1128
|
end
|
1093
1129
|
|
1094
|
-
|
1095
|
-
class RemoteNil
|
1130
|
+
end
|
1096
1131
|
|
1097
|
-
|
1132
|
+
# This is simply a place holder so we don't keep doing gets to s3 or simpledb if already checked.
|
1133
|
+
class RemoteNil
|
1134
|
+
|
1135
|
+
end
|
1098
1136
|
|
1099
1137
|
|
1100
1138
|
end
|