kerryb-right_aws 1.7.6 → 1.10.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/right_aws.rb CHANGED
@@ -36,7 +36,6 @@ require 'right_http_connection'
36
36
 
37
37
  $:.unshift(File.dirname(__FILE__))
38
38
  require 'awsbase/benchmark_fix'
39
- require 'awsbase/file_fix'
40
39
  require 'awsbase/support'
41
40
  require 'awsbase/right_awsbase'
42
41
  require 'ec2/right_ec2'
@@ -47,13 +46,14 @@ require 'sqs/right_sqs'
47
46
  require 'sqs/right_sqs_gen2_interface'
48
47
  require 'sqs/right_sqs_gen2'
49
48
  require 'sdb/right_sdb_interface'
49
+ require 'acf/right_acf_interface'
50
50
 
51
51
 
52
52
  module RightAws #:nodoc:
53
53
  module VERSION #:nodoc:
54
54
  MAJOR = 1
55
- MINOR = 7
56
- TINY = 3
55
+ MINOR = 10
56
+ TINY = 1
57
57
 
58
58
  STRING = [MAJOR, MINOR, TINY].join('.')
59
59
  end
@@ -120,7 +120,8 @@ module RightAws
120
120
  headers[:url].to_s[%r{^([a-z0-9._-]*)(/[^?]*)?(\?.+)?}i]
121
121
  bucket_name, key_path, params_list = $1, $2, $3
122
122
  # select request model
123
- if is_dns_bucket?(bucket_name)
123
+ # No DNS
124
+ if is_dns_bucket?(bucket_name) and false
124
125
  # fix a path
125
126
  server = "#{bucket_name}.#{server}"
126
127
  key_path ||= '/'
@@ -155,12 +156,11 @@ module RightAws
155
156
  # set other headers
156
157
  request['Authorization'] = "AWS #{@aws_access_key_id}:#{signature}"
157
158
  # prepare output hash
158
-
159
159
  { :request => request,
160
160
  :server => server,
161
161
  :port => @params[:port],
162
162
  :protocol => @params[:protocol],
163
- :proxy => @params[:proxy] }
163
+ :proxy => @params[:proxy] }
164
164
  end
165
165
 
166
166
  # Sends request to Amazon and parses the response.
@@ -24,7 +24,7 @@
24
24
  begin
25
25
  require 'uuidtools'
26
26
  rescue LoadError => e
27
- STDERR.puts("RightSDB Alpha requires the uuidtools gem. Run \'gem install uuidtools\' and try again.")
27
+ STDERR.puts("RightSDB requires the uuidtools gem. Run \'gem install uuidtools\' and try again.")
28
28
  exit
29
29
  end
30
30
 
@@ -101,6 +101,15 @@ module RightAws
101
101
  # Create a new handle to an Sdb account. All handles share the same per process or per thread
102
102
  # HTTP connection to Amazon Sdb. Each handle is for a specific account.
103
103
  # The +params+ are passed through as-is to RightAws::SdbInterface.new
104
+ # Params:
105
+ # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
106
+ # :port => 443 # Amazon service port: 80 or 443(default)
107
+ # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
108
+ # :signature_version => '0' # The signature version : '0' or '1'(default)
109
+ # :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
110
+ # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
111
+ # :nil_representation => 'mynil'} # interpret Ruby nil as this string value; i.e. use this string in SDB to represent Ruby nils (default is the string 'nil')
112
+
104
113
  def establish_connection(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
105
114
  @connection = RightAws::SdbInterface.new(aws_access_key_id, aws_secret_access_key, params)
106
115
  end
@@ -259,9 +268,10 @@ module RightAws
259
268
  # Client.find_by_name('Matias Rust')
260
269
  # Client.find_by_name_and_city('Putin','Moscow')
261
270
  # Client.find_by_name_and_city_and_post('Medvedev','Moscow','president')
262
- #
271
+ #
263
272
  # Client.find_all_by_author('G.Bush jr')
264
273
  # Client.find_all_by_age_and_gender_and_ethnicity('34','male','russian')
274
+ # Client.find_all_by_gender_and_country('male', 'Russia', :auto_load => true, :order => 'name desc')
265
275
  #
266
276
  # Returned records have to be +reloaded+ to access their attributes.
267
277
  #
@@ -276,37 +286,231 @@ module RightAws
276
286
  # Client.find(:all, :limit => 10, :next_token => Client.next_token)
277
287
  # end while Client.next_token
278
288
  #
289
+ # Sort oder:
290
+ # Client.find(:all, :order => 'gender')
291
+ # Client.find(:all, :order => 'name desc')
292
+ #
293
+ # Attributes auto load (be carefull - this may take lot of time for a huge bunch of records):
294
+ # Client.find(:first) #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false>
295
+ # Client.find(:first, :auto_load => true) #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}, @new_record=false>
296
+ #
297
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingQuery.html
298
+ #
279
299
  def find(*args)
280
300
  options = args.last.is_a?(Hash) ? args.pop : {}
281
301
  case args.first
282
- when :all : find_every options
283
- when :first : find_initial options
284
- else find_from_ids args, options
302
+ when :all then find_every options
303
+ when :first then find_initial options
304
+ else find_from_ids args, options
285
305
  end
286
306
  end
287
307
 
288
- protected
289
-
290
- def query(query_expression=nil, max_number_of_items = nil, next_token = nil) # :nodoc:
291
- @next_token = next_token
308
+ # Perform a SQL-like select request.
309
+ #
310
+ # Single record:
311
+ #
312
+ # Client.select(:first)
313
+ # Client.select(:first, :conditions=> [ "name=? AND wife=?", "Jon", "Sandy"])
314
+ # Client.select(:first, :conditions=> { :name=>"Jon", :wife=>"Sandy" }, :select => :girfriends)
315
+ #
316
+ # Bunch of records:
317
+ #
318
+ # Client.select(:all)
319
+ # Client.select(:all, :limit => 10)
320
+ # Client.select(:all, :conditions=> [ "name=? AND 'girlfriend'=?", "Jon", "Judy"])
321
+ # Client.select(:all, :conditions=> { :name=>"Sandy" }, :limit => 3)
322
+ #
323
+ # Records by ids:
324
+ #
325
+ # Client.select('1')
326
+ # Client.select('1234987b4583475347523948')
327
+ # Client.select('1','2','3','4', :conditions=> ["toys=?", "beer"])
328
+ #
329
+ # Find helpers: RightAws::ActiveSdb::Base.select_by_... and RightAws::ActiveSdb::Base.select_all_by_...
330
+ #
331
+ # Client.select_by_name('Matias Rust')
332
+ # Client.select_by_name_and_city('Putin','Moscow')
333
+ # Client.select_by_name_and_city_and_post('Medvedev','Moscow','president')
334
+ #
335
+ # Client.select_all_by_author('G.Bush jr')
336
+ # Client.select_all_by_age_and_gender_and_ethnicity('34','male','russian')
337
+ # Client.select_all_by_gender_and_country('male', 'Russia', :order => 'name')
338
+ #
339
+ # Continue listing:
340
+ #
341
+ # # initial listing
342
+ # Client.select(:all, :limit => 10)
343
+ # # continue listing
344
+ # begin
345
+ # Client.select(:all, :limit => 10, :next_token => Client.next_token)
346
+ # end while Client.next_token
347
+ #
348
+ # Sort oder:
349
+ # If :order=>'attribute' option is specified then result response (ordered by 'attribute') will contain only items where attribute is defined (is not null).
350
+ #
351
+ # Client.select(:all) # returns all records
352
+ # Client.select(:all, :order => 'gender') # returns all records ordered by gender where gender attribute exists
353
+ # Client.select(:all, :order => 'name desc') # returns all records ordered by name in desc order where name attribute exists
354
+ #
355
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingSelect.html
356
+ #
357
+ def select(*args)
358
+ options = args.last.is_a?(Hash) ? args.pop : {}
359
+ case args.first
360
+ when :all then sql_select(options)
361
+ when :first then sql_select(options.merge(:limit => 1)).first
362
+ else select_from_ids args, options
363
+ end
364
+ end
365
+
366
+ def generate_id # :nodoc:
367
+ UUID.timestamp_create().to_s
368
+ end
369
+
370
+ protected
371
+
372
+ # Select
373
+
374
+ def select_from_ids(args, options) # :nodoc:
375
+ cond = []
376
+ # detect amount of records requested
377
+ bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
378
+ # flatten ids
379
+ args = args.to_a.flatten
380
+ args.each { |id| cond << "id=#{self.connection.escape(id)}" }
381
+ ids_cond = "(#{cond.join(' OR ')})"
382
+ # user defined :conditions to string (if it was defined)
383
+ options[:conditions] = build_conditions(options[:conditions])
384
+ # join ids condition and user defined conditions
385
+ options[:conditions] = options[:conditions].blank? ? ids_cond : "(#{options[:conditions]}) AND #{ids_cond}"
386
+ result = sql_select(options)
387
+ # if one record was requested then return it
388
+ unless bunch_of_records_requested
389
+ record = result.first
390
+ # railse if nothing was found
391
+ raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record
392
+ record
393
+ else
394
+ # if a bunch of records was requested then return check that we found all of them
395
+ # and return as an array
396
+ unless args.size == result.size
397
+ id_list = args.map{|i| "'#{i}'"}.join(',')
398
+ raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
399
+ else
400
+ result
401
+ end
402
+ end
403
+ end
404
+
405
+ def sql_select(options) # :nodoc:
406
+ @next_token = options[:next_token]
407
+ select_expression = build_select(options)
292
408
  # request items
293
- query_result = self.connection.query(domain, query_expression, max_number_of_items, @next_token)
409
+ query_result = self.connection.select(select_expression, @next_token)
410
+ @next_token = query_result[:next_token]
411
+ items = query_result[:items].map do |hash|
412
+ id, attributes = hash.shift
413
+ new_item = self.new( attributes.merge({ 'id' => id }))
414
+ new_item.mark_as_old
415
+ new_item
416
+ end
417
+ items
418
+ end
419
+
420
+ # select_by helpers
421
+ def select_all_by_(format_str, args, options) # :nodoc:
422
+ fields = format_str.to_s.sub(/^select_(all_)?by_/,'').split('_and_')
423
+ conditions = fields.map { |field| "#{field}=?" }.join(' AND ')
424
+ options[:conditions] = [conditions, *args]
425
+ select(:all, options)
426
+ end
427
+
428
+ def select_by_(format_str, args, options) # :nodoc:
429
+ options[:limit] = 1
430
+ select_all_by_(format_str, args, options).first
431
+ end
432
+
433
+ # Query
434
+
435
+ # Returns an array of query attributes.
436
+ # Query_expression must be a well formated SDB query string:
437
+ # query_attributes("['title' starts-with 'O\\'Reily'] intersection ['year' = '2007']") #=> ["title", "year"]
438
+ def query_attributes(query_expression) # :nodoc:
439
+ attrs = []
440
+ array = query_expression.scan(/['"](.*?[^\\])['"]/).flatten
441
+ until array.empty? do
442
+ attrs << array.shift # skip it's value
443
+ array.shift #
444
+ end
445
+ attrs
446
+ end
447
+
448
+ # Returns an array of [attribute_name, 'asc'|'desc']
449
+ def sort_options(sort_string)
450
+ sort_string[/['"]?(\w+)['"]? *(asc|desc)?/i]
451
+ [$1, ($2 || 'asc')]
452
+ end
453
+
454
+ # Perform a query request.
455
+ #
456
+ # Options
457
+ # :query_expression nil | string | array
458
+ # :max_number_of_items nil | integer
459
+ # :next_token nil | string
460
+ # :sort_option nil | string "name desc|asc"
461
+ #
462
+ def query(options) # :nodoc:
463
+ @next_token = options[:next_token]
464
+ query_expression = build_conditions(options[:query_expression])
465
+ # add sort_options to the query_expression
466
+ if options[:sort_option]
467
+ sort_by, sort_order = sort_options(options[:sort_option])
468
+ sort_query_expression = "['#{sort_by}' starts-with '']"
469
+ sort_by_expression = " sort '#{sort_by}' #{sort_order}"
470
+ # make query_expression to be a string (it may be null)
471
+ query_expression = query_expression.to_s
472
+ # quote from Amazon:
473
+ # The sort attribute must be present in at least one of the predicates of the query expression.
474
+ if query_expression.blank?
475
+ query_expression = sort_query_expression
476
+ elsif !query_attributes(query_expression).include?(sort_by)
477
+ query_expression += " intersection #{sort_query_expression}"
478
+ end
479
+ query_expression += sort_by_expression
480
+ end
481
+ # request items
482
+ query_result = self.connection.query(domain, query_expression, options[:max_number_of_items], @next_token)
294
483
  @next_token = query_result[:next_token]
295
484
  items = query_result[:items].map do |name|
296
485
  new_item = self.new('id' => name)
297
486
  new_item.mark_as_old
487
+ reload_if_exists(record) if options[:auto_load]
298
488
  new_item
299
489
  end
300
490
  items
301
491
  end
302
492
 
493
+ # reload a record unless it is nil
494
+ def reload_if_exists(record) # :nodoc:
495
+ record && record.reload
496
+ end
497
+
498
+ def reload_all_records(*list) # :nodoc:
499
+ list.flatten.each { |record| reload_if_exists(record) }
500
+ end
501
+
303
502
  def find_every(options) # :nodoc:
304
- query(options[:conditions], options[:limit], options[:next_token])
503
+ records = query( :query_expression => options[:conditions],
504
+ :max_number_of_items => options[:limit],
505
+ :next_token => options[:next_token],
506
+ :sort_option => options[:sort] || options[:order] )
507
+ options[:auto_load] ? reload_all_records(records) : records
305
508
  end
306
509
 
307
510
  def find_initial(options) # :nodoc:
308
511
  options[:limit] = 1
309
- find_every(options)[0]
512
+ record = find_every(options).first
513
+ options[:auto_load] ? reload_all_records(record).first : record
310
514
  end
311
515
 
312
516
  def find_from_ids(args, options) # :nodoc:
@@ -318,15 +522,16 @@ module RightAws
318
522
  args.each { |id| cond << "'id'=#{self.connection.escape(id)}" }
319
523
  ids_cond = "[#{cond.join(' OR ')}]"
320
524
  # user defined :conditions to string (if it was defined)
321
- if options[:conditions].is_a?(Array)
322
- options[:conditions] = connection.query_expression_from_array(options[:conditions])
323
- end
525
+ options[:conditions] = build_conditions(options[:conditions])
324
526
  # join ids condition and user defined conditions
325
527
  options[:conditions] = options[:conditions].blank? ? ids_cond : "#{options[:conditions]} intersection #{ids_cond}"
326
528
  result = find_every(options)
327
529
  # if one record was requested then return it
328
530
  unless bunch_of_records_requested
329
- result.first
531
+ record = result.first
532
+ # railse if nothing was found
533
+ raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record
534
+ options[:auto_load] ? reload_all_records(record).first : record
330
535
  else
331
536
  # if a bunch of records was requested then return check that we found all of them
332
537
  # and return as an array
@@ -334,35 +539,57 @@ module RightAws
334
539
  id_list = args.map{|i| "'#{i}'"}.join(',')
335
540
  raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
336
541
  else
337
- result
542
+ options[:auto_load] ? reload_all_records(result) : result
338
543
  end
339
544
  end
340
545
  end
341
546
 
342
547
  # find_by helpers
343
- def find_all_by_(format_str, args, limit=nil) # :nodoc:
548
+ def find_all_by_(format_str, args, options) # :nodoc:
344
549
  fields = format_str.to_s.sub(/^find_(all_)?by_/,'').split('_and_')
345
550
  conditions = fields.map { |field| "['#{field}'=?]" }.join(' intersection ')
346
- find(:all, :conditions => [conditions, *args], :limit => limit)
551
+ options[:conditions] = [conditions, *args]
552
+ find(:all, options)
347
553
  end
348
554
 
349
- def find_by_(format_str, args) # :nodoc:
350
- find_all_by_(format_str, args, 1)[0]
555
+ def find_by_(format_str, args, options) # :nodoc:
556
+ options[:limit] = 1
557
+ find_all_by_(format_str, args, options).first
351
558
  end
352
559
 
560
+ # Misc
561
+
353
562
  def method_missing(method, *args) # :nodoc:
354
- if method.to_s[/^find_all_by_/] then return find_all_by_(method, args)
355
- elsif method.to_s[/^find_by_/] then return find_by_(method, args)
356
- else super(method, *args)
563
+ if method.to_s[/^(find_all_by_|find_by_|select_all_by_|select_by_)/]
564
+ options = args.last.is_a?(Hash) ? args.pop : {}
565
+ __send__($1, method, args, options)
566
+ else
567
+ super(method, *args)
357
568
  end
358
569
  end
359
-
360
- end
361
-
362
- def self.generate_id # :nodoc:
363
- result = ''
364
- result = UUID.timestamp_create().to_s
365
- result
570
+
571
+ def build_select(options) # :nodoc:
572
+ select = options[:select] || '*'
573
+ from = options[:from] || domain
574
+ conditions = options[:conditions] ? " WHERE #{build_conditions(options[:conditions])}" : ''
575
+ order = options[:order] ? " ORDER BY #{options[:order]}" : ''
576
+ limit = options[:limit] ? " LIMIT #{options[:limit]}" : ''
577
+ # mix sort by argument (it must present in response)
578
+ unless order.blank?
579
+ sort_by, sort_order = sort_options(options[:order])
580
+ conditions << (conditions.blank? ? " WHERE " : " AND ") << "(#{sort_by} IS NOT NULL)"
581
+ end
582
+ "SELECT #{select} FROM #{from}#{conditions}#{order}#{limit}"
583
+ end
584
+
585
+ def build_conditions(conditions) # :nodoc:
586
+ case
587
+ when conditions.is_a?(Array) then connection.query_expression_from_array(conditions)
588
+ when conditions.is_a?(Hash) then connection.query_expression_from_hash(conditions)
589
+ else conditions
590
+ end
591
+ end
592
+
366
593
  end
367
594
 
368
595
  public
@@ -617,7 +844,15 @@ module RightAws
617
844
  attrs.delete('id')
618
845
  unless attrs.blank?
619
846
  connection.delete_attributes(domain, id, attrs)
620
- attrs.each { |attribute, values| @attributes[attribute] -= values }
847
+ attrs.each do |attribute, values|
848
+ # remove the values from the attribute
849
+ if @attributes[attribute]
850
+ @attributes[attribute] -= values
851
+ else
852
+ # if the attribute is unknown remove it from a resulting list of fixed attributes
853
+ attrs.delete(attribute)
854
+ end
855
+ end
621
856
  end
622
857
  attrs
623
858
  end