forked from Veraticus/Dynamoid
-
Notifications
You must be signed in to change notification settings - Fork 1
/
local.rb
196 lines (176 loc) · 7.28 KB
/
local.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
module Dynamoid
module Adapter
# This gimpy hash construct should be equivalent to Amazon's actual DynamoDB, for offline development.
# All tests pass with either this or connecting to the real DynamoDB, and that's good enough for me.
module Local
extend self
# The hash holding all of our data.
#
# @return [Hash] a hash of raw values
#
# @since 0.2.0
def data
@data ||= {}
end
# A convenience method for testing that destroys all table data without destroying their structure.
#
# @since 0.2.0
def reset_data
self.data.each {|k, v| v[:data] = {}}
end
# Get many items at once from the hash.
#
# @example Retrieve IDs 1 and 2 from the table testtable
# Dynamoid::Adapter::Local.batch_get_item('table1' => ['1', '2'])
#
# @param [Hash] options the hash of tables and IDs to retrieve
#
# @return [Hash] a hash where keys are the table names and the values are the retrieved items
#
# @since 0.2.0
def batch_get_item(options)
Hash.new { |h, k| h[k] = Array.new }.tap do |hash|
options.each do |table_name, keys|
table = data[table_name]
if table and table[:range_key]
Array(keys).each do |hash_key, range_key|
hash[table_name] << get_item(table_name, hash_key, :range_key => range_key)
end
else
Array(keys).each do |key|
hash[table_name] << get_item(table_name, key)
end
end
end
end
end
# Create a table.
#
# @param [String] table_name the name of the table to create
# @param [Symbol] key the table's primary key (defaults to :id)
# @param [Hash] options provide a range_key here if you want one for the table
#
# @since 0.2.0
def create_table(table_name, key, options = {})
range_key = options[:range_key] && options[:range_key].keys.first
data[table_name] = {:hash_key => key, :range_key => range_key, :data => {}}
end
# Removes an item from the hash.
#
# @param [String] table_name the name of the table
# @param [String] key the hash key of the item to delete
# @param [Number] range_key the range key of the item to delete, required if the table has a composite key
#
# @since 0.2.0
def delete_item(table_name, key, options = {})
range_key = options.delete(:range_key)
data[table_name][:data].delete("#{key}.#{range_key}")
end
# Deletes an entire table from the hash.
#
# @param [String] table_name the name of the table to destroy
#
# @since 0.2.0
def delete_table(table_name)
data.delete(table_name)
end
# @todo Add a DescribeTable method.
# Fetches an item from the hash.
#
# @param [String] table_name the name of the table
# @param [String] key the hash key of the item to find
# @param [Number] range_key the range key of the item to find, required if the table has a composite key
#
# @return [Hash] a hash representing the raw item
#
# @since 0.2.0
def get_item(table_name, key, options = {})
range_key = options[:range_key]
if data[table_name][:data]
data[table_name][:data]["#{key}.#{range_key}"]
else
nil
end
end
# List all tables on DynamoDB.
#
# @since 0.2.0
def list_tables
data.keys
end
# Persists an item in the hash.
#
# @param [String] table_name the name of the table
# @param [Object] object a hash or Dynamoid object to persist
#
# @since 0.2.0
def put_item(table_name, object)
table = data[table_name]
table[:data][object[table[:hash_key]]]
table[:data]["#{object[table[:hash_key]]}.#{object[table[:range_key]]}"] = object.delete_if{|k, v| v.nil? || (v.respond_to?(:empty?) && v.empty?)}
rescue
raise data.inspect
end
# Query the hash.
#
# @param [String] table_name the name of the table
# @param [Hash] opts the options to query the table with
# @option opts [String] :hash_value the value of the hash key to find
# @option opts [Range] :range_value find the range key within this range
# @option opts [Number] :range_greater_than find range keys greater than this
# @option opts [Number] :range_less_than find range keys less than this
# @option opts [Number] :range_gte find range keys greater than or equal to this
# @option opts [Number] :range_lte find range keys less than or equal to this
#
# @return [Array] an array of all matching items
#
# @since 0.2.0
def query(table_name, opts = {})
id = opts[:hash_value]
hash_key = data[table_name][:hash_key]
range_key = data[table_name][:range_key]
results = if opts[:range_value]
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && opts[:range_value].include?(v[range_key])}
elsif opts[:range_greater_than]
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && v[range_key] > opts[:range_greater_than]}
elsif opts[:range_less_than]
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && v[range_key] < opts[:range_less_than]}
elsif opts[:range_gte]
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && v[range_key] >= opts[:range_gte]}
elsif opts[:range_lte]
data[table_name][:data].values.find_all{|v| v[hash_key] == id && !v[range_key].nil? && v[range_key] <= opts[:range_lte]}
else
data[table_name][:data].values.find_all{|v| v[hash_key] == id}
end
results = drop_till_start(results, opts[:next_token], range_key, hash_key)
results = results.take(opts[:limit]) if opts[:limit]
results
end
# Scan the hash.
#
# @param [String] table_name the name of the table
# @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan
#
# @return [Array] an array of all matching items
#
# @since 0.2.0
def scan(table_name, scan_hash, opts = {})
return [] if data[table_name].nil?
results = data[table_name][:data].values.flatten.select{|d| scan_hash.all?{|k, v| !d[k].nil? && d[k] == v}}
results = drop_till_start(results, opts[:next_token], data[table_name][:range_key], data[table_name][:hash_key])
results = results.take(opts[:limit]) if opts[:limit]
results
end
def drop_till_start(results, next_token, range_key, hash_key)
return results unless next_token
hash_value = next_token[:hash_key_element].values.first
range_value = next_token[:range_key_element].values.first if next_token[:range_key_element]
results = results.drop_while do |r|
(r[hash_key] != hash_value or r[range_key] != range_value)
end.drop(1)
end
# @todo Add an UpdateItem method.
# @todo Add an UpdateTable method.
end
end
end