aws 1.11.9 → 1.11.36

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.
@@ -22,894 +22,920 @@
22
22
  #
23
23
 
24
24
  begin
25
- require 'uuidtools'
25
+ require 'uuidtools'
26
26
  rescue LoadError => e
27
- STDERR.puts("RightSDB requires the uuidtools gem. Run \'gem install uuidtools\' and try again.")
28
- exit
27
+ STDERR.puts("RightSDB requires the uuidtools gem. Run \'gem install uuidtools\' and try again.")
28
+ exit
29
29
  end
30
30
 
31
31
  module RightAws
32
32
 
33
- # = RightAws::ActiveSdb -- RightScale SDB interface (alpha release)
34
- # The RightAws::ActiveSdb class provides a complete interface to Amazon's Simple
35
- # Database Service.
36
- #
37
- # ActiveSdb is in alpha and does not load by default with the rest of RightAws. You must use an additional require statement to load the ActiveSdb class. For example:
38
- #
39
- # require 'right_aws'
40
- # require 'sdb/active_sdb'
41
- #
42
- # Additionally, the ActiveSdb class requires the 'uuidtools' gem; this gem is not normally required by RightAws and is not installed as a
43
- # dependency of RightAws.
44
- #
45
- # Simple ActiveSdb usage example:
46
- #
47
- # class Client < RightAws::ActiveSdb::Base
48
- # end
49
- #
50
- # # connect to SDB
51
- # RightAws::ActiveSdb.establish_connection
52
- #
53
- # # create domain
54
- # Client.create_domain
55
- #
56
- # # create initial DB
57
- # Client.create 'name' => 'Bush', 'country' => 'USA', 'gender' => 'male', 'expiration' => '2009', 'post' => 'president'
58
- # Client.create 'name' => 'Putin', 'country' => 'Russia', 'gender' => 'male', 'expiration' => '2008', 'post' => 'president'
59
- # Client.create 'name' => 'Medvedev', 'country' => 'Russia', 'gender' => 'male', 'expiration' => '2012', 'post' => 'president'
60
- # Client.create 'name' => 'Mary', 'country' => 'USA', 'gender' => 'female', 'hobby' => ['patchwork', 'bundle jumping']
61
- # Client.create 'name' => 'Mary', 'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking']
62
- # sandy_id = Client.create('name' => 'Sandy', 'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking']).id
63
- #
64
- # # find all Bushes in USA
65
- # Client.find(:all, :conditions => ["['name'=?] intersection ['country'=?]",'Bush','USA']).each do |client|
66
- # client.reload
67
- # puts client.attributes.inspect
68
- # end
69
- #
70
- # # find all Maries through the world
71
- # Client.find_all_by_name_and_gender('Mary','female').each do |mary|
72
- # mary.reload
73
- # puts "#{mary[:name]}, gender: #{mary[:gender]}, hobbies: #{mary[:hobby].join(',')}"
74
- # end
75
- #
76
- # # find new russian president
77
- # medvedev = Client.find_by_post_and_country_and_expiration('president','Russia','2012')
78
- # if medvedev
79
- # medvedev.reload
80
- # medvedev.save_attributes('age' => '42', 'hobby' => 'Gazprom')
81
- # end
82
- #
83
- # # retire old president
84
- # Client.find_by_name('Putin').delete
85
- #
86
- # # Sandy disappointed in 'cooking' and decided to hide her 'gender' and 'country' ()
87
- # sandy = Client.find(sandy_id)
88
- # sandy.reload
89
- # sandy.delete_values('hobby' => ['cooking'] )
90
- # sandy.delete_attributes('country', 'gender')
91
- #
92
- # # remove domain
93
- # Client.delete_domain
94
- #
95
- class ActiveSdb
96
-
97
- module ActiveSdbConnect
98
- def connection
99
- @connection || raise(ActiveSdbError.new('Connection to SDB is not established'))
100
- end
101
-
102
- # Create a new handle to an Sdb account. All handles share the same per process or per thread
103
- # HTTP connection to Amazon Sdb. Each handle is for a specific account.
104
- # The +params+ are passed through as-is to RightAws::SdbInterface.new
105
- # Params:
106
- # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
107
- # :port => 443 # Amazon service port: 80 or 443(default)
108
- # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
109
- # :signature_version => '0' # The signature version : '0' or '1'(default)
110
- # DEPRECATED :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
111
- # :connection_mode => :default # options are :default (will use best known option, may change in the future)
112
- # :per_request (opens and closes a connection on every request to SDB)
113
- # :single (same as old multi_thread=>false)
114
- # :per_thread (same as old multi_thread=>true)
115
- # :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET)
116
- # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
117
- # :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')
118
-
119
- def establish_connection(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
120
- @connection = RightAws::SdbInterface.new(aws_access_key_id, aws_secret_access_key, params)
121
- end
122
-
123
- def close_connection
124
- @connection.close_connection unless @connection.nil?
125
- end
126
- end
33
+ # = RightAws::ActiveSdb -- RightScale SDB interface (alpha release)
34
+ # The RightAws::ActiveSdb class provides a complete interface to Amazon's Simple
35
+ # Database Service.
36
+ #
37
+ # ActiveSdb is in alpha and does not load by default with the rest of RightAws. You must use an additional require statement to load the ActiveSdb class. For example:
38
+ #
39
+ # require 'right_aws'
40
+ # require 'sdb/active_sdb'
41
+ #
42
+ # Additionally, the ActiveSdb class requires the 'uuidtools' gem; this gem is not normally required by RightAws and is not installed as a
43
+ # dependency of RightAws.
44
+ #
45
+ # Simple ActiveSdb usage example:
46
+ #
47
+ # class Client < RightAws::ActiveSdb::Base
48
+ # end
49
+ #
50
+ # # connect to SDB
51
+ # RightAws::ActiveSdb.establish_connection
52
+ #
53
+ # # create domain
54
+ # Client.create_domain
55
+ #
56
+ # # create initial DB
57
+ # Client.create 'name' => 'Bush', 'country' => 'USA', 'gender' => 'male', 'expiration' => '2009', 'post' => 'president'
58
+ # Client.create 'name' => 'Putin', 'country' => 'Russia', 'gender' => 'male', 'expiration' => '2008', 'post' => 'president'
59
+ # Client.create 'name' => 'Medvedev', 'country' => 'Russia', 'gender' => 'male', 'expiration' => '2012', 'post' => 'president'
60
+ # Client.create 'name' => 'Mary', 'country' => 'USA', 'gender' => 'female', 'hobby' => ['patchwork', 'bundle jumping']
61
+ # Client.create 'name' => 'Mary', 'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking']
62
+ # sandy_id = Client.create('name' => 'Sandy', 'country' => 'Russia', 'gender' => 'female', 'hobby' => ['flowers', 'cats', 'cooking']).id
63
+ #
64
+ # # find all Bushes in USA
65
+ # Client.find(:all, :conditions => ["['name'=?] intersection ['country'=?]",'Bush','USA']).each do |client|
66
+ # client.reload
67
+ # puts client.attributes.inspect
68
+ # end
69
+ #
70
+ # # find all Maries through the world
71
+ # Client.find_all_by_name_and_gender('Mary','female').each do |mary|
72
+ # mary.reload
73
+ # puts "#{mary[:name]}, gender: #{mary[:gender]}, hobbies: #{mary[:hobby].join(',')}"
74
+ # end
75
+ #
76
+ # # find new russian president
77
+ # medvedev = Client.find_by_post_and_country_and_expiration('president','Russia','2012')
78
+ # if medvedev
79
+ # medvedev.reload
80
+ # medvedev.save_attributes('age' => '42', 'hobby' => 'Gazprom')
81
+ # end
82
+ #
83
+ # # retire old president
84
+ # Client.find_by_name('Putin').delete
85
+ #
86
+ # # Sandy disappointed in 'cooking' and decided to hide her 'gender' and 'country' ()
87
+ # sandy = Client.find(sandy_id)
88
+ # sandy.reload
89
+ # sandy.delete_values('hobby' => ['cooking'] )
90
+ # sandy.delete_attributes('country', 'gender')
91
+ #
92
+ # # remove domain
93
+ # Client.delete_domain
94
+ #
95
+ class ActiveSdb
96
+
97
+ module ActiveSdbConnect
98
+ def connection
99
+ @connection || raise(ActiveSdbError.new('Connection to SDB is not established'))
100
+ end
127
101
 
128
- class ActiveSdbError < RuntimeError ; end
129
-
130
- class << self
131
- include ActiveSdbConnect
132
-
133
- # Retreive a list of domains.
134
- #
135
- # put RightAws::ActiveSdb.domains #=> ['co-workers','family','friends','clients']
136
- #
137
- def domains
138
- connection.list_domains[:domains]
139
- end
140
-
141
- # Create new domain.
142
- # Raises no errors if the domain already exists.
143
- #
144
- # RightAws::ActiveSdb.create_domain('alpha') #=> {:request_id=>"6fc652a0-0000-41d5-91f4-3ed390a3d3b2", :box_usage=>"0.0055590278"}
145
- #
146
- def create_domain(domain_name)
147
- connection.create_domain(domain_name)
148
- end
149
-
150
- # Remove domain from SDB.
151
- # Raises no errors if the domain does not exist.
152
- #
153
- # RightAws::ActiveSdb.create_domain('alpha') #=> {:request_id=>"6fc652a0-0000-41c6-91f4-3ed390a3d3b2", :box_usage=>"0.0055590001"}
154
- #
155
- def delete_domain(domain_name)
156
- connection.delete_domain(domain_name)
157
- end
158
- end
102
+ # Create a new handle to an Sdb account. All handles share the same per process or per thread
103
+ # HTTP connection to Amazon Sdb. Each handle is for a specific account.
104
+ # The +params+ are passed through as-is to RightAws::SdbInterface.new
105
+ # Params:
106
+ # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
107
+ # :port => 443 # Amazon service port: 80 or 443(default)
108
+ # :protocol => 'https' # Amazon service protocol: 'http' or 'https'(default)
109
+ # :signature_version => '2' # The signature version : '0', '1' or '2' (default)
110
+ # DEPRECATED :multi_thread => true|false # Multi-threaded (connection per each thread): true or false(default)
111
+ # :connection_mode => :default # options are :default (will use best known option, may change in the future)
112
+ # :per_request (opens and closes a connection on every request to SDB)
113
+ # :single (same as old multi_thread=>false)
114
+ # :per_thread (same as old multi_thread=>true)
115
+ # :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET)
116
+ # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
117
+ # :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')
118
+ # :service_endpoint => '/' # Set this to /mdb/request.mgwsi for usage with M/DB
119
+
120
+ def establish_connection(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
121
+ @connection = RightAws::SdbInterface.new(aws_access_key_id, aws_secret_access_key, params)
122
+ end
159
123
 
160
- class Base
161
-
162
- class << self
163
- include ActiveSdbConnect
164
-
165
- # next_token value returned by last find: is useful to continue finding
166
- attr_accessor :next_token
167
-
168
- # Returns a RightAws::SdbInterface object
169
- #
170
- # class A < RightAws::ActiveSdb::Base
171
- # end
172
- #
173
- # class B < RightAws::ActiveSdb::Base
174
- # end
175
- #
176
- # class C < RightAws::ActiveSdb::Base
177
- # end
178
- #
179
- # RightAws::ActiveSdb.establish_connection 'key_id_1', 'secret_key_1'
180
- #
181
- # C.establish_connection 'key_id_2', 'secret_key_2'
182
- #
183
- # # A and B uses the default connection, C - uses its own
184
- # puts A.connection #=> #<RightAws::SdbInterface:0xb76d6d7c>
185
- # puts B.connection #=> #<RightAws::SdbInterface:0xb76d6d7c>
186
- # puts C.connection #=> #<RightAws::SdbInterface:0xb76d6ca0>
187
- #
188
- def connection
189
- @connection || ActiveSdb::connection
124
+ def close_connection
125
+ @connection.close_connection unless @connection.nil?
126
+ end
190
127
  end
191
128
 
192
- @domain = nil
193
-
194
- # Current domain name.
195
- #
196
- # # if 'ActiveSupport' is not loaded then class name converted to downcase
197
- # class Client < RightAws::ActiveSdb::Base
198
- # end
199
- # puts Client.domain #=> 'client'
200
- #
201
- # # if 'ActiveSupport' is loaded then class name being tableized
202
- # require 'activesupport'
203
- # class Client < RightAws::ActiveSdb::Base
204
- # end
205
- # puts Client.domain #=> 'clients'
206
- #
207
- # # Explicit domain name definition
208
- # class Client < RightAws::ActiveSdb::Base
209
- # set_domain_name :foreign_clients
210
- # end
211
- # puts Client.domain #=> 'foreign_clients'
212
- #
213
- def domain
214
- unless @domain
215
- if defined? ActiveSupport::CoreExtensions::String::Inflections
216
- @domain = name.tableize
217
- else
218
- @domain = name.downcase
219
- end
220
- end
221
- @domain
129
+ class ActiveSdbError < RuntimeError
222
130
  end
223
131
 
224
- # Change the default domain name to user defined.
225
- #
226
- # class Client < RightAws::ActiveSdb::Base
227
- # set_domain_name :foreign_clients
228
- # end
229
- #
230
- def set_domain_name(domain)
231
- @domain = domain.to_s
232
- end
132
+ class << self
133
+ include ActiveSdbConnect
233
134
 
234
- # Create domain at SDB.
235
- # Raises no errors if the domain already exists.
236
- #
237
- # class Client < RightAws::ActiveSdb::Base
238
- # end
239
- # Client.create_domain #=> {:request_id=>"6fc652a0-0000-41d5-91f4-3ed390a3d3b2", :box_usage=>"0.0055590278"}
240
- #
241
- def create_domain
242
- connection.create_domain(domain)
243
- end
135
+ # Retreive a list of domains.
136
+ #
137
+ # put RightAws::ActiveSdb.domains #=> ['co-workers','family','friends','clients']
138
+ #
139
+ def domains
140
+ connection.list_domains[:domains]
141
+ end
142
+
143
+ # Create new domain.
144
+ # Raises no errors if the domain already exists.
145
+ #
146
+ # RightAws::ActiveSdb.create_domain('alpha') #=> {:request_id=>"6fc652a0-0000-41d5-91f4-3ed390a3d3b2", :box_usage=>"0.0055590278"}
147
+ #
148
+ def create_domain(domain_name)
149
+ connection.create_domain(domain_name)
150
+ end
244
151
 
245
- # Remove domain from SDB.
246
- # Raises no errors if the domain does not exist.
247
- #
248
- # class Client < RightAws::ActiveSdb::Base
249
- # end
250
- # Client.delete_domain #=> {:request_id=>"e14d90d3-0000-4898-9995-0de28cdda270", :box_usage=>"0.0055590278"}
251
- #
252
- def delete_domain
253
- connection.delete_domain(domain)
152
+ # Remove domain from SDB.
153
+ # Raises no errors if the domain does not exist.
154
+ #
155
+ # RightAws::ActiveSdb.create_domain('alpha') #=> {:request_id=>"6fc652a0-0000-41c6-91f4-3ed390a3d3b2", :box_usage=>"0.0055590001"}
156
+ #
157
+ def delete_domain(domain_name)
158
+ connection.delete_domain(domain_name)
159
+ end
254
160
  end
255
161
 
256
- #
257
- # See select(), original find with QUERY syntax is deprecated so now find and select are synonyms.
258
- #
259
- def find(*args)
260
- select(*args)
162
+ class Base
163
+
164
+ class << self
165
+ include ActiveSdbConnect
166
+
167
+ # next_token value returned by last find: is useful to continue finding
168
+ attr_accessor :next_token
169
+
170
+ # Returns a RightAws::SdbInterface object
171
+ #
172
+ # class A < RightAws::ActiveSdb::Base
173
+ # end
174
+ #
175
+ # class B < RightAws::ActiveSdb::Base
176
+ # end
177
+ #
178
+ # class C < RightAws::ActiveSdb::Base
179
+ # end
180
+ #
181
+ # RightAws::ActiveSdb.establish_connection 'key_id_1', 'secret_key_1'
182
+ #
183
+ # C.establish_connection 'key_id_2', 'secret_key_2'
184
+ #
185
+ # # A and B uses the default connection, C - uses its own
186
+ # puts A.connection #=> #<RightAws::SdbInterface:0xb76d6d7c>
187
+ # puts B.connection #=> #<RightAws::SdbInterface:0xb76d6d7c>
188
+ # puts C.connection #=> #<RightAws::SdbInterface:0xb76d6ca0>
189
+ #
190
+ def connection
191
+ @connection || ActiveSdb::connection
192
+ end
193
+
194
+ @domain = nil
195
+
196
+ # Current domain name.
197
+ #
198
+ # # if 'ActiveSupport' is not loaded then class name converted to downcase
199
+ # class Client < RightAws::ActiveSdb::Base
200
+ # end
201
+ # puts Client.domain #=> 'client'
202
+ #
203
+ # # if 'ActiveSupport' is loaded then class name being tableized
204
+ # require 'activesupport'
205
+ # class Client < RightAws::ActiveSdb::Base
206
+ # end
207
+ # puts Client.domain #=> 'clients'
208
+ #
209
+ # # Explicit domain name definition
210
+ # class Client < RightAws::ActiveSdb::Base
211
+ # set_domain_name :foreign_clients
212
+ # end
213
+ # puts Client.domain #=> 'foreign_clients'
214
+ #
215
+ def domain
216
+ unless @domain
217
+ if defined? ActiveSupport::CoreExtensions::String::Inflections
218
+ @domain = name.tableize
219
+ else
220
+ @domain = name.downcase
221
+ end
222
+ end
223
+ @domain
224
+ end
225
+
226
+ # Change the default domain name to user defined.
227
+ #
228
+ # class Client < RightAws::ActiveSdb::Base
229
+ # set_domain_name :foreign_clients
230
+ # end
231
+ #
232
+ def set_domain_name(domain)
233
+ @domain = domain.to_s
234
+ end
235
+
236
+ # Create domain at SDB.
237
+ # Raises no errors if the domain already exists.
238
+ #
239
+ # class Client < RightAws::ActiveSdb::Base
240
+ # end
241
+ # Client.create_domain #=> {:request_id=>"6fc652a0-0000-41d5-91f4-3ed390a3d3b2", :box_usage=>"0.0055590278"}
242
+ #
243
+ def create_domain
244
+ connection.create_domain(domain)
245
+ end
246
+
247
+ # Remove domain from SDB.
248
+ # Raises no errors if the domain does not exist.
249
+ #
250
+ # class Client < RightAws::ActiveSdb::Base
251
+ # end
252
+ # Client.delete_domain #=> {:request_id=>"e14d90d3-0000-4898-9995-0de28cdda270", :box_usage=>"0.0055590278"}
253
+ #
254
+ def delete_domain
255
+ connection.delete_domain(domain)
256
+ end
257
+
258
+ #
259
+ # See select(), original find with QUERY syntax is deprecated so now find and select are synonyms.
260
+ #
261
+ def find(*args)
262
+ options = args.last.is_a?(Hash) ? args.pop : {}
263
+ # puts 'first=' + args.first.to_s
264
+ case args.first
265
+ when nil then
266
+ raise "Invalid parameters passed to find: nil."
267
+ when :all then
268
+ sql_select(options)
269
+ when :first then
270
+ sql_select(options.merge(:limit => 1)).first
271
+ when :count then
272
+ res = sql_select(options.merge(:count => true))
273
+ res
274
+ else
275
+ select_from_ids args, options
276
+ end
277
+ end
278
+
279
+ # Perform a SQL-like select request.
280
+ #
281
+ # Single record:
282
+ #
283
+ # Client.select(:first)
284
+ # Client.select(:first, :conditions=> [ "name=? AND wife=?", "Jon", "Sandy"])
285
+ # Client.select(:first, :conditions=> { :name=>"Jon", :wife=>"Sandy" }, :select => :girfriends)
286
+ #
287
+ # Bunch of records:
288
+ #
289
+ # Client.select(:all)
290
+ # Client.select(:all, :limit => 10)
291
+ # Client.select(:all, :conditions=> [ "name=? AND 'girlfriend'=?", "Jon", "Judy"])
292
+ # Client.select(:all, :conditions=> { :name=>"Sandy" }, :limit => 3)
293
+ #
294
+ # Records by ids:
295
+ #
296
+ # Client.select('1')
297
+ # Client.select('1234987b4583475347523948')
298
+ # Client.select('1','2','3','4', :conditions=> ["toys=?", "beer"])
299
+ #
300
+ # Find helpers: RightAws::ActiveSdb::Base.select_by_... and RightAws::ActiveSdb::Base.select_all_by_...
301
+ #
302
+ # Client.select_by_name('Matias Rust')
303
+ # Client.select_by_name_and_city('Putin','Moscow')
304
+ # Client.select_by_name_and_city_and_post('Medvedev','Moscow','president')
305
+ #
306
+ # Client.select_all_by_author('G.Bush jr')
307
+ # Client.select_all_by_age_and_gender_and_ethnicity('34','male','russian')
308
+ # Client.select_all_by_gender_and_country('male', 'Russia', :order => 'name')
309
+ #
310
+ # Continue listing:
311
+ #
312
+ # # initial listing
313
+ # Client.select(:all, :limit => 10)
314
+ # # continue listing
315
+ # begin
316
+ # Client.select(:all, :limit => 10, :next_token => Client.next_token)
317
+ # end while Client.next_token
318
+ #
319
+ # Sort oder:
320
+ # If :order=>'attribute' option is specified then result response (ordered by 'attribute') will contain only items where attribute is defined (is not null).
321
+ #
322
+ # Client.select(:all) # returns all records
323
+ # Client.select(:all, :order => 'gender') # returns all records ordered by gender where gender attribute exists
324
+ # Client.select(:all, :order => 'name desc') # returns all records ordered by name in desc order where name attribute exists
325
+ #
326
+ # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingSelect.html
327
+ #
328
+ def select(*args)
329
+ find(*args)
330
+ end
331
+
332
+ def generate_id # :nodoc:
333
+ UUIDTools::UUID.timestamp_create().to_s
334
+ end
335
+
336
+ protected
337
+
338
+ # Select
339
+
340
+ def select_from_ids(args, options) # :nodoc:
341
+ cond = []
342
+ # detect amount of records requested
343
+ bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
344
+ # flatten ids
345
+ args = args.to_a.flatten
346
+ args.each { |id| cond << "id=#{self.connection.escape(id)}" }
347
+ ids_cond = "(#{cond.join(' OR ')})"
348
+ # user defined :conditions to string (if it was defined)
349
+ options[:conditions] = build_conditions(options[:conditions])
350
+ # join ids condition and user defined conditions
351
+ options[:conditions] = options[:conditions].blank? ? ids_cond : "(#{options[:conditions]}) AND #{ids_cond}"
352
+ result = sql_select(options)
353
+ # if one record was requested then return it
354
+ unless bunch_of_records_requested
355
+ record = result.first
356
+ # railse if nothing was found
357
+ raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record
358
+ record
359
+ else
360
+ # if a bunch of records was requested then return check that we found all of them
361
+ # and return as an array
362
+ unless args.size == result.size
363
+ # todo: might make sense to return the array but with nil values in the slots where an item wasn't found?
364
+ id_list = args.map{|i| "'#{i}'"}.join(',')
365
+ raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
366
+ else
367
+ result
368
+ end
369
+ end
370
+ end
371
+
372
+ def sql_select(options) # :nodoc:
373
+ count = options[:count] || false
374
+ #puts 'count? ' + count.to_s
375
+ @next_token = options[:next_token]
376
+ select_expression = build_select(options)
377
+ # request items
378
+ query_result = self.connection.select(select_expression, @next_token)
379
+ #puts 'QR=' + query_result.inspect
380
+ if count
381
+ return query_result[:items][0]["Domain"]["Count"][0].to_i
382
+ end
383
+ @next_token = query_result[:next_token]
384
+ items = query_result[:items].map do |hash|
385
+ id, attributes = hash.shift
386
+ new_item = self.new( attributes.merge({ 'id' => id }))
387
+ new_item.mark_as_old
388
+ new_item
389
+ end
390
+ items
391
+ end
392
+
393
+ # select_by helpers
394
+ def select_all_by_(format_str, args, options) # :nodoc:
395
+ fields = format_str.to_s.sub(/^select_(all_)?by_/, '').split('_and_')
396
+ conditions = fields.map { |field| "#{field}=?" }.join(' AND ')
397
+ options[:conditions] = [conditions, *args]
398
+ find(:all, options)
399
+ end
400
+
401
+ def select_by_(format_str, args, options) # :nodoc:
402
+ options[:limit] = 1
403
+ select_all_by_(format_str, args, options).first
404
+ end
405
+
406
+ # Query
407
+
408
+ # Returns an array of query attributes.
409
+ # Query_expression must be a well formated SDB query string:
410
+ # query_attributes("['title' starts-with 'O\\'Reily'] intersection ['year' = '2007']") #=> ["title", "year"]
411
+ def query_attributes(query_expression) # :nodoc:
412
+ attrs = []
413
+ array = query_expression.scan(/['"](.*?[^\\])['"]/).flatten
414
+ until array.empty? do
415
+ attrs << array.shift # skip it's value
416
+ array.shift #
417
+ end
418
+ attrs
419
+ end
420
+
421
+ # Returns an array of [attribute_name, 'asc'|'desc']
422
+ def sort_options(sort_string)
423
+ sort_string[/['"]?(\w+)['"]? *(asc|desc)?/i]
424
+ [$1, ($2 || 'asc')]
425
+ end
426
+
427
+ # Perform a query request.
428
+ #
429
+ # Options
430
+ # :query_expression nil | string | array
431
+ # :max_number_of_items nil | integer
432
+ # :next_token nil | string
433
+ # :sort_option nil | string "name desc|asc"
434
+ #
435
+ def query(options) # :nodoc:
436
+ @next_token = options[:next_token]
437
+ query_expression = build_conditions(options[:query_expression])
438
+ # add sort_options to the query_expression
439
+ if options[:sort_option]
440
+ sort_by, sort_order = sort_options(options[:sort_option])
441
+ sort_query_expression = "['#{sort_by}' starts-with '']"
442
+ sort_by_expression = " sort '#{sort_by}' #{sort_order}"
443
+ # make query_expression to be a string (it may be null)
444
+ query_expression = query_expression.to_s
445
+ # quote from Amazon:
446
+ # The sort attribute must be present in at least one of the predicates of the query expression.
447
+ if query_expression.blank?
448
+ query_expression = sort_query_expression
449
+ elsif !query_attributes(query_expression).include?(sort_by)
450
+ query_expression += " intersection #{sort_query_expression}"
451
+ end
452
+ query_expression += sort_by_expression
453
+ end
454
+ # request items
455
+ query_result = self.connection.query(domain, query_expression, options[:max_number_of_items], @next_token)
456
+ @next_token = query_result[:next_token]
457
+ items = query_result[:items].map do |name|
458
+ new_item = self.new('id' => name)
459
+ new_item.mark_as_old
460
+ reload_if_exists(record) if options[:auto_load]
461
+ new_item
462
+ end
463
+ items
464
+ end
465
+
466
+ # reload a record unless it is nil
467
+ def reload_if_exists(record) # :nodoc:
468
+ record && record.reload
469
+ end
470
+
471
+ def reload_all_records(*list) # :nodoc:
472
+ list.flatten.each { |record| reload_if_exists(record) }
473
+ end
474
+
475
+ def find_every(options) # :nodoc:
476
+ records = query( :query_expression => options[:conditions],
477
+ :max_number_of_items => options[:limit],
478
+ :next_token => options[:next_token],
479
+ :sort_option => options[:sort] || options[:order] )
480
+ options[:auto_load] ? reload_all_records(records) : records
481
+ end
482
+
483
+ def find_initial(options) # :nodoc:
484
+ options[:limit] = 1
485
+ record = find_every(options).first
486
+ options[:auto_load] ? reload_all_records(record).first : record
487
+ end
488
+
489
+ def find_from_ids(args, options) # :nodoc:
490
+ cond = []
491
+ # detect amount of records requested
492
+ bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
493
+ # flatten ids
494
+ args = args.to_a.flatten
495
+ args.each { |id| cond << "'id'=#{self.connection.escape(id)}" }
496
+ ids_cond = "[#{cond.join(' OR ')}]"
497
+ # user defined :conditions to string (if it was defined)
498
+ options[:conditions] = build_conditions(options[:conditions])
499
+ # join ids condition and user defined conditions
500
+ options[:conditions] = options[:conditions].blank? ? ids_cond : "#{options[:conditions]} intersection #{ids_cond}"
501
+ result = find_every(options)
502
+ # if one record was requested then return it
503
+ unless bunch_of_records_requested
504
+ record = result.first
505
+ # railse if nothing was found
506
+ raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record
507
+ options[:auto_load] ? reload_all_records(record).first : record
508
+ else
509
+ # if a bunch of records was requested then return check that we found all of them
510
+ # and return as an array
511
+ unless args.size == result.size
512
+ id_list = args.map{|i| "'#{i}'"}.join(',')
513
+ raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
514
+ else
515
+ options[:auto_load] ? reload_all_records(result) : result
516
+ end
517
+ end
518
+ end
519
+
520
+ # find_by helpers
521
+ def find_all_by_(format_str, args, options) # :nodoc:
522
+ fields = format_str.to_s.sub(/^find_(all_)?by_/, '').split('_and_')
523
+ conditions = fields.map { |field| "['#{field}'=?]" }.join(' intersection ')
524
+ options[:conditions] = [conditions, *args]
525
+ find(:all, options)
526
+ end
527
+
528
+ def find_by_(format_str, args, options) # :nodoc:
529
+ options[:limit] = 1
530
+ find_all_by_(format_str, args, options).first
531
+ end
532
+
533
+ # Misc
534
+
535
+ def method_missing(method, *args) # :nodoc:
536
+ if method.to_s[/^(find_all_by_|find_by_|select_all_by_|select_by_)/]
537
+ # get rid of the find ones, only select now
538
+ to_send_to = $1
539
+ attributes = method.to_s[$1.length..method.to_s.length]
540
+ # puts 'attributes=' + attributes
541
+ if to_send_to[0...4] == "find"
542
+ to_send_to = "select" + to_send_to[4..to_send_to.length]
543
+ # puts 'CONVERTED ' + $1 + " to " + to_send_to
544
+ end
545
+
546
+ options = args.last.is_a?(Hash) ? args.pop : {}
547
+ __send__(to_send_to, attributes, args, options)
548
+ else
549
+ super(method, *args)
550
+ end
551
+ end
552
+
553
+ def build_select(options) # :nodoc:
554
+ select = options[:select] || '*'
555
+ select = options[:count] ? "count(*)" : select
556
+ #puts 'select=' + select.to_s
557
+ from = options[:from] || domain
558
+ conditions = options[:conditions] ? " WHERE #{build_conditions(options[:conditions])}" : ''
559
+ order = options[:order] ? " ORDER BY #{options[:order]}" : ''
560
+ limit = options[:limit] ? " LIMIT #{options[:limit]}" : ''
561
+ # mix sort by argument (it must present in response)
562
+ unless order.blank?
563
+ sort_by, sort_order = sort_options(options[:order])
564
+ conditions << (conditions.blank? ? " WHERE " : " AND ") << "(#{sort_by} IS NOT NULL)"
565
+ end
566
+ "SELECT #{select} FROM #{from}#{conditions}#{order}#{limit}"
567
+ end
568
+
569
+ def build_conditions(conditions) # :nodoc:
570
+ case
571
+ when conditions.is_a?(Array) then
572
+ connection.query_expression_from_array(conditions)
573
+ when conditions.is_a?(Hash) then
574
+ connection.query_expression_from_hash(conditions)
575
+ else
576
+ conditions
577
+ end
578
+ end
261
579
 
262
- =begin
580
+ end
263
581
 
264
- options = args.last.is_a?(Hash) ? args.pop : {}
265
- case args.first
266
- when :all then find_every options
267
- when :first then find_initial options
268
- else find_from_ids args, options
269
- end
582
+ public
583
+
584
+ # instance attributes
585
+ attr_accessor :attributes
586
+
587
+ # item name
588
+ attr_accessor :id
589
+
590
+ # Create new Item instance.
591
+ # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
592
+ #
593
+ # item = Client.new('name' => 'Jon', 'toys' => ['girls', 'beer', 'pub'])
594
+ # puts item.inspect #=> #<Client:0xb77a2698 @new_record=true, @attributes={"name"=>["Jon"], "toys"=>["girls", "beer", "pub"]}>
595
+ # item.save #=> {"name"=>["Jon"], "id"=>"c03edb7e-e45c-11dc-bede-001bfc466dd7", "toys"=>["girls", "beer", "pub"]}
596
+ # puts item.inspect #=> #<Client:0xb77a2698 @new_record=false, @attributes={"name"=>["Jon"], "id"=>"c03edb7e-e45c-11dc-bede-001bfc466dd7", "toys"=>["girls", "beer", "pub"]}>
597
+ #
598
+ def initialize(attrs={})
599
+ @attributes = uniq_values(attrs)
600
+ @new_record = true
601
+ end
270
602
 
271
- =end
603
+ # Create and save new Item instance.
604
+ # +Attributes+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
605
+ #
606
+ # item = Client.create('name' => 'Cat', 'toys' => ['Jons socks', 'mice', 'clew'])
607
+ # puts item.inspect #=> #<Client:0xb77a0a78 @new_record=false, @attributes={"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "mice", "clew"]}>
608
+ #
609
+ def self.create(attributes={})
610
+ item = self.new(attributes)
611
+ item.save
612
+ item
613
+ end
272
614
 
273
- end
615
+ # Returns an item id. Same as: item['id'] or item.attributes['id']
616
+ def id
617
+ @attributes['id']
618
+ end
274
619
 
275
- # Perform a SQL-like select request.
276
- #
277
- # Single record:
278
- #
279
- # Client.select(:first)
280
- # Client.select(:first, :conditions=> [ "name=? AND wife=?", "Jon", "Sandy"])
281
- # Client.select(:first, :conditions=> { :name=>"Jon", :wife=>"Sandy" }, :select => :girfriends)
282
- #
283
- # Bunch of records:
284
- #
285
- # Client.select(:all)
286
- # Client.select(:all, :limit => 10)
287
- # Client.select(:all, :conditions=> [ "name=? AND 'girlfriend'=?", "Jon", "Judy"])
288
- # Client.select(:all, :conditions=> { :name=>"Sandy" }, :limit => 3)
289
- #
290
- # Records by ids:
291
- #
292
- # Client.select('1')
293
- # Client.select('1234987b4583475347523948')
294
- # Client.select('1','2','3','4', :conditions=> ["toys=?", "beer"])
295
- #
296
- # Find helpers: RightAws::ActiveSdb::Base.select_by_... and RightAws::ActiveSdb::Base.select_all_by_...
297
- #
298
- # Client.select_by_name('Matias Rust')
299
- # Client.select_by_name_and_city('Putin','Moscow')
300
- # Client.select_by_name_and_city_and_post('Medvedev','Moscow','president')
301
- #
302
- # Client.select_all_by_author('G.Bush jr')
303
- # Client.select_all_by_age_and_gender_and_ethnicity('34','male','russian')
304
- # Client.select_all_by_gender_and_country('male', 'Russia', :order => 'name')
305
- #
306
- # Continue listing:
307
- #
308
- # # initial listing
309
- # Client.select(:all, :limit => 10)
310
- # # continue listing
311
- # begin
312
- # Client.select(:all, :limit => 10, :next_token => Client.next_token)
313
- # end while Client.next_token
314
- #
315
- # Sort oder:
316
- # If :order=>'attribute' option is specified then result response (ordered by 'attribute') will contain only items where attribute is defined (is not null).
317
- #
318
- # Client.select(:all) # returns all records
319
- # Client.select(:all, :order => 'gender') # returns all records ordered by gender where gender attribute exists
320
- # Client.select(:all, :order => 'name desc') # returns all records ordered by name in desc order where name attribute exists
321
- #
322
- # see http://docs.amazonwebservices.com/AmazonSimpleDB/2007-11-07/DeveloperGuide/index.html?UsingSelect.html
323
- #
324
- def select(*args)
325
- options = args.last.is_a?(Hash) ? args.pop : {}
326
- # puts 'first=' + args.first.to_s
327
- case args.first
328
- when :all then sql_select(options)
329
- when :first then sql_select(options.merge(:limit => 1)).first
330
- else select_from_ids args, options
331
- end
332
- end
620
+ # Sets an item id.
621
+ def id=(id)
622
+ @attributes['id'] = id.to_s
623
+ end
333
624
 
334
- def generate_id # :nodoc:
335
- UUIDTools::UUID.timestamp_create().to_s
336
- end
625
+ # Returns a hash of all the attributes.
626
+ #
627
+ # puts item.attributes.inspect #=> {"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "clew", "mice"]}
628
+ #
629
+ def attributes
630
+ @attributes.dup
631
+ end
337
632
 
338
- protected
339
-
340
- # Select
341
-
342
- def select_from_ids(args, options) # :nodoc:
343
- cond = []
344
- # detect amount of records requested
345
- bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
346
- # flatten ids
347
- args = args.to_a.flatten
348
- args.each { |id| cond << "id=#{self.connection.escape(id)}" }
349
- ids_cond = "(#{cond.join(' OR ')})"
350
- # user defined :conditions to string (if it was defined)
351
- options[:conditions] = build_conditions(options[:conditions])
352
- # join ids condition and user defined conditions
353
- options[:conditions] = options[:conditions].blank? ? ids_cond : "(#{options[:conditions]}) AND #{ids_cond}"
354
- result = sql_select(options)
355
- # if one record was requested then return it
356
- unless bunch_of_records_requested
357
- record = result.first
358
- # railse if nothing was found
359
- raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record
360
- record
361
- else
362
- # if a bunch of records was requested then return check that we found all of them
363
- # and return as an array
364
- unless args.size == result.size
365
- id_list = args.map{|i| "'#{i}'"}.join(',')
366
- raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
367
- else
368
- result
369
- end
370
- end
371
- end
633
+ # Allows one to set all the attributes at once by passing in a hash with keys matching the attribute names.
634
+ # if '+id+' attribute is not set in new attributes has then it being derived from old attributes.
635
+ #
636
+ # puts item.attributes.inspect #=> {"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "clew", "mice"]}
637
+ # # set new attributes ('id' is missed)
638
+ # item.attributes = { 'name'=>'Dog', 'toys'=>['bones','cats'] }
639
+ # puts item.attributes.inspect #=> {"name"=>["Dog"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["bones", "cats"]}
640
+ # # set new attributes ('id' is set)
641
+ # item.attributes = { 'id' => 'blah-blah', 'name'=>'Birds', 'toys'=>['seeds','dogs tail'] }
642
+ # puts item.attributes.inspect #=> {"name"=>["Birds"], "id"=>"blah-blah", "toys"=>["seeds", "dogs tail"]}
643
+ #
644
+ def attributes=(attrs)
645
+ old_id = @attributes['id']
646
+ @attributes = uniq_values(attrs)
647
+ @attributes['id'] = old_id if @attributes['id'].blank? && !old_id.blank?
648
+ self.attributes
649
+ end
372
650
 
373
- def sql_select(options) # :nodoc:
374
- @next_token = options[:next_token]
375
- select_expression = build_select(options)
376
- # request items
377
- query_result = self.connection.select(select_expression, @next_token)
378
- @next_token = query_result[:next_token]
379
- items = query_result[:items].map do |hash|
380
- id, attributes = hash.shift
381
- new_item = self.new( attributes.merge({ 'id' => id }))
382
- new_item.mark_as_old
383
- new_item
384
- end
385
- items
386
- end
651
+ def connection
652
+ self.class.connection
653
+ end
387
654
 
388
- # select_by helpers
389
- def select_all_by_(format_str, args, options) # :nodoc:
390
- fields = format_str.to_s.sub(/^select_(all_)?by_/,'').split('_and_')
391
- conditions = fields.map { |field| "#{field}=?" }.join(' AND ')
392
- options[:conditions] = [conditions, *args]
393
- select(:all, options)
394
- end
655
+ # Item domain name.
656
+ def domain
657
+ self.class.domain
658
+ end
395
659
 
396
- def select_by_(format_str, args, options) # :nodoc:
397
- options[:limit] = 1
398
- select_all_by_(format_str, args, options).first
399
- end
660
+ # Returns the values of the attribute identified by +attribute+.
661
+ #
662
+ # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"]
663
+ #
664
+ def [](attribute)
665
+ @attributes[attribute.to_s]
666
+ end
400
667
 
401
- # Query
402
-
403
- # Returns an array of query attributes.
404
- # Query_expression must be a well formated SDB query string:
405
- # query_attributes("['title' starts-with 'O\\'Reily'] intersection ['year' = '2007']") #=> ["title", "year"]
406
- def query_attributes(query_expression) # :nodoc:
407
- attrs = []
408
- array = query_expression.scan(/['"](.*?[^\\])['"]/).flatten
409
- until array.empty? do
410
- attrs << array.shift # skip it's value
411
- array.shift #
412
- end
413
- attrs
414
- end
668
+ # Updates the attribute identified by +attribute+ with the specified +values+.
669
+ #
670
+ # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"]
671
+ # item['Cat'] = ["Whiskas", "chicken"]
672
+ # puts item['Cat'].inspect #=> ["Whiskas", "chicken"]
673
+ #
674
+ def []=(attribute, values)
675
+ attribute = attribute.to_s
676
+ @attributes[attribute] = attribute == 'id' ? values.to_s : values.is_a?(Array) ? values.uniq : [values]
415
677
 
416
- # Returns an array of [attribute_name, 'asc'|'desc']
417
- def sort_options(sort_string)
418
- sort_string[/['"]?(\w+)['"]? *(asc|desc)?/i]
419
- [$1, ($2 || 'asc')]
420
- end
678
+ end
421
679
 
422
- # Perform a query request.
423
- #
424
- # Options
425
- # :query_expression nil | string | array
426
- # :max_number_of_items nil | integer
427
- # :next_token nil | string
428
- # :sort_option nil | string "name desc|asc"
429
- #
430
- def query(options) # :nodoc:
431
- @next_token = options[:next_token]
432
- query_expression = build_conditions(options[:query_expression])
433
- # add sort_options to the query_expression
434
- if options[:sort_option]
435
- sort_by, sort_order = sort_options(options[:sort_option])
436
- sort_query_expression = "['#{sort_by}' starts-with '']"
437
- sort_by_expression = " sort '#{sort_by}' #{sort_order}"
438
- # make query_expression to be a string (it may be null)
439
- query_expression = query_expression.to_s
440
- # quote from Amazon:
441
- # The sort attribute must be present in at least one of the predicates of the query expression.
442
- if query_expression.blank?
443
- query_expression = sort_query_expression
444
- elsif !query_attributes(query_expression).include?(sort_by)
445
- query_expression += " intersection #{sort_query_expression}"
446
- end
447
- query_expression += sort_by_expression
448
- end
449
- # request items
450
- query_result = self.connection.query(domain, query_expression, options[:max_number_of_items], @next_token)
451
- @next_token = query_result[:next_token]
452
- items = query_result[:items].map do |name|
453
- new_item = self.new('id' => name)
454
- new_item.mark_as_old
455
- reload_if_exists(record) if options[:auto_load]
456
- new_item
457
- end
458
- items
459
- end
680
+ # Reload attributes from SDB. Replaces in-memory attributes.
681
+ #
682
+ # item = Client.find_by_name('Cat') #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false>
683
+ # item.reload #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}, @new_record=false>
684
+ #
685
+ def reload
686
+ raise_on_id_absence
687
+ old_id = id
688
+ attrs = connection.get_attributes(domain, id)[:attributes]
689
+ @attributes = {}
690
+ unless attrs.blank?
691
+ attrs.each { |attribute, values| @attributes[attribute] = values }
692
+ @attributes['id'] = old_id
693
+ end
694
+ mark_as_old
695
+ @attributes
696
+ end
460
697
 
461
- # reload a record unless it is nil
462
- def reload_if_exists(record) # :nodoc:
463
- record && record.reload
464
- end
698
+ # Reload a set of attributes from SDB. Adds the loaded list to in-memory data.
699
+ # +attrs_list+ is an array or comma separated list of attributes names.
700
+ # Returns a hash of loaded attributes.
701
+ #
702
+ # This is not the best method to get a bunch of attributes because
703
+ # a web service call is being performed for every attribute.
704
+ #
705
+ # item = Client.find_by_name('Cat')
706
+ # item.reload_attributes('toys', 'name') #=> {"name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}
707
+ #
708
+ def reload_attributes(*attrs_list)
709
+ raise_on_id_absence
710
+ attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s }
711
+ attrs_list.delete('id')
712
+ result = {}
713
+ attrs_list.flatten.uniq.each do |attribute|
714
+ attribute = attribute.to_s
715
+ values = connection.get_attributes(domain, id, attribute)[:attributes][attribute]
716
+ unless values.blank?
717
+ @attributes[attribute] = result[attribute] = values
718
+ else
719
+ @attributes.delete(attribute)
720
+ end
721
+ end
722
+ mark_as_old
723
+ result
724
+ end
465
725
 
466
- def reload_all_records(*list) # :nodoc:
467
- list.flatten.each { |record| reload_if_exists(record) }
468
- end
726
+ # Stores in-memory attributes to SDB.
727
+ # Adds the attributes values to already stored at SDB.
728
+ # Returns a hash of stored attributes.
729
+ #
730
+ # sandy = Client.new(:name => 'Sandy') #=> #<Client:0xb775a7a8 @attributes={"name"=>["Sandy"]}, @new_record=true>
731
+ # sandy['toys'] = 'boys'
732
+ # sandy.put
733
+ # sandy['toys'] = 'patchwork'
734
+ # sandy.put
735
+ # sandy['toys'] = 'kids'
736
+ # sandy.put
737
+ # puts sandy.attributes.inspect #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
738
+ # sandy.reload #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"]}
739
+ #
740
+ # compare to +save+ method
741
+ def put
742
+ @attributes = uniq_values(@attributes)
743
+ prepare_for_update
744
+ attrs = @attributes.dup
745
+ attrs.delete('id')
746
+ connection.put_attributes(domain, id, attrs) unless attrs.blank?
747
+ connection.put_attributes(domain, id, { 'id' => id }, :replace)
748
+ mark_as_old
749
+ @attributes
750
+ end
469
751
 
470
- def find_every(options) # :nodoc:
471
- records = query( :query_expression => options[:conditions],
472
- :max_number_of_items => options[:limit],
473
- :next_token => options[:next_token],
474
- :sort_option => options[:sort] || options[:order] )
475
- options[:auto_load] ? reload_all_records(records) : records
476
- end
752
+ # Stores specified attributes.
753
+ # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
754
+ # Returns a hash of saved attributes.
755
+ #
756
+ # see to +put+ method
757
+ def put_attributes(attrs)
758
+ attrs = uniq_values(attrs)
759
+ prepare_for_update
760
+ # if 'id' is present in attrs hash:
761
+ # replace internal 'id' attribute and remove it from the attributes to be sent
762
+ @attributes['id'] = attrs['id'] unless attrs['id'].blank?
763
+ attrs.delete('id')
764
+ # add new values to all attributes from list
765
+ connection.put_attributes(domain, id, attrs) unless attrs.blank?
766
+ connection.put_attributes(domain, id, { 'id' => id }, :replace)
767
+ attrs.each do |attribute, values|
768
+ @attributes[attribute] ||= []
769
+ @attributes[attribute] += values
770
+ @attributes[attribute].uniq!
771
+ end
772
+ mark_as_old
773
+ attributes
774
+ end
477
775
 
478
- def find_initial(options) # :nodoc:
479
- options[:limit] = 1
480
- record = find_every(options).first
481
- options[:auto_load] ? reload_all_records(record).first : record
482
- end
776
+ # Store in-memory attributes to SDB.
777
+ # Replaces the attributes values already stored at SDB by in-memory data.
778
+ # Returns a hash of stored attributes.
779
+ #
780
+ # sandy = Client.new(:name => 'Sandy') #=> #<Client:0xb775a7a8 @attributes={"name"=>["Sandy"]}, @new_record=true>
781
+ # sandy['toys'] = 'boys'
782
+ # sandy.save
783
+ # sandy['toys'] = 'patchwork'
784
+ # sandy.save
785
+ # sandy['toys'] = 'kids'
786
+ # sandy.save
787
+ # puts sandy.attributes.inspect #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
788
+ # sandy.reload #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
789
+ #
790
+ # Options:
791
+ # - :except => Array of attributes to NOT save
792
+ #
793
+ # compare to +put+ method
794
+ def save(options={})
795
+ pre_save2
796
+ atts_to_save = @attributes.dup
797
+ #options = params.first.is_a?(Hash) ? params.pop : {}
798
+ if options[:except]
799
+ options[:except].each do |e|
800
+ atts_to_save.delete(e).inspect
801
+ end
802
+ end
803
+ if options[:dirty] # Only used in simple_record right now
804
+ # only save if the attribute is dirty
805
+ dirty_atts = options[:dirty_atts]
806
+ atts_to_save.delete_if { |key, value| !dirty_atts.has_key?(key) }
807
+ end
808
+ connection.put_attributes(domain, id, atts_to_save, :replace)
809
+ apres_save2
810
+ @attributes
811
+ end
483
812
 
484
- def find_from_ids(args, options) # :nodoc:
485
- cond = []
486
- # detect amount of records requested
487
- bunch_of_records_requested = args.size > 1 || args.first.is_a?(Array)
488
- # flatten ids
489
- args = args.to_a.flatten
490
- args.each { |id| cond << "'id'=#{self.connection.escape(id)}" }
491
- ids_cond = "[#{cond.join(' OR ')}]"
492
- # user defined :conditions to string (if it was defined)
493
- options[:conditions] = build_conditions(options[:conditions])
494
- # join ids condition and user defined conditions
495
- options[:conditions] = options[:conditions].blank? ? ids_cond : "#{options[:conditions]} intersection #{ids_cond}"
496
- result = find_every(options)
497
- # if one record was requested then return it
498
- unless bunch_of_records_requested
499
- record = result.first
500
- # railse if nothing was found
501
- raise ActiveSdbError.new("Couldn't find #{name} with ID #{args}") unless record
502
- options[:auto_load] ? reload_all_records(record).first : record
503
- else
504
- # if a bunch of records was requested then return check that we found all of them
505
- # and return as an array
506
- unless args.size == result.size
507
- id_list = args.map{|i| "'#{i}'"}.join(',')
508
- raise ActiveSdbError.new("Couldn't find all #{name} with IDs (#{id_list}) (found #{result.size} results, but was looking for #{args.size})")
509
- else
510
- options[:auto_load] ? reload_all_records(result) : result
511
- end
512
- end
513
- end
813
+ def pre_save2
814
+ @attributes = uniq_values(@attributes)
815
+ prepare_for_update
816
+ end
514
817
 
515
- # find_by helpers
516
- def find_all_by_(format_str, args, options) # :nodoc:
517
- fields = format_str.to_s.sub(/^find_(all_)?by_/,'').split('_and_')
518
- conditions = fields.map { |field| "['#{field}'=?]" }.join(' intersection ')
519
- options[:conditions] = [conditions, *args]
520
- find(:all, options)
521
- end
818
+ def apres_save2
819
+ mark_as_old
820
+ end
522
821
 
523
- def find_by_(format_str, args, options) # :nodoc:
524
- options[:limit] = 1
525
- find_all_by_(format_str, args, options).first
526
- end
822
+ # Replaces the attributes at SDB by the given values.
823
+ # +Attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
824
+ # The other in-memory attributes are not being saved.
825
+ # Returns a hash of stored attributes.
826
+ #
827
+ # see +save+ method
828
+ def save_attributes(attrs)
829
+ prepare_for_update
830
+ attrs = uniq_values(attrs)
831
+ # if 'id' is present in attrs hash then replace internal 'id' attribute
832
+ unless attrs['id'].blank?
833
+ @attributes['id'] = attrs['id']
834
+ else
835
+ attrs['id'] = id
836
+ end
837
+ connection.put_attributes(domain, id, attrs, :replace) unless attrs.blank?
838
+ attrs.each { |attribute, values| attrs[attribute] = values }
839
+ mark_as_old
840
+ attrs
841
+ end
527
842
 
528
- # Misc
843
+ # Remove specified values from corresponding attributes.
844
+ # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
845
+ #
846
+ # sandy = Client.find_by_name 'Sandy'
847
+ # sandy.reload
848
+ # puts sandy.inspect #=> #<Client:0xb77b48fc @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"]}>
849
+ # puts sandy.delete_values('toys' => 'patchwork') #=> { 'toys' => ['patchwork'] }
850
+ # puts sandy.inspect #=> #<Client:0xb77b48fc @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids"]}>
851
+ #
852
+ def delete_values(attrs)
853
+ raise_on_id_absence
854
+ attrs = uniq_values(attrs)
855
+ attrs.delete('id')
856
+ unless attrs.blank?
857
+ connection.delete_attributes(domain, id, attrs)
858
+ attrs.each do |attribute, values|
859
+ # remove the values from the attribute
860
+ if @attributes[attribute]
861
+ @attributes[attribute] -= values
862
+ else
863
+ # if the attribute is unknown remove it from a resulting list of fixed attributes
864
+ attrs.delete(attribute)
865
+ end
866
+ end
867
+ end
868
+ attrs
869
+ end
529
870
 
530
- def method_missing(method, *args) # :nodoc:
531
- if method.to_s[/^(find_all_by_|find_by_|select_all_by_|select_by_)/]
532
- # get rid of the find ones, only select now
533
- to_send_to = $1
534
- attributes = method.to_s[$1.length..method.to_s.length]
535
- # puts 'attributes=' + attributes
536
- if to_send_to[0...4] == "find"
537
- to_send_to = "select" + to_send_to[4..to_send_to.length]
538
- # puts 'CONVERTED ' + $1 + " to " + to_send_to
871
+ # Removes specified attributes from the item.
872
+ # +attrs_list+ is an array or comma separated list of attributes names.
873
+ # Returns the list of deleted attributes.
874
+ #
875
+ # sandy = Client.find_by_name 'Sandy'
876
+ # sandy.reload
877
+ # puts sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"}>
878
+ # puts sandy.delete_attributes('toys') #=> ['toys']
879
+ # puts sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7"}>
880
+ #
881
+ def delete_attributes(*attrs_list)
882
+ raise_on_id_absence
883
+ attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s }
884
+ attrs_list.delete('id')
885
+ unless attrs_list.blank?
886
+ connection.delete_attributes(domain, id, attrs_list)
887
+ attrs_list.each { |attribute| @attributes.delete(attribute) }
888
+ end
889
+ attrs_list
539
890
  end
540
891
 
541
- options = args.last.is_a?(Hash) ? args.pop : {}
542
- __send__(to_send_to, attributes, args, options)
543
- else
544
- super(method, *args)
545
- end
546
- end
892
+ # Delete the Item entirely from SDB.
893
+ #
894
+ # sandy = Client.find_by_name 'Sandy'
895
+ # sandy.reload
896
+ # sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"}>
897
+ # puts sandy.delete
898
+ # sandy.reload
899
+ # puts sandy.inspect #=> #<Client:0xb7761d28 @attributes={}, @new_record=false>
900
+ #
901
+ def delete
902
+ raise_on_id_absence
903
+ connection.delete_attributes(domain, id)
904
+ end
547
905
 
548
- def build_select(options) # :nodoc:
549
- select = options[:select] || '*'
550
- from = options[:from] || domain
551
- conditions = options[:conditions] ? " WHERE #{build_conditions(options[:conditions])}" : ''
552
- order = options[:order] ? " ORDER BY #{options[:order]}" : ''
553
- limit = options[:limit] ? " LIMIT #{options[:limit]}" : ''
554
- # mix sort by argument (it must present in response)
555
- unless order.blank?
556
- sort_by, sort_order = sort_options(options[:order])
557
- conditions << (conditions.blank? ? " WHERE " : " AND ") << "(#{sort_by} IS NOT NULL)"
558
- end
559
- "SELECT #{select} FROM #{from}#{conditions}#{order}#{limit}"
560
- end
906
+ # Item ID
907
+ def to_s
908
+ @id
909
+ end
561
910
 
562
- def build_conditions(conditions) # :nodoc:
563
- case
564
- when conditions.is_a?(Array) then connection.query_expression_from_array(conditions)
565
- when conditions.is_a?(Hash) then connection.query_expression_from_hash(conditions)
566
- else conditions
567
- end
568
- end
911
+ # Returns true if this object hasn‘t been saved yet.
912
+ def new_record?
913
+ @new_record
914
+ end
569
915
 
570
- end
571
-
572
- public
573
-
574
- # instance attributes
575
- attr_accessor :attributes
576
-
577
- # item name
578
- attr_accessor :id
579
-
580
- # Create new Item instance.
581
- # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
582
- #
583
- # item = Client.new('name' => 'Jon', 'toys' => ['girls', 'beer', 'pub'])
584
- # puts item.inspect #=> #<Client:0xb77a2698 @new_record=true, @attributes={"name"=>["Jon"], "toys"=>["girls", "beer", "pub"]}>
585
- # item.save #=> {"name"=>["Jon"], "id"=>"c03edb7e-e45c-11dc-bede-001bfc466dd7", "toys"=>["girls", "beer", "pub"]}
586
- # puts item.inspect #=> #<Client:0xb77a2698 @new_record=false, @attributes={"name"=>["Jon"], "id"=>"c03edb7e-e45c-11dc-bede-001bfc466dd7", "toys"=>["girls", "beer", "pub"]}>
587
- #
588
- def initialize(attrs={})
589
- @attributes = uniq_values(attrs)
590
- @new_record = true
591
- end
592
-
593
- # Create and save new Item instance.
594
- # +Attributes+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
595
- #
596
- # item = Client.create('name' => 'Cat', 'toys' => ['Jons socks', 'mice', 'clew'])
597
- # puts item.inspect #=> #<Client:0xb77a0a78 @new_record=false, @attributes={"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "mice", "clew"]}>
598
- #
599
- def self.create(attributes={})
600
- item = self.new(attributes)
601
- item.save
602
- item
603
- end
604
-
605
- # Returns an item id. Same as: item['id'] or item.attributes['id']
606
- def id
607
- @attributes['id']
608
- end
609
-
610
- # Sets an item id.
611
- def id=(id)
612
- @attributes['id'] = id.to_s
613
- end
614
-
615
- # Returns a hash of all the attributes.
616
- #
617
- # puts item.attributes.inspect #=> {"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "clew", "mice"]}
618
- #
619
- def attributes
620
- @attributes.dup
621
- end
622
-
623
- # Allows one to set all the attributes at once by passing in a hash with keys matching the attribute names.
624
- # if '+id+' attribute is not set in new attributes has then it being derived from old attributes.
625
- #
626
- # puts item.attributes.inspect #=> {"name"=>["Cat"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["Jons socks", "clew", "mice"]}
627
- # # set new attributes ('id' is missed)
628
- # item.attributes = { 'name'=>'Dog', 'toys'=>['bones','cats'] }
629
- # puts item.attributes.inspect #=> {"name"=>["Dog"], "id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "toys"=>["bones", "cats"]}
630
- # # set new attributes ('id' is set)
631
- # item.attributes = { 'id' => 'blah-blah', 'name'=>'Birds', 'toys'=>['seeds','dogs tail'] }
632
- # puts item.attributes.inspect #=> {"name"=>["Birds"], "id"=>"blah-blah", "toys"=>["seeds", "dogs tail"]}
633
- #
634
- def attributes=(attrs)
635
- old_id = @attributes['id']
636
- @attributes = uniq_values(attrs)
637
- @attributes['id'] = old_id if @attributes['id'].blank? && !old_id.blank?
638
- self.attributes
639
- end
640
-
641
- def connection
642
- self.class.connection
643
- end
644
-
645
- # Item domain name.
646
- def domain
647
- self.class.domain
648
- end
649
-
650
- # Returns the values of the attribute identified by +attribute+.
651
- #
652
- # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"]
653
- #
654
- def [](attribute)
655
- @attributes[attribute.to_s]
656
- end
657
-
658
- # Updates the attribute identified by +attribute+ with the specified +values+.
659
- #
660
- # puts item['Cat'].inspect #=> ["Jons socks", "clew", "mice"]
661
- # item['Cat'] = ["Whiskas", "chicken"]
662
- # puts item['Cat'].inspect #=> ["Whiskas", "chicken"]
663
- #
664
- def []=(attribute, values)
665
- attribute = attribute.to_s
666
- @attributes[attribute] = attribute == 'id' ? values.to_s : values.is_a?(Array) ? values.uniq : [values]
667
- end
668
-
669
- # Reload attributes from SDB. Replaces in-memory attributes.
670
- #
671
- # item = Client.find_by_name('Cat') #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7"}, @new_record=false>
672
- # item.reload #=> #<Client:0xb77d0d40 @attributes={"id"=>"2937601a-e45d-11dc-a75f-001bfc466dd7", "name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}, @new_record=false>
673
- #
674
- def reload
675
- raise_on_id_absence
676
- old_id = id
677
- attrs = connection.get_attributes(domain, id)[:attributes]
678
- @attributes = {}
679
- unless attrs.blank?
680
- attrs.each { |attribute, values| @attributes[attribute] = values }
681
- @attributes['id'] = old_id
682
- end
683
- mark_as_old
684
- @attributes
685
- end
686
-
687
- # Reload a set of attributes from SDB. Adds the loaded list to in-memory data.
688
- # +attrs_list+ is an array or comma separated list of attributes names.
689
- # Returns a hash of loaded attributes.
690
- #
691
- # This is not the best method to get a bunch of attributes because
692
- # a web service call is being performed for every attribute.
693
- #
694
- # item = Client.find_by_name('Cat')
695
- # item.reload_attributes('toys', 'name') #=> {"name"=>["Cat"], "toys"=>["Jons socks", "clew", "mice"]}
696
- #
697
- def reload_attributes(*attrs_list)
698
- raise_on_id_absence
699
- attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s }
700
- attrs_list.delete('id')
701
- result = {}
702
- attrs_list.flatten.uniq.each do |attribute|
703
- attribute = attribute.to_s
704
- values = connection.get_attributes(domain, id, attribute)[:attributes][attribute]
705
- unless values.blank?
706
- @attributes[attribute] = result[attribute] = values
707
- else
708
- @attributes.delete(attribute)
709
- end
710
- end
711
- mark_as_old
712
- result
713
- end
714
-
715
- # Stores in-memory attributes to SDB.
716
- # Adds the attributes values to already stored at SDB.
717
- # Returns a hash of stored attributes.
718
- #
719
- # sandy = Client.new(:name => 'Sandy') #=> #<Client:0xb775a7a8 @attributes={"name"=>["Sandy"]}, @new_record=true>
720
- # sandy['toys'] = 'boys'
721
- # sandy.put
722
- # sandy['toys'] = 'patchwork'
723
- # sandy.put
724
- # sandy['toys'] = 'kids'
725
- # sandy.put
726
- # puts sandy.attributes.inspect #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
727
- # sandy.reload #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"]}
728
- #
729
- # compare to +save+ method
730
- def put
731
- @attributes = uniq_values(@attributes)
732
- prepare_for_update
733
- attrs = @attributes.dup
734
- attrs.delete('id')
735
- connection.put_attributes(domain, id, attrs) unless attrs.blank?
736
- connection.put_attributes(domain, id, { 'id' => id }, :replace)
737
- mark_as_old
738
- @attributes
739
- end
740
-
741
- # Stores specified attributes.
742
- # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
743
- # Returns a hash of saved attributes.
744
- #
745
- # see to +put+ method
746
- def put_attributes(attrs)
747
- attrs = uniq_values(attrs)
748
- prepare_for_update
749
- # if 'id' is present in attrs hash:
750
- # replace internal 'id' attribute and remove it from the attributes to be sent
751
- @attributes['id'] = attrs['id'] unless attrs['id'].blank?
752
- attrs.delete('id')
753
- # add new values to all attributes from list
754
- connection.put_attributes(domain, id, attrs) unless attrs.blank?
755
- connection.put_attributes(domain, id, { 'id' => id }, :replace)
756
- attrs.each do |attribute, values|
757
- @attributes[attribute] ||= []
758
- @attributes[attribute] += values
759
- @attributes[attribute].uniq!
760
- end
761
- mark_as_old
762
- attributes
763
- end
764
-
765
- # Store in-memory attributes to SDB.
766
- # Replaces the attributes values already stored at SDB by in-memory data.
767
- # Returns a hash of stored attributes.
768
- #
769
- # sandy = Client.new(:name => 'Sandy') #=> #<Client:0xb775a7a8 @attributes={"name"=>["Sandy"]}, @new_record=true>
770
- # sandy['toys'] = 'boys'
771
- # sandy.put
772
- # sandy['toys'] = 'patchwork'
773
- # sandy.put
774
- # sandy['toys'] = 'kids'
775
- # sandy.put
776
- # puts sandy.attributes.inspect #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
777
- # sandy.reload #=> {"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["kids"]}
778
- #
779
- # compare to +put+ method
780
- def save
781
- pre_save2
782
- connection.put_attributes(domain, id, @attributes, :replace)
783
- apres_save2
784
- @attributes
785
- end
786
-
787
- def pre_save2
788
- @attributes = uniq_values(@attributes)
789
- prepare_for_update
790
- end
791
-
792
- def apres_save2
793
- mark_as_old
794
- end
795
-
796
- # Replaces the attributes at SDB by the given values.
797
- # +Attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
798
- # The other in-memory attributes are not being saved.
799
- # Returns a hash of stored attributes.
800
- #
801
- # see +save+ method
802
- def save_attributes(attrs)
803
- prepare_for_update
804
- attrs = uniq_values(attrs)
805
- # if 'id' is present in attrs hash then replace internal 'id' attribute
806
- unless attrs['id'].blank?
807
- @attributes['id'] = attrs['id']
808
- else
809
- attrs['id'] = id
810
- end
811
- connection.put_attributes(domain, id, attrs, :replace) unless attrs.blank?
812
- attrs.each { |attribute, values| attrs[attribute] = values }
813
- mark_as_old
814
- attrs
815
- end
816
-
817
- # Remove specified values from corresponding attributes.
818
- # +attrs+ is a hash: { attribute1 => values1, ..., attributeN => valuesN }.
819
- #
820
- # sandy = Client.find_by_name 'Sandy'
821
- # sandy.reload
822
- # puts sandy.inspect #=> #<Client:0xb77b48fc @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"]}>
823
- # puts sandy.delete_values('toys' => 'patchwork') #=> { 'toys' => ['patchwork'] }
824
- # puts sandy.inspect #=> #<Client:0xb77b48fc @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids"]}>
825
- #
826
- def delete_values(attrs)
827
- raise_on_id_absence
828
- attrs = uniq_values(attrs)
829
- attrs.delete('id')
830
- unless attrs.blank?
831
- connection.delete_attributes(domain, id, attrs)
832
- attrs.each do |attribute, values|
833
- # remove the values from the attribute
834
- if @attributes[attribute]
835
- @attributes[attribute] -= values
836
- else
837
- # if the attribute is unknown remove it from a resulting list of fixed attributes
838
- attrs.delete(attribute)
839
- end
840
- end
841
- end
842
- attrs
843
- end
844
-
845
- # Removes specified attributes from the item.
846
- # +attrs_list+ is an array or comma separated list of attributes names.
847
- # Returns the list of deleted attributes.
848
- #
849
- # sandy = Client.find_by_name 'Sandy'
850
- # sandy.reload
851
- # puts sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"}>
852
- # puts sandy.delete_attributes('toys') #=> ['toys']
853
- # puts sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7"}>
854
- #
855
- def delete_attributes(*attrs_list)
856
- raise_on_id_absence
857
- attrs_list = attrs_list.flatten.map{ |attribute| attribute.to_s }
858
- attrs_list.delete('id')
859
- unless attrs_list.blank?
860
- connection.delete_attributes(domain, id, attrs_list)
861
- attrs_list.each { |attribute| @attributes.delete(attribute) }
862
- end
863
- attrs_list
864
- end
865
-
866
- # Delete the Item entirely from SDB.
867
- #
868
- # sandy = Client.find_by_name 'Sandy'
869
- # sandy.reload
870
- # sandy.inspect #=> #<Client:0xb7761d28 @new_record=false, @attributes={"name"=>["Sandy"], "id"=>"b2832ce2-e461-11dc-b13c-001bfc466dd7", "toys"=>["boys", "kids", "patchwork"}>
871
- # puts sandy.delete
872
- # sandy.reload
873
- # puts sandy.inspect #=> #<Client:0xb7761d28 @attributes={}, @new_record=false>
874
- #
875
- def delete
876
- raise_on_id_absence
877
- connection.delete_attributes(domain, id)
878
- end
879
-
880
- # Item ID
881
- def to_s
882
- @id
883
- end
884
-
885
- # Returns true if this object hasn‘t been saved yet.
886
- def new_record?
887
- @new_record
888
- end
889
-
890
- def mark_as_old # :nodoc:
891
- @new_record = false
892
- end
893
-
894
- private
895
-
896
- def raise_on_id_absence
897
- raise ActiveSdbError.new('Unknown record id') unless id
898
- end
899
-
900
- def prepare_for_update
901
- @attributes['id'] = self.class.generate_id if @attributes['id'].blank?
902
- end
903
-
904
- def uniq_values(attributes=nil) # :nodoc:
905
- attrs = {}
906
- attributes.each do |attribute, values|
907
- attribute = attribute.to_s
908
- attrs[attribute] = attribute == 'id' ? values.to_s : values.is_a?(Array) ? values.uniq : [values]
909
- attrs.delete(attribute) if values.blank?
916
+ def mark_as_old # :nodoc:
917
+ @new_record = false
918
+ end
919
+
920
+ private
921
+
922
+ def raise_on_id_absence
923
+ raise ActiveSdbError.new('Unknown record id') unless id
924
+ end
925
+
926
+ def prepare_for_update
927
+ @attributes['id'] = self.class.generate_id if @attributes['id'].blank?
928
+ end
929
+
930
+ def uniq_values(attributes=nil) # :nodoc:
931
+ attrs = {}
932
+ attributes.each do |attribute, values|
933
+ attribute = attribute.to_s
934
+ attrs[attribute] = attribute == 'id' ? values.to_s : values.is_a?(Array) ? values.uniq : [values]
935
+ attrs.delete(attribute) if values.blank?
936
+ end
937
+ attrs
938
+ end
910
939
  end
911
- attrs
912
- end
913
940
  end
914
- end
915
- end
941
+ end