simple_record 2.0.2 → 2.0.4
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 +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
|