ahoward-helene 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +274 -0
- data/helene.gemspec +26 -0
- data/lib/helene.rb +113 -0
- data/lib/helene/attempt.rb +46 -0
- data/lib/helene/aws.rb +50 -0
- data/lib/helene/config.rb +147 -0
- data/lib/helene/content_type.rb +15 -0
- data/lib/helene/content_type.yml +661 -0
- data/lib/helene/error.rb +12 -0
- data/lib/helene/logging.rb +55 -0
- data/lib/helene/objectpool.rb +220 -0
- data/lib/helene/rails.rb +21 -0
- data/lib/helene/rightscale/acf/right_acf_interface.rb +379 -0
- data/lib/helene/rightscale/awsbase/benchmark_fix.rb +39 -0
- data/lib/helene/rightscale/awsbase/right_awsbase.rb +803 -0
- data/lib/helene/rightscale/awsbase/support.rb +111 -0
- data/lib/helene/rightscale/ec2/right_ec2.rb +1737 -0
- data/lib/helene/rightscale/net_fix.rb +160 -0
- data/lib/helene/rightscale/right_aws.rb +71 -0
- data/lib/helene/rightscale/right_http_connection.rb +507 -0
- data/lib/helene/rightscale/s3/right_s3.rb +1094 -0
- data/lib/helene/rightscale/s3/right_s3_interface.rb +1180 -0
- data/lib/helene/rightscale/sdb/active_sdb.rb +930 -0
- data/lib/helene/rightscale/sdb/right_sdb_interface.rb +696 -0
- data/lib/helene/rightscale/sqs/right_sqs.rb +388 -0
- data/lib/helene/rightscale/sqs/right_sqs_gen2.rb +286 -0
- data/lib/helene/rightscale/sqs/right_sqs_gen2_interface.rb +444 -0
- data/lib/helene/rightscale/sqs/right_sqs_interface.rb +596 -0
- data/lib/helene/s3.rb +34 -0
- data/lib/helene/s3/bucket.rb +379 -0
- data/lib/helene/s3/grantee.rb +134 -0
- data/lib/helene/s3/key.rb +162 -0
- data/lib/helene/s3/owner.rb +16 -0
- data/lib/helene/sdb.rb +9 -0
- data/lib/helene/sdb/base.rb +1204 -0
- data/lib/helene/sdb/base/associations.rb +481 -0
- data/lib/helene/sdb/base/attributes.rb +90 -0
- data/lib/helene/sdb/base/connection.rb +20 -0
- data/lib/helene/sdb/base/error.rb +20 -0
- data/lib/helene/sdb/base/hooks.rb +82 -0
- data/lib/helene/sdb/base/literal.rb +52 -0
- data/lib/helene/sdb/base/logging.rb +23 -0
- data/lib/helene/sdb/base/transactions.rb +53 -0
- data/lib/helene/sdb/base/type.rb +137 -0
- data/lib/helene/sdb/base/types.rb +123 -0
- data/lib/helene/sdb/base/validations.rb +256 -0
- data/lib/helene/sdb/cast.rb +114 -0
- data/lib/helene/sdb/connection.rb +36 -0
- data/lib/helene/sdb/error.rb +5 -0
- data/lib/helene/sdb/interface.rb +412 -0
- data/lib/helene/sdb/sentinel.rb +15 -0
- data/lib/helene/sleepcycle.rb +29 -0
- data/lib/helene/superhash.rb +297 -0
- data/lib/helene/util.rb +132 -0
- data/test/auth.rb +31 -0
- data/test/helper.rb +98 -0
- data/test/integration/begin.rb +0 -0
- data/test/integration/ensure.rb +8 -0
- data/test/integration/s3/bucket.rb +106 -0
- data/test/integration/sdb/associations.rb +45 -0
- data/test/integration/sdb/creating.rb +13 -0
- data/test/integration/sdb/emptiness.rb +56 -0
- data/test/integration/sdb/hooks.rb +19 -0
- data/test/integration/sdb/limits.rb +27 -0
- data/test/integration/sdb/saving.rb +21 -0
- data/test/integration/sdb/selecting.rb +39 -0
- data/test/integration/sdb/types.rb +31 -0
- data/test/integration/sdb/validations.rb +60 -0
- data/test/integration/setup.rb +27 -0
- data/test/integration/teardown.rb +21 -0
- data/test/loader.rb +39 -0
- metadata +139 -0
@@ -0,0 +1,162 @@
|
|
1
|
+
module Helene
|
2
|
+
module S3
|
3
|
+
class Key
|
4
|
+
def url(*args)
|
5
|
+
options = args.extract_options!.to_options!
|
6
|
+
options.to_options!
|
7
|
+
expires = options.delete(:expires) || 24.hours
|
8
|
+
headers = options.delete(:headers) || {}
|
9
|
+
case args.shift.to_s
|
10
|
+
when '', 'get'
|
11
|
+
bucket.interface.get_link(bucket, name.to_s, expires, headers)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method 'url_for', 'url'
|
16
|
+
attr_reader :bucket, :name, :last_modified, :e_tag, :size, :storage_class, :owner
|
17
|
+
attr_accessor :headers, :meta_headers
|
18
|
+
attr_writer :data
|
19
|
+
|
20
|
+
def self.split_meta(headers) #:nodoc:
|
21
|
+
hash = headers.dup
|
22
|
+
meta = {}
|
23
|
+
hash.each do |key, value|
|
24
|
+
if key[%r/^x-amz-meta-/]
|
25
|
+
meta[key.gsub('x-amz-meta-', '')] = value
|
26
|
+
hash.delete(key)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
[hash, meta]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.add_meta_prefix(meta_headers, prefix='x-amz-meta-')
|
33
|
+
meta = {}
|
34
|
+
meta_headers.each do |meta_header, value|
|
35
|
+
if meta_header[/#{prefix}/]
|
36
|
+
meta[meta_header] = value
|
37
|
+
else
|
38
|
+
meta["x-amz-meta-#{meta_header}"] = value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
meta
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.create(bucket, name, data=nil, meta_headers={})
|
45
|
+
new(bucket, name, data, {}, meta_headers)
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(bucket, name, data=nil, headers={}, meta_headers={},
|
49
|
+
last_modified=nil, e_tag=nil, size=nil, storage_class=nil, owner=nil)
|
50
|
+
raise 'Bucket must be a Bucket instance.' unless bucket.is_a?(Bucket)
|
51
|
+
@bucket = bucket
|
52
|
+
@name = name
|
53
|
+
@data = data
|
54
|
+
@e_tag = e_tag
|
55
|
+
@size = size.to_i
|
56
|
+
@storage_class = storage_class
|
57
|
+
@owner = owner
|
58
|
+
@last_modified = last_modified
|
59
|
+
if @last_modified && !@last_modified.is_a?(Time)
|
60
|
+
@last_modified = Time.parse(@last_modified)
|
61
|
+
end
|
62
|
+
@headers, @meta_headers = self.class.split_meta(headers)
|
63
|
+
@meta_headers.merge!(meta_headers)
|
64
|
+
end
|
65
|
+
|
66
|
+
def to_s
|
67
|
+
@name.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def full_name(separator='/')
|
71
|
+
"#{@bucket.to_s}#{separator}#{@name}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def public_link
|
75
|
+
params = @bucket.interface.params
|
76
|
+
"#{params[:protocol]}://#{params[:server]}:#{params[:port]}/#{full_name('/')}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def data
|
80
|
+
get if !@data and exists?
|
81
|
+
@data
|
82
|
+
end
|
83
|
+
|
84
|
+
def get(headers={})
|
85
|
+
response = @bucket.interface.get(@bucket.name, @name, headers)
|
86
|
+
@data = response[:object]
|
87
|
+
@headers, @meta_headers = self.class.split_meta(response[:headers])
|
88
|
+
refresh(false)
|
89
|
+
@data
|
90
|
+
end
|
91
|
+
|
92
|
+
def put(data=nil, perms=nil, headers={})
|
93
|
+
headers['x-amz-acl'] = perms if perms
|
94
|
+
@data = data || @data
|
95
|
+
meta = self.class.add_meta_prefix(@meta_headers)
|
96
|
+
@bucket.interface.put(@bucket.name, @name, @data, meta.merge(headers))
|
97
|
+
end
|
98
|
+
|
99
|
+
def rename(new_name)
|
100
|
+
@bucket.interface.rename(@bucket.name, @name, new_name)
|
101
|
+
@name = new_name
|
102
|
+
end
|
103
|
+
|
104
|
+
def copy(new_key_or_name)
|
105
|
+
new_key_or_name = Key.create(@bucket, new_key_or_name.to_s) unless new_key_or_name.is_a?(Key)
|
106
|
+
@bucket.interface.copy(@bucket.name, @name, new_key_or_name.bucket.name, new_key_or_name.name)
|
107
|
+
new_key_or_name
|
108
|
+
end
|
109
|
+
|
110
|
+
def move(new_key_or_name)
|
111
|
+
new_key_or_name = Key.create(@bucket, new_key_or_name.to_s) unless new_key_or_name.is_a?(Key)
|
112
|
+
@bucket.interface.move(@bucket.name, @name, new_key_or_name.bucket.name, new_key_or_name.name)
|
113
|
+
new_key_or_name
|
114
|
+
end
|
115
|
+
|
116
|
+
def refresh(head=true)
|
117
|
+
new_key = @bucket.find_or_create_key_by_absolute_path(name)
|
118
|
+
@last_modified = new_key.last_modified
|
119
|
+
@e_tag = new_key.e_tag
|
120
|
+
@size = new_key.size
|
121
|
+
@storage_class = new_key.storage_class
|
122
|
+
@owner = new_key.owner
|
123
|
+
if @last_modified
|
124
|
+
self.head
|
125
|
+
true
|
126
|
+
else
|
127
|
+
@headers = @meta_headers = {}
|
128
|
+
false
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def head
|
133
|
+
@headers, @meta_headers = self.class.split_meta(@bucket.interface.head(@bucket, @name))
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
def reload_meta
|
138
|
+
@meta_headers = self.class.split_meta(@bucket.interface.head(@bucket, @name)).last
|
139
|
+
end
|
140
|
+
|
141
|
+
def save_meta(meta_headers)
|
142
|
+
meta = self.class.add_meta_prefix(meta_headers)
|
143
|
+
@bucket.interface.copy(@bucket.name, @name, @bucket.name, @name, :replace, meta)
|
144
|
+
@meta_headers = self.class.split_meta(meta)[1]
|
145
|
+
end
|
146
|
+
|
147
|
+
def exists?
|
148
|
+
@bucket.find_or_create_key_by_absolute_path(name).last_modified ? true : false
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
def delete
|
153
|
+
raise 'Key name must be specified.' if @name.blank?
|
154
|
+
@bucket.interface.delete(@bucket, @name)
|
155
|
+
end
|
156
|
+
|
157
|
+
def grantees
|
158
|
+
Grantee::grantees(self)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
data/lib/helene/sdb.rb
ADDED
@@ -0,0 +1,1204 @@
|
|
1
|
+
|
2
|
+
module Helene
|
3
|
+
module Sdb
|
4
|
+
class Base
|
5
|
+
load 'helene/sdb/base/error.rb'
|
6
|
+
load 'helene/sdb/base/logging.rb'
|
7
|
+
load 'helene/sdb/base/connection.rb'
|
8
|
+
load 'helene/sdb/base/type.rb'
|
9
|
+
load 'helene/sdb/base/types.rb'
|
10
|
+
load 'helene/sdb/base/literal.rb'
|
11
|
+
load 'helene/sdb/base/validations.rb'
|
12
|
+
load 'helene/sdb/base/attributes.rb'
|
13
|
+
load 'helene/sdb/base/associations.rb'
|
14
|
+
load 'helene/sdb/base/transactions.rb'
|
15
|
+
load 'helene/sdb/base/hooks.rb'
|
16
|
+
|
17
|
+
include Attempt
|
18
|
+
|
19
|
+
class << Base
|
20
|
+
# track children
|
21
|
+
#
|
22
|
+
def subclasses
|
23
|
+
@subclasses ||= Array.fields
|
24
|
+
end
|
25
|
+
|
26
|
+
def inherited(subclass)
|
27
|
+
super
|
28
|
+
ensure
|
29
|
+
# TODO - use class_inherited_array - etc
|
30
|
+
subclass.domain = domain unless self==Base
|
31
|
+
subclass.perform_virtual_consistency = perform_virtual_consistency
|
32
|
+
subclass.hooks = hooks.dup
|
33
|
+
key = subclass.name.blank? ? subclass.inspect : subclass.name
|
34
|
+
subclasses[key] = subclass
|
35
|
+
end
|
36
|
+
|
37
|
+
def superclasses
|
38
|
+
@superclasses ||= ancestors.select{|ancestor| ancestor <= Base and ancestor > self}
|
39
|
+
end
|
40
|
+
|
41
|
+
def superclass
|
42
|
+
@superclass ||= superclasses.first
|
43
|
+
end
|
44
|
+
|
45
|
+
# virtual consistency
|
46
|
+
#
|
47
|
+
def perform_virtual_consistency(*value)
|
48
|
+
@perform_virtual_consistency = true unless defined?(@perform_virtual_consistency)
|
49
|
+
@perform_virtual_consistency = !!value.first unless value.empty?
|
50
|
+
@perform_virtual_consistency
|
51
|
+
end
|
52
|
+
|
53
|
+
def perform_virtual_consistency?()
|
54
|
+
perform_virtual_consistency()
|
55
|
+
end
|
56
|
+
|
57
|
+
def perform_virtual_consistency=(value)
|
58
|
+
perform_virtual_consistency(value)
|
59
|
+
end
|
60
|
+
|
61
|
+
def perform_virtual_consistency!()
|
62
|
+
perform_virtual_consistency(true)
|
63
|
+
end
|
64
|
+
|
65
|
+
# domain/migration methods
|
66
|
+
#
|
67
|
+
def domains
|
68
|
+
connection.list_domains[:domains]
|
69
|
+
end
|
70
|
+
|
71
|
+
def domain(*value)
|
72
|
+
if value.empty?
|
73
|
+
@domain ||= name.tableize.gsub(%r|/|, '--')
|
74
|
+
else
|
75
|
+
@domain = value.to_s
|
76
|
+
end
|
77
|
+
while @domain.size < 3
|
78
|
+
@domain = "#{ @domain }_"
|
79
|
+
end
|
80
|
+
@domain
|
81
|
+
end
|
82
|
+
|
83
|
+
def domain=(value)
|
84
|
+
domain(value)
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_domain_name(value)
|
88
|
+
domain(value)
|
89
|
+
end
|
90
|
+
|
91
|
+
def create_domain(*domains)
|
92
|
+
domains.flatten!
|
93
|
+
domains.compact!
|
94
|
+
domains.push(domain) if domains.blank?
|
95
|
+
domains.each do |domain|
|
96
|
+
connection.create_domain(domain)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def delete_domain(*domains)
|
101
|
+
domains.flatten!
|
102
|
+
domains.compact!
|
103
|
+
domains.push(domain) if domains.blank?
|
104
|
+
domains.each do |domain|
|
105
|
+
connection.delete_domain(domain)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def delete_all_domains!(*domains)
|
110
|
+
domains.flatten!
|
111
|
+
domains.compact!
|
112
|
+
domains.push(self.domains) if domains.blank?
|
113
|
+
domains.each do |domain|
|
114
|
+
connection.delete_domain(domain)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def delete_all
|
119
|
+
delete_domain
|
120
|
+
create_domain
|
121
|
+
end
|
122
|
+
|
123
|
+
def migrate
|
124
|
+
create_domain
|
125
|
+
end
|
126
|
+
|
127
|
+
def migrate!
|
128
|
+
delete_domain rescue nil
|
129
|
+
create_domain
|
130
|
+
end
|
131
|
+
|
132
|
+
def migration
|
133
|
+
m = Module.new{ }
|
134
|
+
base = self
|
135
|
+
sc =
|
136
|
+
class << m; self; end
|
137
|
+
sc.module_eval do
|
138
|
+
define_method(:up){ base.migrate }
|
139
|
+
define_method(:down){ base.delete_domain }
|
140
|
+
end
|
141
|
+
m
|
142
|
+
end
|
143
|
+
|
144
|
+
# id methods
|
145
|
+
#
|
146
|
+
def generate_uuid
|
147
|
+
Util.uuid
|
148
|
+
end
|
149
|
+
|
150
|
+
def generate_id
|
151
|
+
generate_uuid
|
152
|
+
end
|
153
|
+
|
154
|
+
def singular
|
155
|
+
name.singularize.downcase
|
156
|
+
end
|
157
|
+
|
158
|
+
def plural
|
159
|
+
name.pluralize.downcase
|
160
|
+
end
|
161
|
+
|
162
|
+
# create
|
163
|
+
#
|
164
|
+
def create(attributes={})
|
165
|
+
record = new(attributes)
|
166
|
+
record.before_create
|
167
|
+
record.save
|
168
|
+
record.after_create
|
169
|
+
record
|
170
|
+
end
|
171
|
+
|
172
|
+
def create!(attributes={})
|
173
|
+
record = new(attributes)
|
174
|
+
record.before_create
|
175
|
+
record.save!
|
176
|
+
record.after_create
|
177
|
+
record
|
178
|
+
end
|
179
|
+
|
180
|
+
# batch create/update
|
181
|
+
#
|
182
|
+
def save_without_validation(*records)
|
183
|
+
prepare_for_update
|
184
|
+
sdb_attributes = ruby_to_sdb
|
185
|
+
connection.put_attributes(domain, id, sdb_attributes, :replace)
|
186
|
+
virtually_load(sdb_attributes)
|
187
|
+
mark_as_old!
|
188
|
+
errors.empty?
|
189
|
+
end
|
190
|
+
|
191
|
+
def batch_put(*args)
|
192
|
+
args.flatten!
|
193
|
+
options = args.extract_options!.to_options!
|
194
|
+
replace = options[:replace]
|
195
|
+
records = args.compact
|
196
|
+
to_put = []
|
197
|
+
|
198
|
+
records.each do |record|
|
199
|
+
record.prepare_for_update
|
200
|
+
item_name = record.id
|
201
|
+
sdb_attributes = record.ruby_to_sdb
|
202
|
+
to_put.push [item_name, sdb_attributes]
|
203
|
+
end
|
204
|
+
|
205
|
+
results =
|
206
|
+
=begin
|
207
|
+
to_put.threadify(2, :each_slice, 25) do |slice|
|
208
|
+
items = Hash[*slice.to_a.flatten]
|
209
|
+
connection.batch_put_attributes(domain, items, options.update(:replace => replace))
|
210
|
+
end.flatten
|
211
|
+
=end
|
212
|
+
results = []
|
213
|
+
to_put.each_slice(25) do |slice|
|
214
|
+
items = Hash[*slice.to_a.flatten]
|
215
|
+
results << connection.batch_put_attributes(domain, items, options.update(:replace => replace))
|
216
|
+
end
|
217
|
+
results.flatten!
|
218
|
+
|
219
|
+
records.each do |record|
|
220
|
+
record.virtually_load(record.ruby_to_sdb)
|
221
|
+
record.mark_as_old!
|
222
|
+
end
|
223
|
+
|
224
|
+
records
|
225
|
+
end
|
226
|
+
|
227
|
+
def batch_save(*args)
|
228
|
+
args.flatten!
|
229
|
+
options = args.extract_options!.to_options!
|
230
|
+
options[:replace] = true
|
231
|
+
args.push options
|
232
|
+
batch_put(*args)
|
233
|
+
end
|
234
|
+
|
235
|
+
def batch_create(n, options = {}, &block)
|
236
|
+
records = nil
|
237
|
+
Integer(n).times do |i|
|
238
|
+
record = new(options)
|
239
|
+
if block
|
240
|
+
block.arity == 1 ? block.call(record) : block.call(record, i)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
batch_put(record)
|
244
|
+
end
|
245
|
+
|
246
|
+
def batch_delete(*args)
|
247
|
+
args.flatten!
|
248
|
+
options = args.extract_options!.to_options!
|
249
|
+
records = args.compact
|
250
|
+
records.each{|record| record.delete}
|
251
|
+
args = records
|
252
|
+
args.push options
|
253
|
+
batch_put(*args)
|
254
|
+
end
|
255
|
+
|
256
|
+
# prepare attributes from sdb for ruby
|
257
|
+
#
|
258
|
+
def sdb_to_ruby(attributes = {})
|
259
|
+
returning Hash.new do |hash|
|
260
|
+
attributes.each do |key, value|
|
261
|
+
unless value.nil?
|
262
|
+
type = type_for(key)
|
263
|
+
value = type ? type.sdb_to_ruby(value) : Type.sdb_to_ruby(value)
|
264
|
+
hash[key.to_s] = value
|
265
|
+
else
|
266
|
+
hash[key.to_s] = nil
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# prepare attributes from ruby for sdb
|
273
|
+
#
|
274
|
+
def ruby_to_sdb(attributes = {})
|
275
|
+
returning Hash.new do |hash|
|
276
|
+
attributes.each do |key, value|
|
277
|
+
unless value.nil?
|
278
|
+
type = type_for(key)
|
279
|
+
value = type ? type.ruby_to_sdb(value) : Type.ruby_to_sdb(value)
|
280
|
+
hash[key.to_s] = value
|
281
|
+
else
|
282
|
+
hash[key.to_s] = nil
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
def type_for name
|
289
|
+
attribute = attributes.detect{|attribute| attribute.name == name.to_s}
|
290
|
+
attribute.type if attribute
|
291
|
+
end
|
292
|
+
|
293
|
+
# create an existing record
|
294
|
+
#
|
295
|
+
def old(id, attributes = {})
|
296
|
+
attributes = Attributes.for(attributes)
|
297
|
+
attributes[:old] = true
|
298
|
+
class_for(attributes).new(id, attributes)
|
299
|
+
end
|
300
|
+
|
301
|
+
def class_for(attributes)
|
302
|
+
if sti_attribute
|
303
|
+
classname = [ attributes[sti_attribute.name.to_s] ].flatten.first
|
304
|
+
begin
|
305
|
+
classname.blank? ? self : classname.constantize
|
306
|
+
rescue NameError => e
|
307
|
+
self
|
308
|
+
end
|
309
|
+
else
|
310
|
+
self
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def sti_attribute
|
315
|
+
@sti_attribute ||= attributes.detect{|attribute| attribute.type.sti?}
|
316
|
+
end
|
317
|
+
|
318
|
+
# select/find support
|
319
|
+
#
|
320
|
+
attr_accessor :next_token
|
321
|
+
|
322
|
+
def select(*args, &block)
|
323
|
+
execute_select(*args, &block)
|
324
|
+
end
|
325
|
+
alias_method 'find', 'select'
|
326
|
+
|
327
|
+
def method_missing(message, *args, &block)
|
328
|
+
message = message.to_s
|
329
|
+
re = %r/^(?:find|select)(_all)?_by_(.*)$/io
|
330
|
+
match, all, clause = message.match(re).to_a
|
331
|
+
super unless match
|
332
|
+
clauses = clause.split(%r/_and_/io)
|
333
|
+
conditions = clauses.inject(Hash.new){|hash,attr| hash.update attr => args.shift}
|
334
|
+
select(all ? :all : :first, :conditions => conditions)
|
335
|
+
end
|
336
|
+
|
337
|
+
def execute_select(*args, &block)
|
338
|
+
log(:debug){ "execute_select <- #{ args.inspect }" }
|
339
|
+
options = args.extract_options!.to_options!
|
340
|
+
|
341
|
+
# yank out specical options used for recurion, formatting, and
|
342
|
+
# following huge queries. none of these affect sql generation
|
343
|
+
#
|
344
|
+
accum = options.delete(:accum) || OpenStruct.new(:items => [], :count => 0)
|
345
|
+
raw = options.delete(:raw)
|
346
|
+
@next_token = options.delete(:next_token)
|
347
|
+
|
348
|
+
# handle limts > amazon's threshold specially - we'll be batching them
|
349
|
+
#
|
350
|
+
limit = Integer(options[:limit]) if options.has_key?(:limit)
|
351
|
+
if limit and limit > 2500
|
352
|
+
options[:limit] = 2500
|
353
|
+
end
|
354
|
+
|
355
|
+
# detect the arity of the result set, also set implied limit (:first)
|
356
|
+
# and go ahead and recurse for queries that are large sets of ids
|
357
|
+
#
|
358
|
+
case args.first.to_s
|
359
|
+
when "", "all"
|
360
|
+
result_arity = -1
|
361
|
+
wants = :all
|
362
|
+
when "first"
|
363
|
+
limit = 1
|
364
|
+
result_arity = 1
|
365
|
+
wants = :first
|
366
|
+
else
|
367
|
+
ids = args.flatten.compact
|
368
|
+
raise ArgumentError, 'no ids' if ids.blank?
|
369
|
+
if(args.first.is_a?(Array) or ids.size > 1)
|
370
|
+
result_arity = -1
|
371
|
+
wants = :ids
|
372
|
+
else
|
373
|
+
result_arity = 1
|
374
|
+
wants = :id
|
375
|
+
end
|
376
|
+
if ids.size > 20
|
377
|
+
if block
|
378
|
+
ids.each_slice(20){|slice| execute_select(*[slice, options], &block)}
|
379
|
+
else
|
380
|
+
# records = ids.threadify(2, :each_slice, 20){|slice| execute_select(*[slice, options])}.flatten
|
381
|
+
records = []
|
382
|
+
ids.each_slice(20){|slice| records.push execute_select(*[slice, options])}
|
383
|
+
records.flatten!
|
384
|
+
return(limit ? records[0,limit] : records)
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
# generate the sql and get the results
|
390
|
+
#
|
391
|
+
sql = sql_for_select(*[args.dup, options.dup].flatten, &block)
|
392
|
+
log(:debug){ "execute_select -> #{ sql.inspect }" }
|
393
|
+
result = connection.select(sql, @next_token)
|
394
|
+
@next_token = result[:next_token]
|
395
|
+
items = result[:items]
|
396
|
+
|
397
|
+
|
398
|
+
# unpack the results into models or hashes (iff :raw=>true). iterate
|
399
|
+
# if a block was given while doing so to prevent creating un-needed
|
400
|
+
# objects
|
401
|
+
#
|
402
|
+
result[:items].each do |hash|
|
403
|
+
item =
|
404
|
+
unless raw
|
405
|
+
id, attributes = hash.shift
|
406
|
+
old(id, attributes)
|
407
|
+
else
|
408
|
+
hash
|
409
|
+
end
|
410
|
+
block ? block.call(item) : accum.items.push(item)
|
411
|
+
accum.count += 1
|
412
|
+
break if limit and accum.count >= limit
|
413
|
+
end
|
414
|
+
|
415
|
+
# if a next_token was returned handle the recursion/following
|
416
|
+
# transparently for the user. handle the specical case where amazon
|
417
|
+
# says there are 'more records' even though our client limit has been
|
418
|
+
# reached
|
419
|
+
#
|
420
|
+
if @next_token
|
421
|
+
if limit.nil?
|
422
|
+
recurse = [
|
423
|
+
args,
|
424
|
+
options.merge(:next_token => @next_token, :raw => raw, :accum => accum)
|
425
|
+
].flatten
|
426
|
+
execute_select(*recurse, &block)
|
427
|
+
else
|
428
|
+
if accum.count < limit
|
429
|
+
recurse = [
|
430
|
+
args,
|
431
|
+
options.merge(:next_token => @next_token, :raw => raw, :accum => accum, :limit => (limit - accum.count))
|
432
|
+
].flatten
|
433
|
+
execute_select(*recurse, &block)
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
# finally, build the return value based on arity and limit (expecting
|
439
|
+
# a single result or many or none when iterating)
|
440
|
+
#
|
441
|
+
if block
|
442
|
+
accum.count
|
443
|
+
else
|
444
|
+
if result_arity == 1
|
445
|
+
record = accum.items.first
|
446
|
+
raise RecordNotFound if(record.nil? and wants==:id)
|
447
|
+
record
|
448
|
+
else
|
449
|
+
limit ? accum.items[0,limit] : accum.items
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
454
|
+
def sql_for_select(*args)
|
455
|
+
options = args.extract_options!.to_options!
|
456
|
+
args.flatten!
|
457
|
+
|
458
|
+
# arity
|
459
|
+
#
|
460
|
+
case args.first.to_s
|
461
|
+
when "", "all"
|
462
|
+
:all
|
463
|
+
when "first"
|
464
|
+
options[:limit] = 1
|
465
|
+
:first
|
466
|
+
else
|
467
|
+
options[:ids] = args.flatten.compact
|
468
|
+
args.size == 1 ? :id : :ids
|
469
|
+
end
|
470
|
+
|
471
|
+
# do you want to show deleted records?
|
472
|
+
#
|
473
|
+
want_deleted = options.has_key?(:deleted) ? options.delete(:deleted) : false
|
474
|
+
|
475
|
+
# build select
|
476
|
+
#
|
477
|
+
select = sql_select_list_for(options[:select])
|
478
|
+
|
479
|
+
# build from
|
480
|
+
#
|
481
|
+
from = options[:domain] || options[:from] || domain
|
482
|
+
from = escape_domain(from)
|
483
|
+
|
484
|
+
# build conditions
|
485
|
+
#
|
486
|
+
conditions = (options[:conditions] || {})
|
487
|
+
conditions.to_options! if conditions.is_a?(Hash)
|
488
|
+
conditions = !conditions.blank? ? " WHERE #{ sql_conditions_for(options[:conditions]) }" : ''
|
489
|
+
|
490
|
+
# build order
|
491
|
+
#
|
492
|
+
order = !options[:order].blank? ?
|
493
|
+
" ORDER BY #{ sort_by, sort_order = sort_options_for(options[:order]); [escape_attribute(sort_by), sort_order].join(' ') }" : ''
|
494
|
+
|
495
|
+
# build limit
|
496
|
+
#
|
497
|
+
limit = !options[:limit].blank? ? " LIMIT #{ options[:limit] }" : ''
|
498
|
+
|
499
|
+
# build ids
|
500
|
+
#
|
501
|
+
ids = options[:ids] || []
|
502
|
+
|
503
|
+
# monkey patch conditions
|
504
|
+
#
|
505
|
+
unless order.blank? # you must have a predicate for any attribute sorted on...
|
506
|
+
sort_by, sort_order = sort_options_for(options[:order])
|
507
|
+
conditions << (conditions.blank? ? " WHERE " : " AND ") << "(#{ escape_attribute(sort_by) } IS NOT NULL)"
|
508
|
+
end
|
509
|
+
unless ids.blank?
|
510
|
+
list = ids.flatten.map{|id| escape(id)}.join(',')
|
511
|
+
conditions << (conditions.blank? ? " WHERE " : " AND ") << "ItemName() in (#{ list })"
|
512
|
+
end
|
513
|
+
#conditions << (conditions.blank? ? " WHERE " : " AND ") << "(deleted_at is not null and every(deleted_at) != 'nil')"
|
514
|
+
#conditions << (conditions.blank? ? " WHERE " : " AND ") << "(every(deleted_at) = 'nil' or deleted_at is null)"
|
515
|
+
if want_deleted
|
516
|
+
conditions << (conditions.blank? ? " WHERE " : " AND ") << "`deleted_at`!='nil'"
|
517
|
+
else
|
518
|
+
conditions << (conditions.blank? ? " WHERE " : " AND ") << "`deleted_at`='nil'"
|
519
|
+
end
|
520
|
+
|
521
|
+
# sql
|
522
|
+
#
|
523
|
+
sql = "SELECT #{ select } FROM #{ from } #{ conditions } #{ order } #{ limit }".strip
|
524
|
+
end
|
525
|
+
|
526
|
+
ItemName = Literal.for('ItemName()') unless defined?(ItemName)
|
527
|
+
Splat = Literal.for('*') unless defined?(Splat)
|
528
|
+
|
529
|
+
def sql_select_list_for(*list)
|
530
|
+
list = listify list
|
531
|
+
list.map!{|attr| attr =~ %r/^\s*id\s*$/io ? ItemName : attr}
|
532
|
+
sql = list.map{|attr| escape_attribute(attr)}.join(',')
|
533
|
+
sql.blank? ? Splat : sql
|
534
|
+
end
|
535
|
+
|
536
|
+
def sql_conditions_for(conditions)
|
537
|
+
sql =
|
538
|
+
case conditions
|
539
|
+
when Array
|
540
|
+
sql_conditions_from_array(conditions)
|
541
|
+
when Hash
|
542
|
+
sql_conditions_from_hash(conditions)
|
543
|
+
else
|
544
|
+
conditions.respond_to?(:to_sql) ? conditions.to_sql : conditions.to_s
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
def sql_conditions_from_array(array)
|
549
|
+
return '' if array.blank?
|
550
|
+
sql = ''
|
551
|
+
|
552
|
+
case array.first
|
553
|
+
when Hash, Array
|
554
|
+
until array.blank?
|
555
|
+
arg = array.shift
|
556
|
+
sql << (
|
557
|
+
case arg
|
558
|
+
when Hash
|
559
|
+
"(#{ sql_conditions_from_hash(arg) })"
|
560
|
+
when Array
|
561
|
+
"(#{ sql_conditions_from_array(arg) })"
|
562
|
+
else
|
563
|
+
" #{ arg.to_s } "
|
564
|
+
end
|
565
|
+
)
|
566
|
+
end
|
567
|
+
else
|
568
|
+
query = array.shift.to_s
|
569
|
+
hash = array.shift
|
570
|
+
raise WTF unless array.empty?
|
571
|
+
raise WTF unless hash.is_a?(Hash)
|
572
|
+
|
573
|
+
hash.each do |key, val|
|
574
|
+
key = key.to_s.to_sym
|
575
|
+
sdb_val = to_condition(key, val)
|
576
|
+
re = %r/[:@]#{ key }/
|
577
|
+
query.gsub! re, sdb_val
|
578
|
+
end
|
579
|
+
sql << query
|
580
|
+
end
|
581
|
+
|
582
|
+
sql
|
583
|
+
end
|
584
|
+
|
585
|
+
def sql_conditions_from_hash(hash)
|
586
|
+
return '' if hash.blank?
|
587
|
+
expression = []
|
588
|
+
every_re = %r/every\s*\(\s*([^)])\s*\)/io
|
589
|
+
|
590
|
+
hash.each do |key, value|
|
591
|
+
key = key.to_s
|
592
|
+
|
593
|
+
m = every_re.match(key)
|
594
|
+
if m
|
595
|
+
key = m[1]
|
596
|
+
every = true
|
597
|
+
else
|
598
|
+
every = false
|
599
|
+
end
|
600
|
+
|
601
|
+
lhs = escape_attribute(key =~ %r/^\s*id\s*$/oi ? ItemName : key)
|
602
|
+
|
603
|
+
rhs =
|
604
|
+
if value.is_a?(Array)
|
605
|
+
first = value.first.to_s.strip.downcase.gsub(%r/\s+/, ' ')
|
606
|
+
if(first.delete('() ') == 'every')
|
607
|
+
every = value.shift
|
608
|
+
op = value.first.to_s.strip.downcase.gsub(%r/\s+/, ' ')
|
609
|
+
else
|
610
|
+
every = false
|
611
|
+
op = first
|
612
|
+
end
|
613
|
+
case op
|
614
|
+
when '=', '!=', '>', '>=', '<', '<=', 'like', 'not like'
|
615
|
+
list = value[1..-1].flatten.map{|val| to_condition(key, val)}
|
616
|
+
"#{ op } #{ list.join(',') }"
|
617
|
+
when 'between'
|
618
|
+
a, b, *ignored = value[1..-1].flatten.map{|val| to_condition(key, val)}
|
619
|
+
"between #{ a } and #{ b }"
|
620
|
+
when 'is null'
|
621
|
+
'is null'
|
622
|
+
when 'is not null'
|
623
|
+
'is not null'
|
624
|
+
else # 'in'
|
625
|
+
value.shift if op == 'in'
|
626
|
+
list = value.flatten.map{|val| to_condition(key, val)}
|
627
|
+
"in (#{ list.join(',') })"
|
628
|
+
end
|
629
|
+
else
|
630
|
+
"= #{ to_condition(key, value) }"
|
631
|
+
end
|
632
|
+
|
633
|
+
lhs = "every(#{ lhs })" if every
|
634
|
+
expression << "#{ lhs } #{ rhs }"
|
635
|
+
end
|
636
|
+
sql = expression.join(' AND ')
|
637
|
+
end
|
638
|
+
|
639
|
+
def escape_value(value)
|
640
|
+
return value if Literal?(value)
|
641
|
+
case value
|
642
|
+
when TrueClass, FalseClass
|
643
|
+
escape(value.to_s)
|
644
|
+
else
|
645
|
+
connection.escape(connection.ruby_to_sdb(value))
|
646
|
+
end
|
647
|
+
end
|
648
|
+
alias_method 'escape', 'escape_value'
|
649
|
+
|
650
|
+
def escape_attribute(value)
|
651
|
+
return value if Literal?(value)
|
652
|
+
return value if value =~ %r/^ItemName(?:\(\))?$/io
|
653
|
+
"`#{ value.gsub(%r/`/, '``') }`"
|
654
|
+
end
|
655
|
+
|
656
|
+
def escape_domain(value)
|
657
|
+
return value if Literal?(value)
|
658
|
+
"`#{ value.gsub(%r/`/, '``') }`"
|
659
|
+
end
|
660
|
+
|
661
|
+
def to_condition(attribute, value)
|
662
|
+
return value if Literal?(value)
|
663
|
+
return value.to_condition() if value.respond_to?(:to_condition)
|
664
|
+
type = type_for(attribute)
|
665
|
+
value = type ? type.to_condition(value) : value
|
666
|
+
escape_value(value)
|
667
|
+
end
|
668
|
+
|
669
|
+
def listify(*list)
|
670
|
+
if list.size == 1 and list.first.is_a?(String)
|
671
|
+
list.first.strip.split(%r/\s*,\s*/)
|
672
|
+
else
|
673
|
+
list.flatten!
|
674
|
+
list.compact!
|
675
|
+
list
|
676
|
+
end
|
677
|
+
end
|
678
|
+
|
679
|
+
def sort_options_for(sort)
|
680
|
+
return sort.to_sql if sort.respond_to?(:to_sql)
|
681
|
+
pair =
|
682
|
+
if sort.is_a?(Array)
|
683
|
+
raise ArgumentError, "empty sort" if sort.empty?
|
684
|
+
sort.push(:asc) if sort.size < 2
|
685
|
+
sort.first(2).map{|s| s.to_s}
|
686
|
+
else
|
687
|
+
sort.to_s[%r/['"]?(\w+)['"]? *(asc|desc)?/io]
|
688
|
+
[$1, ($2 || 'asc')]
|
689
|
+
end
|
690
|
+
[ quoted_attribute(pair.first), pair.last.to_s ]
|
691
|
+
end
|
692
|
+
|
693
|
+
def quoted_attribute attr
|
694
|
+
return attr if Literal?(attr)
|
695
|
+
if attr =~ %r/^\s*id\s*$/
|
696
|
+
ItemName
|
697
|
+
else
|
698
|
+
Literal(escape_attribute(attr))
|
699
|
+
end
|
700
|
+
end
|
701
|
+
|
702
|
+
def [](*ids)
|
703
|
+
select(*ids)
|
704
|
+
end
|
705
|
+
|
706
|
+
def reload_if_exists(record)
|
707
|
+
record && record.reload
|
708
|
+
end
|
709
|
+
|
710
|
+
def reload_all_records(*list)
|
711
|
+
list.flatten.each{|record| reload_if_exists(record)}
|
712
|
+
end
|
713
|
+
|
714
|
+
def first(*args, &block)
|
715
|
+
options = args.extract_options!.to_options!
|
716
|
+
n = Integer(args.shift || options[:limit] || 1)
|
717
|
+
options.to_options!
|
718
|
+
options[:limit] = n
|
719
|
+
order = options.delete(:order)
|
720
|
+
if order
|
721
|
+
sort_by, sort_order = sort_options_for(order)
|
722
|
+
options[:order] = [sort_by, :asc]
|
723
|
+
else
|
724
|
+
options[:order] = [:id, :asc]
|
725
|
+
end
|
726
|
+
list = select(:all, options, &block)
|
727
|
+
n == 1 ? list.first : list.first(n)
|
728
|
+
end
|
729
|
+
|
730
|
+
def last(*args, &block)
|
731
|
+
options = args.extract_options!.to_options!
|
732
|
+
n = Integer(args.shift || options[:limit] || 1)
|
733
|
+
options.to_options!
|
734
|
+
options[:limit] = n
|
735
|
+
order = options.delete(:order)
|
736
|
+
if order
|
737
|
+
sort_by, sort_order = sort_options_for(order)
|
738
|
+
options[:order] = [sort_by, :desc]
|
739
|
+
else
|
740
|
+
options[:order] = [:id, :desc]
|
741
|
+
end
|
742
|
+
list = select(:all, options, &block)
|
743
|
+
n == 1 ? list.first : list.first(n)
|
744
|
+
end
|
745
|
+
|
746
|
+
def all(*args, &block)
|
747
|
+
select(:all, *args, &block)
|
748
|
+
end
|
749
|
+
|
750
|
+
def count(conditions = {}, &block)
|
751
|
+
conditions =
|
752
|
+
case conditions
|
753
|
+
when Hash, NilClass
|
754
|
+
conditions || {}
|
755
|
+
else
|
756
|
+
{:conditions => conditions}
|
757
|
+
end
|
758
|
+
options = conditions.has_key?(:conditions) ? conditions : {:conditions => conditions}
|
759
|
+
options[:select] = Literal('count(*)')
|
760
|
+
sql = sql_for_select(options)
|
761
|
+
result = connection.select(sql, &block)
|
762
|
+
Integer(result[:items].first['Domain']['Count'].first) rescue(raise(Error, result.inspect))
|
763
|
+
end
|
764
|
+
end
|
765
|
+
|
766
|
+
attr_accessor 'id'
|
767
|
+
attr_accessor 'new_record'
|
768
|
+
alias_method 'new_record?', 'new_record'
|
769
|
+
alias_method 'new?', 'new_record'
|
770
|
+
attr_accessor 'attributes'
|
771
|
+
attr_accessor 'attributes_before_sdb_to_ruby'
|
772
|
+
alias_method 'item_name', 'id'
|
773
|
+
attr_accessor 'deleted'
|
774
|
+
alias_method 'deleted?', 'deleted'
|
775
|
+
|
776
|
+
class Attributes < ::HashWithIndifferentAccess
|
777
|
+
def Attributes.for(arg)
|
778
|
+
Attributes === arg ? arg : new(arg)
|
779
|
+
end
|
780
|
+
end
|
781
|
+
|
782
|
+
# instance methods
|
783
|
+
#
|
784
|
+
def initialize(*args, &block)
|
785
|
+
@args, @block = args, block
|
786
|
+
options = @args.extract_options!.to_options!
|
787
|
+
@new_record = true
|
788
|
+
@new_record = !!!options.delete(:old) if options.has_key?(:old)
|
789
|
+
@new_record = !!!options.delete(:new_record) if options.has_key?(:new_record)
|
790
|
+
@id = @args.size == 1 ? @args.shift : generate_id
|
791
|
+
|
792
|
+
if @new_record
|
793
|
+
@attributes = Attributes.new
|
794
|
+
before_initialize
|
795
|
+
klass.attributes.each{|attribute| attribute.initialize_record(self)}
|
796
|
+
klass.associations.each{|association| association.initialize_record(self)}
|
797
|
+
options.each do |name, value|
|
798
|
+
setter = "#{ name }="
|
799
|
+
if respond_to?(setter)
|
800
|
+
send(setter, value)
|
801
|
+
else
|
802
|
+
attributes[name.to_s] = value
|
803
|
+
end
|
804
|
+
end
|
805
|
+
@deleted = attributes['deleted_at'] ? true : false
|
806
|
+
@removed = false
|
807
|
+
after_initialize
|
808
|
+
else
|
809
|
+
before_load
|
810
|
+
@attributes = Attributes.for(options)
|
811
|
+
sdb_to_ruby!
|
812
|
+
@deleted = attributes['deleted_at'] ? true : false
|
813
|
+
@removed = false
|
814
|
+
after_load
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
def klass
|
819
|
+
self.class
|
820
|
+
end
|
821
|
+
|
822
|
+
def attributes= attributes
|
823
|
+
self.attributes.replace attributes
|
824
|
+
end
|
825
|
+
|
826
|
+
def generate_id
|
827
|
+
klass.generate_id
|
828
|
+
end
|
829
|
+
|
830
|
+
def generate_id!
|
831
|
+
@id = generate_id
|
832
|
+
end
|
833
|
+
|
834
|
+
def mark_as_old!
|
835
|
+
self.new_record = false
|
836
|
+
end
|
837
|
+
|
838
|
+
def [](attribute)
|
839
|
+
attributes[attribute.to_s]
|
840
|
+
end
|
841
|
+
|
842
|
+
def []=(key, value)
|
843
|
+
attributes[key] = value
|
844
|
+
end
|
845
|
+
|
846
|
+
def sdb_to_ruby(attributes = self.attributes)
|
847
|
+
klass.sdb_to_ruby(attributes)
|
848
|
+
end
|
849
|
+
|
850
|
+
def sdb_to_ruby!(attributes = self.attributes)
|
851
|
+
self.attributes.replace(sdb_to_ruby(attributes))
|
852
|
+
end
|
853
|
+
|
854
|
+
def ruby_to_sdb(attributes = self.attributes)
|
855
|
+
klass.ruby_to_sdb(attributes)
|
856
|
+
end
|
857
|
+
|
858
|
+
def ruby_to_sdb!(attributes = self.attributes)
|
859
|
+
self.attributes.replace(ruby_to_sdb(attributes))
|
860
|
+
end
|
861
|
+
|
862
|
+
def reload
|
863
|
+
check_id!
|
864
|
+
record = attempt{ klass.select(id) || try_again! }
|
865
|
+
raise Error, "no record for #{ id.inspect } (yet)" unless record
|
866
|
+
replace(record)
|
867
|
+
self
|
868
|
+
end
|
869
|
+
alias_method 'reload!', 'reload'
|
870
|
+
|
871
|
+
def replace other
|
872
|
+
self.id = other.id
|
873
|
+
self.attributes.replace other.attributes
|
874
|
+
end
|
875
|
+
|
876
|
+
def raw
|
877
|
+
klass.select(id, :raw => true)
|
878
|
+
end
|
879
|
+
|
880
|
+
def created_at
|
881
|
+
Time.parse(attributes['created_at'].to_s) unless attributes['created_at'].blank?
|
882
|
+
end
|
883
|
+
def created_at= time
|
884
|
+
attributes['created_at'] = time
|
885
|
+
end
|
886
|
+
|
887
|
+
def updated_at
|
888
|
+
Time.parse(attributes['updated_at'].to_s) unless attributes['updated_at'].blank?
|
889
|
+
end
|
890
|
+
def updated_at= time
|
891
|
+
attributes['updated_at'] = time
|
892
|
+
end
|
893
|
+
|
894
|
+
def deleted_at
|
895
|
+
Time.parse(attributes['deleted_at'].to_s) unless attributes['deleted_at'].blank?
|
896
|
+
end
|
897
|
+
def deleted_at= time
|
898
|
+
attributes['deleted_at'] = time
|
899
|
+
end
|
900
|
+
|
901
|
+
def update(options = {})
|
902
|
+
attributes.update(options)
|
903
|
+
end
|
904
|
+
|
905
|
+
def raising_an_error?
|
906
|
+
$!
|
907
|
+
end
|
908
|
+
|
909
|
+
def updating(&block)
|
910
|
+
return(block.call) if(defined?(@updating) and @updating)
|
911
|
+
@updating = true
|
912
|
+
prepare_for_update
|
913
|
+
before_update
|
914
|
+
block.call
|
915
|
+
ensure
|
916
|
+
@updating = false
|
917
|
+
after_update unless raising_an_error?
|
918
|
+
end
|
919
|
+
|
920
|
+
def save_without_validation
|
921
|
+
updating do
|
922
|
+
sdb_attributes = ruby_to_sdb
|
923
|
+
connection.put_attributes(domain, id, sdb_attributes, :replace)
|
924
|
+
virtually_load(sdb_attributes)
|
925
|
+
mark_as_old!
|
926
|
+
self
|
927
|
+
end
|
928
|
+
end
|
929
|
+
|
930
|
+
def prepare_for_update
|
931
|
+
time = Transaction.time.iso8601(2)
|
932
|
+
attributes['updated_at'] = time
|
933
|
+
if new_record?
|
934
|
+
attributes['created_at'] ||= time
|
935
|
+
attributes['deleted_at'] = nil
|
936
|
+
attributes['transaction_id'] = Transaction.id
|
937
|
+
end
|
938
|
+
end
|
939
|
+
|
940
|
+
def save(options = {})
|
941
|
+
options.to_options!
|
942
|
+
should_raise = options[:raise]
|
943
|
+
before_save
|
944
|
+
if(before_validation()==false)
|
945
|
+
raise(RecordInvalid) if should_raise
|
946
|
+
return false
|
947
|
+
end
|
948
|
+
unless valid?
|
949
|
+
raise(RecordInvalid) if should_raise
|
950
|
+
return false
|
951
|
+
end
|
952
|
+
after_validation()
|
953
|
+
saved = save_without_validation
|
954
|
+
raise(RecordNotSaved) if should_raise unless saved
|
955
|
+
saved
|
956
|
+
ensure
|
957
|
+
after_save unless raising_an_error?
|
958
|
+
end
|
959
|
+
|
960
|
+
def save!(options = {})
|
961
|
+
save(options.to_options.update(:raise => true))
|
962
|
+
end
|
963
|
+
|
964
|
+
def errors!
|
965
|
+
raise Validations::Error.new(self, errors.message)
|
966
|
+
end
|
967
|
+
|
968
|
+
def update!(options = {})
|
969
|
+
updating do
|
970
|
+
attributes.update(options)
|
971
|
+
save!
|
972
|
+
virtually_save(attributes)
|
973
|
+
self
|
974
|
+
end
|
975
|
+
end
|
976
|
+
|
977
|
+
alias_method 'update_attributes', 'update!'
|
978
|
+
|
979
|
+
def put_attributes(attributes)
|
980
|
+
updating do
|
981
|
+
sdb_attributes = ruby_to_sdb(attributes)
|
982
|
+
connection.put_attributes(domain, id, sdb_attributes)
|
983
|
+
virtually_put(sdb_attributes)
|
984
|
+
self
|
985
|
+
end
|
986
|
+
end
|
987
|
+
|
988
|
+
def save_attributes(attributes = self.attributes)
|
989
|
+
updating do
|
990
|
+
sdb_attributes = ruby_to_sdb(attributes)
|
991
|
+
connection.put_attributes(domain, id, sdb_attributes, :replace)
|
992
|
+
virtually_save(sdb_attributes)
|
993
|
+
self
|
994
|
+
end
|
995
|
+
end
|
996
|
+
|
997
|
+
def replace_attributes(attributes = self.attributes)
|
998
|
+
updating do
|
999
|
+
delete_attributes(self.attributes.keys)
|
1000
|
+
save_attributes(attributes)
|
1001
|
+
self
|
1002
|
+
end
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
def delete_attributes(*args)
|
1006
|
+
updating do
|
1007
|
+
args.flatten!
|
1008
|
+
args.compact!
|
1009
|
+
hashes, arrays = args.partition{|arg| arg.is_a?(Hash)}
|
1010
|
+
hashes.map!{|hash| stringify(hash)}
|
1011
|
+
hashes.each do |hash|
|
1012
|
+
raise ArgumentError, hash.inspect if hash.values.any?{|value| value == nil or value == []}
|
1013
|
+
end
|
1014
|
+
array = stringify(arrays.flatten)
|
1015
|
+
unless array.empty?
|
1016
|
+
array_as_hash = array.inject({}){|h,k| h.update k => nil}
|
1017
|
+
hashes.push(array_as_hash)
|
1018
|
+
end
|
1019
|
+
hashes.each{|hash|
|
1020
|
+
next if hash.empty?
|
1021
|
+
connection.delete_attributes(domain, id, hash)
|
1022
|
+
virtually_delete(hash)
|
1023
|
+
}
|
1024
|
+
self
|
1025
|
+
end
|
1026
|
+
end
|
1027
|
+
alias_method 'delete_values', 'delete_attributes'
|
1028
|
+
|
1029
|
+
def delete_item
|
1030
|
+
connection.delete_item(domain, id)
|
1031
|
+
self
|
1032
|
+
ensure
|
1033
|
+
@removed = true
|
1034
|
+
end
|
1035
|
+
alias_method 'remove!', 'delete_item'
|
1036
|
+
|
1037
|
+
# TODO - need to consider how for pass the options along to children being
|
1038
|
+
# deleted along the way - first pass with @removed/@deleted
|
1039
|
+
#
|
1040
|
+
def delete(options = {})
|
1041
|
+
options.to_options!
|
1042
|
+
before_delete
|
1043
|
+
if options[:force]||options[:remove]
|
1044
|
+
delete_item
|
1045
|
+
else
|
1046
|
+
attributes['deleted_at'] = Transaction.time
|
1047
|
+
save_without_validation
|
1048
|
+
end
|
1049
|
+
self
|
1050
|
+
ensure
|
1051
|
+
@deleted = true
|
1052
|
+
after_delete unless $!
|
1053
|
+
end
|
1054
|
+
|
1055
|
+
def delete!(options = {})
|
1056
|
+
delete(options.to_options.update(:force => true))
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
def destroy(options = {})
|
1060
|
+
before_destroy
|
1061
|
+
delete(options)
|
1062
|
+
ensure
|
1063
|
+
after_destroy unless $!
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
# virtual consistency
|
1067
|
+
#
|
1068
|
+
def perform_virtual_consistency(*value)
|
1069
|
+
@perform_virtual_consistency = klass.perform_virtual_consistency unless defined?(@perform_virtual_consistency)
|
1070
|
+
@perform_virtual_consistency = !!value.first unless value.empty?
|
1071
|
+
@perform_virtual_consistency
|
1072
|
+
end
|
1073
|
+
|
1074
|
+
def perform_virtual_consistency?()
|
1075
|
+
perform_virtual_consistency()
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
def perform_virtual_consistency=(value)
|
1079
|
+
perform_virtual_consistency(value)
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
def perform_virtual_consistency!()
|
1083
|
+
perform_virtual_consistency(true)
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
def virtually_load(sdb_attributes)
|
1087
|
+
#return unless perform_virtual_consistency?
|
1088
|
+
self.attributes.replace(sdb_to_ruby(sdb_attributes))
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
def virtually_save(ruby_attributes=self.attributes)
|
1092
|
+
#return unless perform_virtual_consistency?
|
1093
|
+
sdb_attributes = ruby_to_sdb(ruby_attributes)
|
1094
|
+
virtually_load(sdb_attributes)
|
1095
|
+
end
|
1096
|
+
|
1097
|
+
def virtually_put(sdb_attributes)
|
1098
|
+
#return unless perform_virtual_consistency?
|
1099
|
+
a = sdb_attributes
|
1100
|
+
b = ruby_to_sdb
|
1101
|
+
(a.keys + b.keys).uniq.each do |key|
|
1102
|
+
was_virtually_put = a.has_key?(key)
|
1103
|
+
if was_virtually_put
|
1104
|
+
val = b[key]
|
1105
|
+
val = [val] unless val.is_a?(Array)
|
1106
|
+
val += a[key]
|
1107
|
+
end
|
1108
|
+
end
|
1109
|
+
virtually_load(b)
|
1110
|
+
end
|
1111
|
+
|
1112
|
+
def virtually_delete(ruby_attributes)
|
1113
|
+
#return unless perform_virtual_consistency?
|
1114
|
+
ruby_attributes.keys.each do |key|
|
1115
|
+
val = ruby_attributes[key]
|
1116
|
+
if val.nil?
|
1117
|
+
ruby_attributes.delete(key)
|
1118
|
+
attributes.delete(key)
|
1119
|
+
end
|
1120
|
+
end
|
1121
|
+
|
1122
|
+
current = ruby_to_sdb
|
1123
|
+
deleted = ruby_to_sdb(ruby_attributes)
|
1124
|
+
|
1125
|
+
deleted.each do |key, deleted_val|
|
1126
|
+
deleted_val = [ deleted_val ].flatten
|
1127
|
+
current_val = [ current[key] ].flatten
|
1128
|
+
deleted_val.each{|val| current_val.delete(val)}
|
1129
|
+
|
1130
|
+
if current[key].is_a?(Array)
|
1131
|
+
current[key] = current_val
|
1132
|
+
else
|
1133
|
+
if current_val.blank?
|
1134
|
+
current[key] = nil
|
1135
|
+
else
|
1136
|
+
current[key] = current_val
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
end
|
1140
|
+
virtually_load(current)
|
1141
|
+
end
|
1142
|
+
|
1143
|
+
|
1144
|
+
def stringify(arg)
|
1145
|
+
case arg
|
1146
|
+
when Hash
|
1147
|
+
hash = {}
|
1148
|
+
arg.each{|key, val| hash[stringify(key)] = stringify(val)}
|
1149
|
+
hash
|
1150
|
+
when Array
|
1151
|
+
arg.map{|arg| stringify(arg)}
|
1152
|
+
else
|
1153
|
+
arg.to_s
|
1154
|
+
end
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
def listify(*list)
|
1158
|
+
klass.listify(*list)
|
1159
|
+
end
|
1160
|
+
|
1161
|
+
def check_id!
|
1162
|
+
raise Error.new('No record id') unless id
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
def to_hash(options = {})
|
1166
|
+
options.to_options!
|
1167
|
+
depth = options[:depth] || 0
|
1168
|
+
if depth == 0
|
1169
|
+
attributes.to_hash
|
1170
|
+
else
|
1171
|
+
raise NotImplementedError
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
|
1175
|
+
def to_yaml
|
1176
|
+
to_hash.to_yaml
|
1177
|
+
end
|
1178
|
+
|
1179
|
+
def to_json
|
1180
|
+
to_hash.to_json
|
1181
|
+
end
|
1182
|
+
|
1183
|
+
def to_param
|
1184
|
+
id ? id : 'new'
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
def model_name
|
1188
|
+
klass.name
|
1189
|
+
end
|
1190
|
+
|
1191
|
+
def domain
|
1192
|
+
klass.domain
|
1193
|
+
end
|
1194
|
+
|
1195
|
+
def escape(value)
|
1196
|
+
klass.escape(value)
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
def sti_attribute
|
1200
|
+
klass.sti_attribute
|
1201
|
+
end
|
1202
|
+
end
|
1203
|
+
end
|
1204
|
+
end
|