dynamoid-edge 1.1.0
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.
- 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
|