aws 1.11.9 → 1.11.36

Sign up to get free protection for your applications and to get access to all the features.
@@ -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