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.
@@ -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