dynamoid-edge 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +377 -0
- data/Rakefile +67 -0
- data/dynamoid-edge.gemspec +74 -0
- data/lib/dynamoid/adapter.rb +181 -0
- data/lib/dynamoid/adapter_plugin/aws_sdk_v2.rb +761 -0
- data/lib/dynamoid/associations/association.rb +105 -0
- data/lib/dynamoid/associations/belongs_to.rb +44 -0
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +40 -0
- data/lib/dynamoid/associations/has_many.rb +39 -0
- data/lib/dynamoid/associations/has_one.rb +39 -0
- data/lib/dynamoid/associations/many_association.rb +191 -0
- data/lib/dynamoid/associations/single_association.rb +69 -0
- data/lib/dynamoid/associations.rb +106 -0
- data/lib/dynamoid/components.rb +37 -0
- data/lib/dynamoid/config/options.rb +78 -0
- data/lib/dynamoid/config.rb +54 -0
- data/lib/dynamoid/criteria/chain.rb +212 -0
- data/lib/dynamoid/criteria.rb +29 -0
- data/lib/dynamoid/dirty.rb +47 -0
- data/lib/dynamoid/document.rb +201 -0
- data/lib/dynamoid/errors.rb +63 -0
- data/lib/dynamoid/fields.rb +156 -0
- data/lib/dynamoid/finders.rb +197 -0
- data/lib/dynamoid/identity_map.rb +92 -0
- data/lib/dynamoid/middleware/identity_map.rb +16 -0
- data/lib/dynamoid/persistence.rb +324 -0
- data/lib/dynamoid/validations.rb +36 -0
- data/lib/dynamoid.rb +50 -0
- metadata +226 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
# require only 'concurrent/atom' once this issue is resolved:
|
2
|
+
# https://github.com/ruby-concurrency/concurrent-ruby/pull/377
|
3
|
+
require 'concurrent'
|
4
|
+
|
5
|
+
# encoding: utf-8
|
6
|
+
module Dynamoid
|
7
|
+
|
8
|
+
# Adapter's value-add:
|
9
|
+
# 1) For the rest of Dynamoid, the gateway to DynamoDB.
|
10
|
+
# 2) Allows switching `config.adapter` to ease development of a new adapter.
|
11
|
+
# 3) Caches the list of tables Dynamoid knows about.
|
12
|
+
class Adapter
|
13
|
+
def initialize
|
14
|
+
@adapter_ = Concurrent::Atom.new(nil)
|
15
|
+
@tables_ = Concurrent::Atom.new(nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
def tables
|
19
|
+
if !@tables_.value
|
20
|
+
@tables_.swap{|value, args| benchmark('Cache Tables') {list_tables}}
|
21
|
+
end
|
22
|
+
@tables_.value
|
23
|
+
end
|
24
|
+
|
25
|
+
# The actual adapter currently in use.
|
26
|
+
#
|
27
|
+
# @since 0.2.0
|
28
|
+
def adapter
|
29
|
+
if !@adapter_.value
|
30
|
+
adapter = self.class.adapter_plugin_class.new
|
31
|
+
adapter.connect! if adapter.respond_to?(:connect!)
|
32
|
+
@adapter_.compare_and_set(nil, adapter)
|
33
|
+
clear_cache!
|
34
|
+
end
|
35
|
+
@adapter_.value
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear_cache!
|
39
|
+
@tables_.swap{|value, args| nil}
|
40
|
+
end
|
41
|
+
|
42
|
+
# Shows how long it takes a method to run on the adapter. Useful for generating logged output.
|
43
|
+
#
|
44
|
+
# @param [Symbol] method the name of the method to appear in the log
|
45
|
+
# @param [Array] args the arguments to the method to appear in the log
|
46
|
+
# @yield the actual code to benchmark
|
47
|
+
#
|
48
|
+
# @return the result of the yield
|
49
|
+
#
|
50
|
+
# @since 0.2.0
|
51
|
+
def benchmark(method, *args)
|
52
|
+
start = Time.now
|
53
|
+
result = yield
|
54
|
+
Dynamoid.logger.info "(#{((Time.now - start) * 1000.0).round(2)} ms) #{method.to_s.split('_').collect(&:upcase).join(' ')}#{ " - #{args.inspect}" unless args.nil? || args.empty? }"
|
55
|
+
return result
|
56
|
+
end
|
57
|
+
|
58
|
+
# Write an object to the adapter.
|
59
|
+
#
|
60
|
+
# @param [String] table the name of the table to write the object to
|
61
|
+
# @param [Object] object the object itself
|
62
|
+
# @param [Hash] options Options that are passed to the put_item call
|
63
|
+
#
|
64
|
+
# @return [Object] the persisted object
|
65
|
+
#
|
66
|
+
# @since 0.2.0
|
67
|
+
def write(table, object, options = nil)
|
68
|
+
put_item(table, object, options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Read one or many keys from the selected table.
|
72
|
+
# This method intelligently calls batch_get or get on the underlying adapter
|
73
|
+
# depending on whether ids is a range or a single key.
|
74
|
+
# If a range key is present, it will also interpolate that into the ids so
|
75
|
+
# that the batch get will acquire the correct record.
|
76
|
+
#
|
77
|
+
# @param [String] table the name of the table to write the object to
|
78
|
+
# @param [Array] ids to fetch, can also be a string of just one id
|
79
|
+
# @param [Hash] options: Passed to the underlying query. The :range_key option is required whenever the table has a range key,
|
80
|
+
# unless multiple ids are passed in.
|
81
|
+
#
|
82
|
+
# @since 0.2.0
|
83
|
+
def read(table, ids, options = {})
|
84
|
+
range_key = options.delete(:range_key)
|
85
|
+
|
86
|
+
if ids.respond_to?(:each)
|
87
|
+
ids = ids.collect{|id| range_key ? [id, range_key] : id}
|
88
|
+
batch_get_item({table => ids}, options)
|
89
|
+
else
|
90
|
+
options[:range_key] = range_key if range_key
|
91
|
+
get_item(table, ids, options)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# Delete an item from a table.
|
96
|
+
#
|
97
|
+
# @param [String] table the name of the table to write the object to
|
98
|
+
# @param [Array] ids to delete, can also be a string of just one id
|
99
|
+
# @param [Array] range_key of the record to delete, can also be a string of just one range_key
|
100
|
+
#
|
101
|
+
def delete(table, ids, options = {})
|
102
|
+
range_key = options[:range_key] #array of range keys that matches the ids passed in
|
103
|
+
if ids.respond_to?(:each)
|
104
|
+
if range_key.respond_to?(:each)
|
105
|
+
#turn ids into array of arrays each element being hash_key, range_key
|
106
|
+
ids = ids.each_with_index.map{|id,i| [id,range_key[i]]}
|
107
|
+
else
|
108
|
+
ids = range_key ? [[ids, range_key]] : ids
|
109
|
+
end
|
110
|
+
|
111
|
+
batch_delete_item(table => ids)
|
112
|
+
else
|
113
|
+
delete_item(table, ids, options)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# Scans a table. Generally quite slow; try to avoid using scan if at all possible.
|
118
|
+
#
|
119
|
+
# @param [String] table the name of the table to write the object to
|
120
|
+
# @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan
|
121
|
+
#
|
122
|
+
# @since 0.2.0
|
123
|
+
def scan(table, query, opts = {})
|
124
|
+
benchmark('Scan', table, query) {adapter.scan(table, query, opts)}
|
125
|
+
end
|
126
|
+
|
127
|
+
def create_table(table_name, key, options = {})
|
128
|
+
if !tables.include?(table_name)
|
129
|
+
benchmark('Create Table') { adapter.create_table(table_name, key, options) }
|
130
|
+
tables << table_name
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
[:batch_get_item, :delete_item, :delete_table, :get_item, :list_tables, :put_item].each do |m|
|
135
|
+
# Method delegation with benchmark to the underlying adapter. Faster than relying on method_missing.
|
136
|
+
#
|
137
|
+
# @since 0.2.0
|
138
|
+
define_method(m) do |*args|
|
139
|
+
benchmark("#{m.to_s}", args) {adapter.send(m, *args)}
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Delegate all methods that aren't defind here to the underlying adapter.
|
144
|
+
#
|
145
|
+
# @since 0.2.0
|
146
|
+
def method_missing(method, *args, &block)
|
147
|
+
return benchmark(method, *args) {adapter.send(method, *args, &block)} if adapter.respond_to?(method)
|
148
|
+
super
|
149
|
+
end
|
150
|
+
|
151
|
+
# Query the DynamoDB table. This employs DynamoDB's indexes so is generally faster than scanning, but is
|
152
|
+
# only really useful for range queries, since it can only find by one hash key at once. Only provide
|
153
|
+
# one range key to the hash.
|
154
|
+
#
|
155
|
+
# @param [String] table_name the name of the table
|
156
|
+
# @param [Hash] opts the options to query the table with
|
157
|
+
# @option opts [String] :hash_value the value of the hash key to find
|
158
|
+
# @option opts [Range] :range_value find the range key within this range
|
159
|
+
# @option opts [Number] :range_greater_than find range keys greater than this
|
160
|
+
# @option opts [Number] :range_less_than find range keys less than this
|
161
|
+
# @option opts [Number] :range_gte find range keys greater than or equal to this
|
162
|
+
# @option opts [Number] :range_lte find range keys less than or equal to this
|
163
|
+
#
|
164
|
+
# @return [Array] an array of all matching items
|
165
|
+
#
|
166
|
+
def query(table_name, opts = {})
|
167
|
+
adapter.query(table_name, opts)
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def self.adapter_plugin_class
|
173
|
+
unless Dynamoid.const_defined?(:AdapterPlugin) && Dynamoid::AdapterPlugin.const_defined?(Dynamoid::Config.adapter.camelcase)
|
174
|
+
require "dynamoid/adapter_plugin/#{Dynamoid::Config.adapter}"
|
175
|
+
end
|
176
|
+
|
177
|
+
Dynamoid::AdapterPlugin.const_get(Dynamoid::Config.adapter.camelcase)
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|