activerecord-simpledb-adapter 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Ilia Ablamonov, Alex Gorkunov, Cloud Castle Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # ABOUT
2
+
3
+ Amazon SimpleDB Adapter for ActiveRecord for Rails3
4
+
5
+ # FEATURES
6
+
7
+ - All data saves in one sdb domain (each activerecord model class detected by special reserved column 'collection')
8
+ - Columns with numbers (integer and float) saves with special shift and constant size for correcting comparation in SELECT query.
9
+
10
+ # USAGE
11
+
12
+ install:
13
+
14
+ gem install activerecord-simpledb-adapter
15
+
16
+ config/database.yml:
17
+
18
+ defaults: &defaults
19
+ adapter: simpledb
20
+ access_key_id: KEY
21
+ secret_access_key: SECRET
22
+ development:
23
+ <<: *defaults
24
+ domain_name: domain_name
25
+
26
+ model example:
27
+ Person < ActiveRecord::Base
28
+ columns_definition do |t|
29
+ t.string :login
30
+ t.integer :year, :limit => 4
31
+ t.boolean :active
32
+ t.string :from, :to => 'from_property'
33
+ t.float :price
34
+ t.integer :lock_version
35
+
36
+ t.timestamps
37
+ end
38
+ end
39
+
40
+ # LICENSE
41
+
42
+ (The MIT License)
43
+
44
+ Copyright © Ilia Ablamonov, Alex Gorkunov, Cloud Castle Inc.
45
+
46
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ‘Software’), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
47
+
48
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
49
+
50
+ THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,80 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{activerecord-simpledb-adapter}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Ilia Ablamonov", "Alex Gorkunov", "Cloud Castle Inc."]
12
+ s.date = %q{2011-01-14}
13
+ s.email = %q{ilia@flamefork.ru}
14
+ s.extra_rdoc_files = [
15
+ "LICENSE.txt",
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ "LICENSE.txt",
20
+ "README.md",
21
+ "activerecord-simpledb-adapter.gemspec",
22
+ "lib/active_record/connection_adapters/simpledb_adapter.rb",
23
+ "lib/activerecord-simpledb-adapter.rb",
24
+ "lib/arel/visitors/simpledb.rb",
25
+ "lib/tasks/simpledb.rake"
26
+ ]
27
+ s.homepage = %q{http://github.com/cloudcastle/activerecord-simpledb-adapter}
28
+ s.licenses = ["MIT"]
29
+ s.require_paths = ["lib"]
30
+ s.rubygems_version = %q{1.3.7}
31
+ s.summary = %q{ActiveRecord SimpleDB adapter}
32
+ s.test_files = [
33
+ "spec/activerecord-simpledb-adapter_spec.rb",
34
+ "spec/spec_helper.rb"
35
+ ]
36
+
37
+ if s.respond_to? :specification_version then
38
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
39
+ s.specification_version = 3
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
43
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
44
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
45
+ s.add_development_dependency(%q<rcov>, [">= 0"])
46
+ s.add_development_dependency(%q<aws>, [">= 0"])
47
+ s.add_development_dependency(%q<activerecord>, [">= 0"])
48
+ s.add_development_dependency(%q<activesupport>, [">= 0"])
49
+ s.add_runtime_dependency(%q<aws>, ["~> 2.3.0"])
50
+ s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.3"])
51
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.0.3"])
52
+ s.add_runtime_dependency(%q<uuidtools>, ["~> 2.1.1"])
53
+ else
54
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
55
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
56
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
57
+ s.add_dependency(%q<rcov>, [">= 0"])
58
+ s.add_dependency(%q<aws>, [">= 0"])
59
+ s.add_dependency(%q<activerecord>, [">= 0"])
60
+ s.add_dependency(%q<activesupport>, [">= 0"])
61
+ s.add_dependency(%q<aws>, ["~> 2.3.0"])
62
+ s.add_dependency(%q<activerecord>, ["~> 3.0.3"])
63
+ s.add_dependency(%q<activesupport>, ["~> 3.0.3"])
64
+ s.add_dependency(%q<uuidtools>, ["~> 2.1.1"])
65
+ end
66
+ else
67
+ s.add_dependency(%q<rspec>, ["~> 2.3.0"])
68
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
69
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
70
+ s.add_dependency(%q<rcov>, [">= 0"])
71
+ s.add_dependency(%q<aws>, [">= 0"])
72
+ s.add_dependency(%q<activerecord>, [">= 0"])
73
+ s.add_dependency(%q<activesupport>, [">= 0"])
74
+ s.add_dependency(%q<aws>, ["~> 2.3.0"])
75
+ s.add_dependency(%q<activerecord>, ["~> 3.0.3"])
76
+ s.add_dependency(%q<activesupport>, ["~> 3.0.3"])
77
+ s.add_dependency(%q<uuidtools>, ["~> 2.1.1"])
78
+ end
79
+ end
80
+
@@ -0,0 +1,410 @@
1
+ require 'active_record/connection_adapters/abstract_adapter'
2
+ require 'active_support/core_ext/kernel/requires'
3
+ require 'active_support/core_ext/hash'
4
+ require 'uuidtools'
5
+
6
+ class Aws::SdbInterface
7
+ def put_attributes(domain_name, item_name, attributes, replace = false, expected_attributes = {})
8
+ params = params_with_attributes(domain_name, item_name, attributes, replace, expected_attributes)
9
+ link = generate_request("PutAttributes", params)
10
+ request_info( link, QSdbSimpleParser.new )
11
+ rescue Exception
12
+ on_exception
13
+ end
14
+
15
+ def delete_attributes(domain_name, item_name, attributes = nil, expected_attributes = {})
16
+ params = params_with_attributes(domain_name, item_name, attributes, false, expected_attributes)
17
+ link = generate_request("DeleteAttributes", params)
18
+ request_info( link, QSdbSimpleParser.new )
19
+ rescue Exception
20
+ on_exception
21
+ end
22
+
23
+ private
24
+ def pack_expected_attributes(attributes) #:nodoc:
25
+ {}.tap do |result|
26
+ idx = 0
27
+ attributes.each do |attribute, value|
28
+ v = value.is_a?(Array) ? value.first : value
29
+ result["Expected.#{idx}.Name"] = attribute.to_s
30
+ result["Expected.#{idx}.Value"] = ruby_to_sdb(v)
31
+ idx += 1
32
+ end
33
+ end
34
+ end
35
+
36
+ def pack_attributes(attributes = {}, replace = false, key_prefix = "")
37
+ {}.tap do |result|
38
+ idx = 0
39
+ if attributes
40
+ attributes.each do |attribute, value|
41
+ v = value.is_a?(Array) ? value.first : value
42
+ result["#{key_prefix}Attribute.#{idx}.Replace"] = 'true' if replace
43
+ result["#{key_prefix}Attribute.#{idx}.Name"] = attribute
44
+ result["#{key_prefix}Attribute.#{idx}.Value"] = ruby_to_sdb(v)
45
+ idx += 1
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ def params_with_attributes(domain_name, item_name, attributes, replace, expected_attrubutes)
52
+ {}.tap do |p|
53
+ p['DomainName'] = domain_name
54
+ p['ItemName'] = item_name
55
+ p.merge!(pack_attributes(attributes, replace)).merge!(pack_expected_attributes(expected_attrubutes))
56
+ end
57
+ end
58
+ end
59
+
60
+ module ActiveRecord
61
+ class Base
62
+ def self.simpledb_connection(config) # :nodoc:
63
+ require 'aws'
64
+
65
+ config = config.symbolize_keys
66
+
67
+ ConnectionAdapters::SimpleDBAdapter.new nil, logger,
68
+ config[:access_key_id],
69
+ config[:secret_access_key],
70
+ config[:domain_name],
71
+ {
72
+ :server => config[:host],
73
+ :port => config[:port],
74
+ :protocol => config[:protocol],
75
+ :logger => logger
76
+ },
77
+ config
78
+ end
79
+
80
+ DEFAULT_COLLECTION_COLUMN_NAME = "collection".freeze
81
+
82
+ def self.columns_definition options = {}
83
+ table_definition = ConnectionAdapters::SimpleDbTableDifinition.new(options[:collection_column_name] || DEFAULT_COLLECTION_COLUMN_NAME)
84
+ table_definition.primary_key(Base.get_primary_key(table_name.to_s.singularize))
85
+ yield table_definition if block_given?
86
+
87
+ ConnectionAdapters::SimpleDBAdapter.set_collection_columns table_name, table_definition
88
+ end
89
+
90
+
91
+
92
+ end
93
+
94
+ module Validations
95
+
96
+ class UniquenessValidator < ActiveModel::EachValidator
97
+
98
+ def validate_each(record, attribute, value)
99
+ finder_class = find_finder_class_for(record)
100
+ table = finder_class.unscoped
101
+
102
+ table_name = record.class.quoted_table_name
103
+
104
+ if value && record.class.serialized_attributes.key?(attribute.to_s)
105
+ value = YAML.dump value
106
+ end
107
+
108
+ sql, params = mount_sql_and_params(finder_class, table_name, attribute, value)
109
+
110
+ relation = table.where(sql, *params)
111
+
112
+ Array.wrap(options[:scope]).each do |scope_item|
113
+ scope_value = record.send(scope_item)
114
+ relation = relation.where(scope_item => scope_value)
115
+ end
116
+
117
+ if record.persisted?
118
+ # TODO : This should be in Arel
119
+ relation = relation.where("#{record.class.primary_key} != ?", record.send(:id))
120
+ end
121
+
122
+ if relation.exists?
123
+ record.errors.add(attribute, :taken, options.except(:case_sensitive, :scope).merge(:value => value))
124
+ end
125
+ end
126
+
127
+ protected
128
+ def mount_sql_and_params(klass, table_name, attribute, value) #:nodoc:
129
+ column = klass.columns_hash[attribute.to_s]
130
+
131
+ sql_attribute = "#{table_name}.#{klass.connection.quote_column_name(column.db_column_name)}"
132
+ sql = "#{sql_attribute} = ?"
133
+ if value.nil?
134
+ [sql, [value]]
135
+ elsif (options[:case_sensitive] || !column.text?)
136
+ [sql, [value]]
137
+ else
138
+ [sql, [value.downcase]]
139
+ end
140
+ end
141
+ end
142
+
143
+ end
144
+
145
+ module ConnectionAdapters
146
+
147
+ class SimpleDbTableDifinition < TableDefinition
148
+
149
+ attr_reader :collection_column_name
150
+
151
+ def initialize collection_column_name
152
+ super nil
153
+ @collection_column_name = collection_column_name
154
+ end
155
+
156
+ def xml_column_fallback(*args)
157
+ raise ConfigurationError, "Not supported"
158
+ end
159
+
160
+ def [](name)
161
+ @columns.find {|column| column.db_column_name == name.to_s}
162
+ end
163
+
164
+ def column(name, type, options = {})
165
+ raise ConfigurationError, %Q(column '#{collection_column_name}' reserved, please change column name) if name.to_s == collection_column_name
166
+ @columns << SimpleDBColumn.new(name.to_s, type.to_sym, options[:limit], options[:precision], options[:to])
167
+ self
168
+ end
169
+
170
+ end
171
+
172
+ class SimpleDBColumn < Column
173
+
174
+ DEFAULT_NUMBER_LIMIT = 4
175
+ DEFAULT_FLOAT_PRECISION = 4
176
+
177
+ def initialize(name, type, limit = nil, pricision = nil, to = nil)
178
+ super name, nil, type, true
179
+ @limit = limit if limit.present?
180
+ @precision = precision if precision.present?
181
+ @to = to
182
+ end
183
+
184
+ def quote_number value
185
+ case sql_type
186
+ when :float then
187
+ sprintf("%.#{number_precision}f", number_shift + value.to_f)
188
+ else
189
+ (number_shift + value.to_i).to_s
190
+ end
191
+ end
192
+
193
+ def unquote_number value
194
+ case sql_type
195
+ when :integer then
196
+ value.to_i - number_shift
197
+ when :float then
198
+ precision_part = 10 ** number_precision
199
+ ((value.to_f - number_shift) * precision_part).round / precision_part.to_f
200
+ else
201
+ value
202
+ end
203
+ end
204
+
205
+ def db_column_name
206
+ @to || name
207
+ end
208
+
209
+ private
210
+ def number_shift
211
+ 5 * 10 ** (limit || DEFAULT_NUMBER_LIMIT)
212
+ end
213
+
214
+ def number_precision
215
+ @precision || DEFAULT_FLOAT_PRECISION
216
+ end
217
+
218
+ def simplified_type(field_type)
219
+ t = field_type.to_s
220
+ if t == "primary_key"
221
+ :string
222
+ else
223
+ super(t)
224
+ end
225
+ end
226
+ end
227
+
228
+ class SimpleDBAdapter < AbstractAdapter
229
+
230
+ @@collections = {}
231
+ @@ccn = {}
232
+
233
+ def self.set_collection_columns table_name, columns_definition
234
+ @@collections[table_name] = columns_definition
235
+ @@ccn[table_name] = columns_definition.collection_column_name
236
+ end
237
+
238
+ def columns_definition table_name
239
+ @@collections[table_name]
240
+ end
241
+
242
+ def collection_column_name table_name
243
+ @@ccn[table_name]
244
+ end
245
+
246
+ ADAPTER_NAME = 'SimpleDB'.freeze
247
+
248
+ def adapter_name
249
+ ADAPTER_NAME
250
+ end
251
+
252
+ NIL_REPRESENTATION = "Aws::Nil".freeze
253
+
254
+ def nil_representation
255
+ NIL_REPRESENTATION
256
+ end
257
+
258
+
259
+ def supports_count_distinct?; false; end
260
+
261
+ #========= QUOTING =====================
262
+
263
+ #dirty hack for removing all (') from value for hash table attrubutes
264
+ def hash_value_quote(value, column = nil)
265
+ return nil if value.nil?
266
+ quote(value, column).gsub /^'*|'*$/, ''
267
+ end
268
+
269
+ def quote(value, column = nil)
270
+ if value.present? && column.present? && column.number?
271
+ "'#{column.quote_number value}'"
272
+ elsif value.nil?
273
+ "'#{nil_representation}'"
274
+ else
275
+ super
276
+ end
277
+ end
278
+ #=======================================
279
+
280
+ attr_reader :domain_name
281
+
282
+ def initialize(connection, logger, aws_key, aws_secret, domain_name, connection_parameters, config)
283
+ super(connection, logger)
284
+ @config = config
285
+ @domain_name = domain_name
286
+ @connection_parameters = [
287
+ aws_key,
288
+ aws_secret,
289
+ connection_parameters.merge(:nil_representation => nil_representation)
290
+ ]
291
+ connect
292
+ end
293
+
294
+ def connect
295
+ @connection = Aws::SdbInterface.new *@connection_parameters
296
+ end
297
+
298
+ def tables
299
+ @@collections.keys
300
+ end
301
+
302
+ def columns table_name, name = nil
303
+ @@collections[table_name].columns
304
+ end
305
+
306
+ def primary_key _
307
+ 'id'
308
+ end
309
+
310
+ def execute sql, name = nil, skip_logging = false
311
+ p sql
312
+ case sql[:action]
313
+ when :insert
314
+ item_name = get_id sql[:attrs]
315
+ item_name = sql[:attrs][:id] = generate_id unless item_name
316
+ @connection.put_attributes domain_name, item_name, sql[:attrs], true
317
+ @last_insert_id = item_name
318
+ when :update
319
+ item_name = get_id sql[:wheres], true
320
+ @connection.put_attributes domain_name, item_name, sql[:attrs], true, sql[:wheres]
321
+ when :delete
322
+ item_name = get_id sql[:wheres], true
323
+ @connection.delete_attributes domain_name, item_name, nil, sql[:wheres]
324
+ else
325
+ raise "Unsupported action: #{sql[:action].inspect}"
326
+ end
327
+ end
328
+
329
+ def insert_sql sql, name = nil, pk = nil, id_value = nil, sequence_name = nil
330
+ super || @last_insert_id
331
+ end
332
+ alias :create :insert_sql
333
+
334
+ def select sql, name = nil
335
+ puts sql
336
+ result = []
337
+ response = @connection.select(sql, nil, true)
338
+ collection_name = get_collection_column_and_name(sql)
339
+ columns = columns_definition(collection_name)
340
+
341
+ response[:items].each do |item|
342
+ item.each do |id, attrs|
343
+ ritem = {}
344
+ ritem['id'] = id unless id == 'Domain' && attrs['Count'] # unless count(*) result
345
+ attrs.each {|k, vs|
346
+ column = columns[k]
347
+ if column.present?
348
+ ritem[column.name] = column.unquote_number(vs.first)
349
+ else
350
+ ritem[k] = vs.first
351
+ end
352
+ }
353
+ puts ritem.inspect
354
+ result << ritem
355
+ end
356
+ end
357
+ # puts "Box usage: #{response[:box_usage].to_f}"
358
+ result
359
+ end
360
+
361
+ # Executes the update statement and returns the number of rows affected.
362
+ def update_sql(sql, name = nil)
363
+ begin
364
+ execute(sql, name)
365
+ 1
366
+ rescue Aws::AwsError => ex
367
+ #if not conflict state raise
368
+ raise if ex.http_code != '409'
369
+ 0
370
+ end
371
+ end
372
+
373
+ # Executes the delete statement and returns the number of rows affected.
374
+ def delete_sql(sql, name = nil)
375
+ update_sql(sql, name)
376
+ end
377
+
378
+ def create_domain domain_name
379
+ @connection.create_domain domain_name
380
+ end
381
+
382
+ def delete_domain domain_name
383
+ @connection.delete_domain domain_name
384
+ end
385
+
386
+ private
387
+
388
+ def generate_id
389
+ UUIDTools::UUID.timestamp_create().to_s
390
+ end
391
+
392
+ def get_id hash, delete_id = false
393
+ if delete_id
394
+ hash.delete(:id) || hash.delete('id')
395
+ else
396
+ hash[:id] || hash['id']
397
+ end
398
+ end
399
+
400
+ def get_collection_column_and_name sql
401
+ if sql.match /(#{@@ccn.values.join("|")})\s*=\s*'(.*?)'/
402
+ $2
403
+ else
404
+ raise PreparedStatementInvalid, "collection column '#{@@ccn.values.join(" or ")}' not found in the WHERE section in query"
405
+ end
406
+ end
407
+
408
+ end
409
+ end
410
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveRecordSimpledbAdapter
2
+ class Railtie < ::Rails::Railtie
3
+ rake_tasks do
4
+ load 'tasks/simpledb.rake'
5
+ end
6
+
7
+ ActiveSupport.on_load(:active_record) do
8
+ require 'active_record/connection_adapters/simpledb_adapter'
9
+ require 'arel/visitors/simpledb'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,121 @@
1
+ require 'arel/visitors/to_sql'
2
+ module Arel
3
+ module Visitors
4
+ class SimpleDB < Arel::Visitors::ToSql
5
+ private
6
+
7
+ def hash_value_quote value, column = nil
8
+ @connection.hash_value_quote value, column
9
+ end
10
+
11
+ def visit_Arel_Nodes_DeleteStatement o
12
+ {
13
+ :action => :delete,
14
+ :wheres => merge_to_hash(o.wheres)
15
+ }
16
+ end
17
+
18
+ def visit_Arel_Nodes_UpdateStatement o
19
+ {
20
+ :action => :update,
21
+ :attrs => merge_to_hash(o.values),
22
+ :wheres => merge_to_hash(o.wheres)
23
+ }
24
+ end
25
+
26
+ def visit_Arel_Nodes_InsertStatement o
27
+ collection_name = visit(o.relation)
28
+ ccn = @connection.collection_column_name(collection_name)
29
+
30
+ {
31
+ :action => :insert,
32
+ :attrs => visit(o.values).merge(ccn => collection_name)
33
+ }
34
+ end
35
+
36
+ def visit_Arel_Nodes_SelectStatement o
37
+ o.orders.each do |order|
38
+ attr = visit(order).to_s.gsub(/ASC|DESC/, '').strip
39
+ # The sort attribute must be present in predicates
40
+ o.cores.first.wheres << SqlLiteral.new("#{attr} IS NOT NULL")
41
+ end
42
+ collection = o.cores.first.froms.name
43
+ o.cores.first.wheres << SqlLiteral.new("#{@connection.collection_column_name(collection)} = #{quote(collection)}")
44
+ o.cores.first.froms = Table.new @connection.domain_name
45
+ super
46
+ end
47
+
48
+ def visit_Arel_Nodes_Values o
49
+ result = {}
50
+ o.expressions.zip(o.columns).map {|value, column|
51
+ result[column.column.db_column_name] = hash_value_quote(value, column.column)
52
+ }
53
+ result
54
+ end
55
+
56
+ def visit_Arel_Nodes_Grouping o
57
+ visit(o.expr)
58
+ end
59
+
60
+ def visit_Arel_Nodes_And o
61
+ visit(o.left).merge(visit(o.right)).tap{|m| m.override_to_s super}
62
+ end
63
+
64
+ def visit_Arel_Nodes_Assignment o
65
+ right = o.right ? hash_value_quote(o.right, o.left.column) : nil
66
+ {visit(o.left) => right}
67
+ end
68
+
69
+ def visit_Arel_Nodes_Equality o
70
+ right = o.right ? hash_value_quote(o.right, o.left.column) : nil
71
+ {visit(o.left) => right}.tap { |m|
72
+ m.override_to_s "#{visit o.left} = #{quote(o.right, o.left.column)}"
73
+ }
74
+ end
75
+
76
+ def visit_Arel_Nodes_NotEqual o
77
+ "#{visit o.left} != #{o.right.nil? ? @connection.nil_representation : visit(o.right)}"
78
+ end
79
+
80
+ def visit_Arel_Attributes_Attribute o
81
+ # Do not use table. prefix for attribute names
82
+ @last_column = o.column
83
+ quote_column_name o.column.db_column_name
84
+ end
85
+ alias :visit_Arel_Attributes_Integer :visit_Arel_Attributes_Attribute
86
+ alias :visit_Arel_Attributes_Float :visit_Arel_Attributes_Attribute
87
+ alias :visit_Arel_Attributes_Decimal :visit_Arel_Attributes_Attribute
88
+ alias :visit_Arel_Attributes_String :visit_Arel_Attributes_Attribute
89
+ alias :visit_Arel_Attributes_Time :visit_Arel_Attributes_Attribute
90
+ alias :visit_Arel_Attributes_Boolean :visit_Arel_Attributes_Attribute
91
+
92
+ def visit_Arel_Nodes_SqlLiteral o
93
+ # Strip table name from table.column -like literals
94
+ result = o.to_s.gsub(/\w+\.([\w*]+)/, '\1')
95
+
96
+ if result.match /(\w+)\s*=\s*'(.*?)'/
97
+ # transform 'a = b' to {'a' => 'b'}
98
+ {$1 => $2}.tap {|m| m.override_to_s result}
99
+ else
100
+ result
101
+ end
102
+ end
103
+ alias :visit_Arel_SqlLiteral :visit_Arel_Nodes_SqlLiteral
104
+
105
+
106
+ def merge_to_hash a
107
+ a.map{|x| visit(x)}.inject({}, &:merge)
108
+ end
109
+ end
110
+
111
+ VISITORS['simpledb'] = Arel::Visitors::SimpleDB
112
+ end
113
+ end
114
+
115
+ class Object
116
+ def override_to_s s
117
+ @override_to_s = s
118
+ #noinspection RubyResolve
119
+ def self.to_s; @override_to_s; end
120
+ end
121
+ end
@@ -0,0 +1,17 @@
1
+ Rake::Task['db:create'].clear
2
+ Rake::Task['db:seed'].clear
3
+ namespace :db do
4
+ task :create => :load_config do
5
+ create_domain(ActiveRecord::Base.configurations[Rails.env])
6
+ end
7
+
8
+ task :seed => :environment do
9
+ seed_file = File.join(Rails.root, 'db', 'seeds.rb')
10
+ load(seed_file) if File.exist?(seed_file)
11
+ end
12
+
13
+ def create_domain(config)
14
+ ActiveRecord::Base.establish_connection(config)
15
+ ActiveRecord::Base.connection.create_domain(config['domain_name'])
16
+ end
17
+ end
@@ -0,0 +1,141 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "model with active_record_simple_db" do
4
+
5
+ before :each do
6
+ ActiveRecord::Base.establish_connection($config)
7
+ ActiveRecord::Base.connection.create_domain($config[:domain_name])
8
+ end
9
+
10
+ after :each do
11
+ ActiveRecord::Base.connection.delete_domain($config[:domain_name])
12
+ end
13
+ #test .new
14
+ it "should be without id, created_at and updated_at when model is new" do
15
+ p = Person.new
16
+ p.id.should be_nil
17
+ p.created_at.should be_nil
18
+ p.updated_at.should be_nil
19
+ end
20
+
21
+ #test .create and .save
22
+ it "should be with id, created_at and updated_at when saved when new model was saved or created immediately" do
23
+ p1 = Person.new
24
+ p1.save
25
+ p1.id.should_not be_nil
26
+ p1.created_at.should_not be_nil
27
+ p1.updated_at.should_not be_nil
28
+ p2 = Person.create!
29
+ p2.id.should_not be_nil
30
+ p2.created_at.should_not be_nil
31
+ p2.updated_at.should_not be_nil
32
+ end
33
+
34
+ #test correct saving/loading properties
35
+ it "should correct save and load all properties" do
36
+ p = Person.create!(Person.valid_params)
37
+
38
+ r = Person.find p.id
39
+ Person.valid_params.each do |key, value|
40
+ r.try(key).should == value
41
+ end
42
+ end
43
+
44
+ #test validates instructions
45
+ it "should be correct work with validates instruction" do
46
+ class PersonWithValidates < Person
47
+ validates_presence_of :login
48
+ validates_uniqueness_of :login
49
+ end
50
+
51
+ lambda {
52
+ PersonWithValidates.create!
53
+ }.should raise_error(ActiveRecord::RecordInvalid, "Validation failed: Login can't be blank")
54
+
55
+ PersonWithValidates.create!(Person.valid_params)
56
+
57
+ lambda {
58
+ PersonWithValidates.create!(Person.valid_params)
59
+ }.should raise_error(ActiveRecord::RecordInvalid, "Validation failed: Login has already been taken")
60
+ end
61
+
62
+ #test .create
63
+ it "should be different ids for different model instances" do
64
+ p1 = Person.create!
65
+ p2 = Person.create!
66
+ p1.id.should_not == p2.id
67
+ end
68
+
69
+ #test .find
70
+ it "should be found entry by id" do
71
+ p1 = Person.create!
72
+ p2 = Person.find p1.id
73
+ p1.should == p2
74
+ end
75
+
76
+ #test .all
77
+ it "should return collection of models when invoke .all" do
78
+ p = Person.new
79
+ p.save
80
+ r = Person.all
81
+ r.should_not be_nil
82
+ r.should_not be_empty
83
+ end
84
+
85
+ #test .first
86
+ it "should return valid entry when invoke .first" do
87
+ p = Person.new
88
+ p.save
89
+ p1 = Person.first
90
+ p1.should_not be_nil
91
+ end
92
+
93
+ #test .first
94
+ it "should return valid entry when invoke .last" do
95
+ p = Person.new
96
+ p.save
97
+ p1 = Person.last
98
+ p1.should_not be_nil
99
+ end
100
+
101
+ #test .touch
102
+ it "should update updated_at property when invoke .touch" do
103
+ p1 = Person.new
104
+ p1.save
105
+ p2 = Person.find p1.id
106
+ p1.touch
107
+ p1.updated_at.should > p2.updated_at
108
+ end
109
+
110
+ #test .destroy
111
+ it "should be destroy entry when invoke destroy" do
112
+ p1 = Person.new
113
+ p1.save
114
+ id = p1.id
115
+ p1.destroy
116
+ p2 = Person.find_by_id id
117
+ p2.should be_nil
118
+ end
119
+
120
+ #test optimistic locking
121
+ it "should be throw exception when invoke destroy twice for same object" do
122
+ p1 = Person.new
123
+ p1.save
124
+ p2 = Person.find p1.id
125
+ p1.year = 2008
126
+ p1.save
127
+ lambda {p2.destroy}.should raise_error(ActiveRecord::StaleObjectError)
128
+ end
129
+
130
+ #test where selection
131
+ it "should be correct work with where statment with equalities" do
132
+ p1 = Person.new
133
+ p1.year = 2008
134
+ p1.save
135
+ p2 = Person.new
136
+ p2.year = 2010
137
+ p2.save
138
+ p = Person.where(:year => 2010).all
139
+ p.should_not be_empty
140
+ end
141
+ end
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ # Set up gems listed in the Gemfile.
3
+ gemfile = File.expand_path('../../Gemfile', __FILE__)
4
+ begin
5
+ ENV['BUNDLE_GEMFILE'] = gemfile
6
+ require 'bundler'
7
+ Bundler.setup
8
+ rescue Bundler::GemNotFound => e
9
+ STDERR.puts e.message
10
+ STDERR.puts "Try running `bundle install`."
11
+ exit!
12
+ end if File.exist?(gemfile)
13
+
14
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
15
+ require 'rspec'
16
+ require 'aws'
17
+ require 'active_record'
18
+ require 'active_record/connection_adapters/simpledb_adapter'
19
+ require 'arel/visitors/simpledb'
20
+
21
+ CONNECTION_PARAMS = {
22
+ :adapter => 'simpledb',
23
+ :access_key_id => "some_key",
24
+ :secret_access_key => "some_secret",
25
+ :domain_name => "test_domain",
26
+ :host => 'localhost',
27
+ :port => '8080',
28
+ :protocol => 'http'
29
+ }
30
+
31
+ $config = CONNECTION_PARAMS
32
+
33
+ #define stub model
34
+ class Person < ActiveRecord::Base
35
+ establish_connection $config
36
+
37
+ columns_definition do |t|
38
+ t.string :login
39
+ t.integer :year, :limit => 4
40
+ t.boolean :active
41
+ t.string :state
42
+ t.float :price
43
+ t.integer :lock_version
44
+
45
+ t.timestamps
46
+ end
47
+
48
+ def self.valid_params
49
+ {
50
+ :login => "john",
51
+ :year => 2010,
52
+ :active => true,
53
+ :price => 10.04,
54
+ :state => 'paid'
55
+ }
56
+ end
57
+ end
58
+
59
+
metadata ADDED
@@ -0,0 +1,246 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-simpledb-adapter
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Ilia Ablamonov
14
+ - Alex Gorkunov
15
+ - Cloud Castle Inc.
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2011-01-14 00:00:00 +03:00
21
+ default_executable:
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
24
+ version_requirements: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 2
32
+ - 3
33
+ - 0
34
+ version: 2.3.0
35
+ requirement: *id001
36
+ prerelease: false
37
+ type: :development
38
+ name: rspec
39
+ - !ruby/object:Gem::Dependency
40
+ version_requirements: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 23
46
+ segments:
47
+ - 1
48
+ - 0
49
+ - 0
50
+ version: 1.0.0
51
+ requirement: *id002
52
+ prerelease: false
53
+ type: :development
54
+ name: bundler
55
+ - !ruby/object:Gem::Dependency
56
+ version_requirements: &id003 !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ hash: 7
62
+ segments:
63
+ - 1
64
+ - 5
65
+ - 2
66
+ version: 1.5.2
67
+ requirement: *id003
68
+ prerelease: false
69
+ type: :development
70
+ name: jeweler
71
+ - !ruby/object:Gem::Dependency
72
+ version_requirements: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ hash: 3
78
+ segments:
79
+ - 0
80
+ version: "0"
81
+ requirement: *id004
82
+ prerelease: false
83
+ type: :development
84
+ name: rcov
85
+ - !ruby/object:Gem::Dependency
86
+ version_requirements: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ requirement: *id005
96
+ prerelease: false
97
+ type: :development
98
+ name: aws
99
+ - !ruby/object:Gem::Dependency
100
+ version_requirements: &id006 !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ hash: 3
106
+ segments:
107
+ - 0
108
+ version: "0"
109
+ requirement: *id006
110
+ prerelease: false
111
+ type: :development
112
+ name: activerecord
113
+ - !ruby/object:Gem::Dependency
114
+ version_requirements: &id007 !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ hash: 3
120
+ segments:
121
+ - 0
122
+ version: "0"
123
+ requirement: *id007
124
+ prerelease: false
125
+ type: :development
126
+ name: activesupport
127
+ - !ruby/object:Gem::Dependency
128
+ version_requirements: &id008 !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ hash: 3
134
+ segments:
135
+ - 2
136
+ - 3
137
+ - 0
138
+ version: 2.3.0
139
+ requirement: *id008
140
+ prerelease: false
141
+ type: :runtime
142
+ name: aws
143
+ - !ruby/object:Gem::Dependency
144
+ version_requirements: &id009 !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ hash: 1
150
+ segments:
151
+ - 3
152
+ - 0
153
+ - 3
154
+ version: 3.0.3
155
+ requirement: *id009
156
+ prerelease: false
157
+ type: :runtime
158
+ name: activerecord
159
+ - !ruby/object:Gem::Dependency
160
+ version_requirements: &id010 !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ~>
164
+ - !ruby/object:Gem::Version
165
+ hash: 1
166
+ segments:
167
+ - 3
168
+ - 0
169
+ - 3
170
+ version: 3.0.3
171
+ requirement: *id010
172
+ prerelease: false
173
+ type: :runtime
174
+ name: activesupport
175
+ - !ruby/object:Gem::Dependency
176
+ version_requirements: &id011 !ruby/object:Gem::Requirement
177
+ none: false
178
+ requirements:
179
+ - - ~>
180
+ - !ruby/object:Gem::Version
181
+ hash: 9
182
+ segments:
183
+ - 2
184
+ - 1
185
+ - 1
186
+ version: 2.1.1
187
+ requirement: *id011
188
+ prerelease: false
189
+ type: :runtime
190
+ name: uuidtools
191
+ description:
192
+ email: ilia@flamefork.ru
193
+ executables: []
194
+
195
+ extensions: []
196
+
197
+ extra_rdoc_files:
198
+ - LICENSE.txt
199
+ - README.md
200
+ files:
201
+ - LICENSE.txt
202
+ - README.md
203
+ - activerecord-simpledb-adapter.gemspec
204
+ - lib/active_record/connection_adapters/simpledb_adapter.rb
205
+ - lib/activerecord-simpledb-adapter.rb
206
+ - lib/arel/visitors/simpledb.rb
207
+ - lib/tasks/simpledb.rake
208
+ - spec/activerecord-simpledb-adapter_spec.rb
209
+ - spec/spec_helper.rb
210
+ has_rdoc: true
211
+ homepage: http://github.com/cloudcastle/activerecord-simpledb-adapter
212
+ licenses:
213
+ - MIT
214
+ post_install_message:
215
+ rdoc_options: []
216
+
217
+ require_paths:
218
+ - lib
219
+ required_ruby_version: !ruby/object:Gem::Requirement
220
+ none: false
221
+ requirements:
222
+ - - ">="
223
+ - !ruby/object:Gem::Version
224
+ hash: 3
225
+ segments:
226
+ - 0
227
+ version: "0"
228
+ required_rubygems_version: !ruby/object:Gem::Requirement
229
+ none: false
230
+ requirements:
231
+ - - ">="
232
+ - !ruby/object:Gem::Version
233
+ hash: 3
234
+ segments:
235
+ - 0
236
+ version: "0"
237
+ requirements: []
238
+
239
+ rubyforge_project:
240
+ rubygems_version: 1.3.7
241
+ signing_key:
242
+ specification_version: 3
243
+ summary: ActiveRecord SimpleDB adapter
244
+ test_files:
245
+ - spec/activerecord-simpledb-adapter_spec.rb
246
+ - spec/spec_helper.rb