kerryb-right_aws 1.7.6 → 1.10.1

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/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