mince_dynamo_db 1.3.1 → 2.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+
6
+ # uncomment this line if your project needs to run something other than `rake`:
7
+ #script: bundle exec rspec spec/units
data/README.md CHANGED
@@ -7,7 +7,7 @@ Provides a very light weight interface for storing and retreiving information to
7
7
  The motivation behind this is so your application is not tightly tied to a specific database. As your application grows you may need to upgrade to a different database or pull specific models to a different persistence strategy.
8
8
 
9
9
  [@github](https://github.com/coffeencoke/mince_dynamo_db)
10
- [@rubygems (not yet published)](#)
10
+ [@rubygems](https://rubygems.org/gems/mince_dynamo_db)
11
11
 
12
12
  # How to use
13
13
 
@@ -15,13 +15,13 @@ view the [example mince rails app](https://github.com/coffeencoke/mince_rails_ex
15
15
 
16
16
  <pre>
17
17
  # Add a book to the books collection
18
- MinceDynamoDb::DataStore.add 'books', title: 'The World In Photographs', publisher: 'National Geographic'
18
+ MinceDynamoDb::DataStore.instance.add 'books', title: 'The World In Photographs', publisher: 'National Geographic'
19
19
 
20
20
  # Retrieve all records from the books collection
21
- MinceDynamoDb::DataStore.find_all 'books'
21
+ MinceDynamoDb::DataStore.instance.find_all 'books'
22
22
 
23
23
  # Replace a specific book
24
- MinceDynamoDb::DataStore.replace 'books', id: 1, title: 'A World In Photographs', publisher: 'National Geographic'
24
+ MinceDynamoDb::DataStore.instance.replace 'books', id: 1, title: 'A World In Photographs', publisher: 'National Geographic'
25
25
  </pre>
26
26
 
27
27
  View the docs for MinceDynamoDb::DataStore for all methods available.
data/lib/dynamo_db.rb ADDED
@@ -0,0 +1,10 @@
1
+ module Mince
2
+ module DynamoDb
3
+ require_relative 'dynamo_db/config'
4
+ require_relative 'dynamo_db/connection'
5
+ require_relative 'dynamo_db/data_sanitizer'
6
+ require_relative 'dynamo_db/data_store'
7
+ require_relative 'dynamo_db/interface'
8
+ require_relative 'dynamo_db/version'
9
+ end
10
+ end
@@ -0,0 +1,56 @@
1
+ module Mince # :nodoc:
2
+ module DynamoDb # :nodoc:
3
+ require 'singleton'
4
+ require 'aws'
5
+
6
+ # = DynamoDb Config
7
+ #
8
+ # Config specifies the configuration settings
9
+ #
10
+ # @author Matt Simpson
11
+ class Config
12
+ include Singleton
13
+
14
+ # Returns the primary key identifier for records. This is necessary because not all databases use the same
15
+ # primary key.
16
+ #
17
+ # @return [Symbol] the name of the primary key field.
18
+ def self.primary_key
19
+ instance.primary_key
20
+ end
21
+
22
+ # Returns the secret access key used for authenticating with Amazon's DynamoDB service
23
+ #
24
+ # Can either be set explicitely, or uses AWS.config by default
25
+ def self.secret_access_key
26
+ instance.secret_access_key
27
+ end
28
+
29
+ def self.access_key_id
30
+ instance.access_key_id
31
+ end
32
+
33
+ attr_reader :primary_key
34
+
35
+ def initialize
36
+ @primary_key = :id
37
+ end
38
+
39
+ def access_key_id
40
+ AWS.config.access_key_id
41
+ end
42
+
43
+ def access_key_id=(id)
44
+ AWS.config.access_key_id = id
45
+ end
46
+
47
+ def secret_access_key
48
+ AWS.config.secret_access_key
49
+ end
50
+
51
+ def secret_access_key=(key)
52
+ AWS.config.secret_access_key = key
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,21 @@
1
+ module Mince
2
+ module DynamoDb
3
+ require 'singleton'
4
+ require_relative 'config'
5
+ require 'aws/dynamo_db'
6
+
7
+ class Connection
8
+ include Singleton
9
+
10
+ def self.connection
11
+ instance.connection
12
+ end
13
+
14
+ attr_accessor :connection
15
+
16
+ def connection
17
+ @connection ||= AWS::DynamoDB.new(access_key_id: Config.access_key_id, secret_access_key: Config.secret_access_key)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module Mince
2
+ module DynamoDb
3
+ module DataSanitizer
4
+ def self.prepare_field_for_storage(field)
5
+ field.is_a?(Numeric) ? field : field.to_s
6
+ end
7
+
8
+ def self.prepare_hash_for_storage(hash)
9
+ hash.each{|key, value| hash[key] = prepare_field_for_storage(value) }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,44 @@
1
+ module Mince
2
+ module DynamoDb # :nodoc:
3
+ require 'singleton'
4
+ require_relative 'connection'
5
+
6
+ class DataStore
7
+ include Singleton
8
+
9
+ # Returns the collection, or table, for a given collection name
10
+ #
11
+ # The schema must be loaded before any queries are made against a collection
12
+ # There are a couple ways to do this, from digging in their documentation about
13
+ # this topic. One is to have the schema loaded in memory, which is not recommended
14
+ # for production code, the other way is to call hash_key :) not sure why this works.
15
+ #
16
+ # @param [String] collection_name the name of the collection
17
+ # @return [AWS::DynamoDB::Table] the AWS::DynamoDB::Table for the given collection_name
18
+ def self.collection(collection_name)
19
+ collections[collection_name.to_s].tap do |c|
20
+ c.hash_key unless c.schema_loaded?
21
+ end
22
+ end
23
+
24
+ # Returns the database object which comes from MinceDynamoDb::Connection
25
+ def self.db
26
+ instance.db
27
+ end
28
+
29
+ def self.collections
30
+ db.tables
31
+ end
32
+
33
+ def self.items(collection_name)
34
+ collection(collection_name).items
35
+ end
36
+
37
+ attr_accessor :db
38
+
39
+ def db
40
+ @db ||= Connection.connection
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,232 @@
1
+ module Mince
2
+ module DynamoDb # :nodoc:
3
+ require 'digest'
4
+ require 'active_support/hash_with_indifferent_access'
5
+ require_relative 'data_store'
6
+ require_relative 'data_sanitizer'
7
+ require_relative 'config'
8
+
9
+ module Interface
10
+ # Not yet implemented
11
+ def self.update_field_with_value(*args)
12
+ raise %(The method `MinceDynamoDb::DataStore.singleton.update_field_with_value` is not implemented, you should implement it for us!)
13
+ end
14
+
15
+ def self.delete_field(collection_name, field)
16
+ raise %(The method `MinceDynamoDb::DataStore.singleton.delete_field` is not implemented, you should implement it for us!)
17
+ end
18
+
19
+ def self.delete_collection(collection_name)
20
+ raise %(The method `MinceDynamoDb::DataStore.singleton.delete_collection` is not implemented, you should implement it for us!)
21
+ end
22
+
23
+ def self.increment_field_by_amount(collection_name, id, field_name, amount)
24
+ raise %(The method `MinceDynamoDb::DataStore.singleton.increment_field_by_amount` is not implemented, you should implement it for us!)
25
+ end
26
+
27
+
28
+
29
+ # Returns the primary key identifier for records. This is necessary because not all databases use the same
30
+ # primary key.
31
+ #
32
+ # @return [String] the name of the primary key field.
33
+ def self.primary_key_identifier
34
+ Config.primary_key
35
+ end
36
+
37
+ # Generates a unique ID for a database record
38
+ #
39
+ # @note This is necessary because different databases use different types of primary key values. Thus, each mince
40
+ # implementation must define how to generate a unique id.
41
+ #
42
+ # @param [#to_s] salt any object that responds to #to_s
43
+ # @return [String] A unique id based on the salt and the current time
44
+ def self.generate_unique_id(salt)
45
+ Digest::SHA256.hexdigest("#{Time.current.utc}#{salt}")[0..6]
46
+ end
47
+
48
+ # Inserts one record into a collection.
49
+ #
50
+ # @param [String] collection_name the name of the collection
51
+ # @param [Hash] hash a hash of data to be added to the collection
52
+ def self.add(collection_name, hash)
53
+ hash.delete_if{|k,v| v.nil? }
54
+ items(collection_name).create(sanitized_hash(hash))
55
+ end
56
+
57
+ # Replaces a record in the collection based on the primary key's value. The hash must contain a key, defined
58
+ # by the +primary_key_identifier+ method, with a value. If a record in the data store is found with that key and
59
+ # value, the entire record will be replaced with the given hash.
60
+ #
61
+ # @param [String] collection_name the name of the collection
62
+ # @param [Hash] hash a hash to replace the record in the collection with
63
+ def self.replace(collection_name, hash)
64
+ items(collection_name).put(sanitized_hash(hash))
65
+ end
66
+
67
+ # Gets all records that have the value for a given key.
68
+ #
69
+ # @param [String] collection_name the name of the collection
70
+ # @param [String] key the key, or field, to get a record for
71
+ # @param [*] value the value to get a record for
72
+ # @return [Array] an array of records that match the key and value
73
+ def self.get_all_for_key_with_value(collection_name, key, value)
74
+ get_by_params(collection_name, key => value)
75
+ end
76
+
77
+ # Gets the first record that has the value for a given key.
78
+ #
79
+ # @param [String] collection_name the name of the collection
80
+ # @param [String] key the key to find a record by
81
+ # @param [String] value the value to find a record by
82
+ # @return [Hash] a hash for the record found by the key and value in the collection
83
+ def self.get_for_key_with_value(collection_name, key, value)
84
+ get_all_for_key_with_value(collection_name, key.to_s, value).first
85
+ end
86
+
87
+ # Gets all records that have all of the keys and values in the given hash.
88
+ #
89
+ # @param [String] collection_name the name of the collection
90
+ # @param [Hash] hash a hash to get a record for
91
+ # @return [Array] an array of all records matching the given hash for the collection
92
+ def self.get_by_params(collection_name, hash)
93
+ hash = HashWithIndifferentAccess.new(hash)
94
+ array_to_hash(items(collection_name).where(hash).select)
95
+ end
96
+
97
+ # Gets all records for a collection
98
+ #
99
+ # @param [String] collection_name the name of the collection
100
+ # @return [Array] all records for the given collection name
101
+ def self.find_all(collection_name)
102
+ array_to_hash(items(collection_name).select)
103
+ end
104
+
105
+ # Gets a record matching a key and value
106
+ #
107
+ # @param [String] collection_name the name of the collection
108
+ # @param [String] key the key to find a record by
109
+ # @param [*] value a value the find a record by
110
+ # @return [Hash] a record that matches the given key and value
111
+ def self.find(collection_name, key, value)
112
+ get_for_key_with_value(collection_name, key, value)
113
+ end
114
+
115
+ # Pushes a value to a record's key that is an array
116
+ #
117
+ # @param [String] collection_name the name of the collection
118
+ # @param [String] identifying_key the field used to find the record
119
+ # @param [*] identifying_value the value used to find the record
120
+ # @param [String] array_key the field to push an array to
121
+ # @param [*] value_to_push the value to push to the array
122
+ def self.push_to_array(collection_name, identifying_key, identifying_value, array_key, value_to_push)
123
+ item = items(collection_name).where(identifying_key.to_s => identifying_value).first
124
+ item.attributes.add(array_key => [sanitized_field(value_to_push)])
125
+ end
126
+
127
+ # Removes a value from a record's key that is an array
128
+ #
129
+ # @param [String] collection_name the name of the collection
130
+ # @param [String] identifying_key the field used to find the record
131
+ # @param [*] identifying_value the value used to find the record
132
+ # @param [String] array_key the field to push an array from
133
+ # @param [*] value_to_remove the value to remove from the array
134
+ def self.remove_from_array(collection_name, identifying_key, identifying_value, array_key, value_to_remove)
135
+ item = items(collection_name).where(identifying_key.to_s => identifying_value).select.first
136
+ item.attributes.delete(array_key => [value_to_remove])
137
+ end
138
+
139
+ # Returns all records where the given key contains any of the values provided
140
+ #
141
+ # @param [String] collection_name the name of the collection
142
+ # @param [String] key the key to find the record by
143
+ # @param [Array] values an array of values that the record could contain
144
+ # @return [Array] all records that contain any of the values given
145
+ def self.containing_any(collection_name, key, values)
146
+ array_to_hash items(collection_name).where(key.to_sym).in(values).select
147
+ end
148
+
149
+ # Returns all records where the given key contains the given value
150
+ #
151
+ # @param [String] collection_name the name of the collection
152
+ # @param [String] key the key to find records by
153
+ # @param [*] value the value to find a record by
154
+ # @return [Array] all records where the key contains the given value
155
+ def self.array_contains(collection_name, key, value)
156
+ array_to_hash items(collection_name).where(key.to_sym).contains(value).select
157
+ end
158
+
159
+ # Deletes a record that matches the given criteria from the data store.
160
+ def self.delete_by_params(collection_name, params)
161
+ item = items(collection_name).where(params).select.first
162
+ item.delete
163
+ end
164
+
165
+ # Clears the data store.
166
+ # Mainly used for rolling back the data store in tests.
167
+ def self.clear
168
+ db.tables.each do |t|
169
+ t.hash_key unless t.schema_loaded? # to load the schema
170
+ t.items.each do |i|
171
+ i.delete
172
+ end
173
+ end
174
+ end
175
+
176
+ def self.set_data(data)
177
+ clear
178
+
179
+ data.each do |key, records|
180
+ records.each do |record|
181
+ add key, record
182
+ end
183
+ end
184
+ end
185
+
186
+ # Returns the collection, or table, for a given collection name
187
+ #
188
+ # The schema must be loaded before any queries are made against a collection
189
+ # There are a couple ways to do this, from digging in their documentation about
190
+ # this topic. One is to have the schema loaded in memory, which is not recommended
191
+ # for production code, the other way is to call hash_key :) not sure why this works.
192
+ #
193
+ # @param [String] collection_name the name of the collection
194
+ # @return [AWS::DynamoDB::Table] the AWS::DynamoDB::Table for the given collection_name
195
+ def self.collection(collection_name)
196
+ DataStore.collection(collection_name)
197
+ end
198
+
199
+ def self.sanitized_field(field)
200
+ DataSanitizer.prepare_field_for_storage(field)
201
+ end
202
+
203
+ def self.sanitized_hash(hash)
204
+ DataSanitizer.prepare_hash_for_storage(hash)
205
+ end
206
+
207
+ # Takes a DynamoDB item and returns the attributes of that item as a hash
208
+ def self.to_hash(item)
209
+ item.attributes if item
210
+ end
211
+
212
+ # Takes an array of DynamoDB items and returns the attributes of each item as a hash.
213
+ # calls
214
+ def self.array_to_hash(array)
215
+ array.map{|a| to_hash(a) }
216
+ end
217
+
218
+ # Returns the database object which comes from MinceDynamoDb::Connection
219
+ def self.db
220
+ DataStore.db
221
+ end
222
+
223
+ def self.collections
224
+ DataStore.collections
225
+ end
226
+
227
+ def self.items(collection_name)
228
+ DataStore.items(collection_name)
229
+ end
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,22 @@
1
+ module Mince
2
+ module DynamoDb
3
+ module Version
4
+ def self.major
5
+ 2
6
+ end
7
+
8
+ def self.minor
9
+ 0
10
+ end
11
+
12
+ def self.patch
13
+ "0.pre.1"
14
+ end
15
+ end
16
+
17
+ def self.version
18
+ [Version.major, Version.minor, Version.patch].join(".")
19
+ end
20
+ end
21
+ end
22
+
@@ -1,10 +1,10 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  $:.push File.expand_path("../lib", __FILE__)
3
- require "mince_dynamo_db/version"
3
+ require "dynamo_db/version"
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "mince_dynamo_db"
7
- s.version = MinceDynamoDb::VERSION
7
+ s.version = Mince::DynamoDb.version
8
8
  s.authors = ["Matt Simpson"]
9
9
  s.email = ["matt@railsgrammer.com"]
10
10
  s.homepage = "https://github.com/coffeencoke/mince_dynamo_db"
@@ -21,8 +21,11 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency 'aws-sdk', "~> 1.3.7"
22
22
  s.add_dependency 'activesupport', "~> 3.0"
23
23
 
24
+ s.add_development_dependency('rake', '~> 0.9')
24
25
  s.add_development_dependency "rspec", "~> 2.8.0"
25
26
  s.add_development_dependency "guard-rspec", "~> 0.6.0"
26
27
  s.add_development_dependency "yard", "~> 0.7.5"
27
28
  s.add_development_dependency "redcarpet", "~> 2.1.1"
29
+ s.add_development_dependency "fake_dynamo"
30
+ s.add_development_dependency "mince", "~> 2.0.0.pre.2"
28
31
  end
@@ -0,0 +1,18 @@
1
+ require_relative '../../lib/dynamo_db'
2
+ require 'mince/shared_examples/interface_example'
3
+
4
+ describe 'Mince Interface with DynamoDb' do
5
+ pending do
6
+ before do
7
+ AWS.config(
8
+ :use_ssl => false,
9
+ :dynamo_db_endpoint => 'localhost',
10
+ :access_key_id => "xxx",
11
+ :secret_access_key => "xxx"
12
+ )
13
+ Mince::Config.interface = Mince::DynamoDb::Interface
14
+ end
15
+
16
+ it_behaves_like 'a mince interface'
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ require_relative '../../lib/dynamo_db/config'
2
+
3
+ describe Mince::DynamoDb::Config do
4
+ let(:secret_access_key) { mock }
5
+ let(:access_key_id) { mock }
6
+
7
+ before do
8
+ AWS.config(access_key_id: access_key_id, secret_access_key: secret_access_key)
9
+ end
10
+
11
+ it 'uses "id" as the primary key for the database' do
12
+ described_class.primary_key.should == :id
13
+ end
14
+
15
+ it "has the credentials for using Amazon's Dynamo DB service" do
16
+ described_class.secret_access_key.should == secret_access_key
17
+ described_class.access_key_id.should == access_key_id
18
+ end
19
+ end
@@ -1,6 +1,6 @@
1
- require_relative '../../lib/mince_dynamo_db/connection'
1
+ require_relative '../../lib/dynamo_db/connection'
2
2
 
3
- describe MinceDynamoDb::Connection do
3
+ describe Mince::DynamoDb::Connection do
4
4
  subject { described_class.instance }
5
5
 
6
6
  let(:connection) { mock 'an amazon dynamo db object' }
@@ -9,7 +9,7 @@ describe MinceDynamoDb::Connection do
9
9
  let(:aws_config) { mock 'aws config object', secret_access_key: secret_access_key, access_key_id: access_key_id }
10
10
 
11
11
  before do
12
- AWS.stub(config: aws_config)
12
+ Mince::DynamoDb::Config.stub(access_key_id: access_key_id, secret_access_key: secret_access_key)
13
13
  end
14
14
 
15
15
  it 'has a dynamo db connection' do
@@ -17,4 +17,4 @@ describe MinceDynamoDb::Connection do
17
17
 
18
18
  subject.connection.should == connection
19
19
  end
20
- end
20
+ end
@@ -1,8 +1,8 @@
1
- require_relative '../../lib/mince_dynamo_db/data_sanitizer'
1
+ require_relative '../../lib/dynamo_db/data_sanitizer'
2
2
 
3
3
  require 'date'
4
4
 
5
- describe MinceDynamoDb::DataSanitizer do
5
+ describe Mince::DynamoDb::DataSanitizer do
6
6
  it 'converts a Time object to a string' do
7
7
  value = Time.now
8
8
 
@@ -0,0 +1,55 @@
1
+ require_relative '../../lib/dynamo_db/data_store'
2
+
3
+ describe Mince::DynamoDb::DataStore do
4
+ let(:db) { mock 'db connection', tables: collections }
5
+ let(:collections) { { collection_name => collection } }
6
+ let(:collection) { mock 'some collection', items: items, schema_loaded?: true }
7
+ let(:collection_name) { 'some_collection_name'}
8
+ let(:items) { mock 'items' }
9
+
10
+ before do
11
+ described_class.instance.db = db # do this each time because it's a singleton
12
+ end
13
+
14
+ describe 'when getting a collection' do
15
+ context 'when the schema has not been loaded' do
16
+ before do
17
+ collection.stub(schema_loaded?: false, hash_key: nil)
18
+ end
19
+
20
+ it 'loads the schema' do
21
+ collection.should_receive(:hash_key)
22
+
23
+ described_class.collection(collection_name)
24
+ end
25
+
26
+ it 'returns the collection' do
27
+ described_class.collection(collection_name).should == collection
28
+ end
29
+ end
30
+
31
+ context 'when the schema has been loaded' do
32
+ it 'does not load the schema' do
33
+ collection.should_not_receive(:hash_key)
34
+
35
+ described_class.collection(collection_name)
36
+ end
37
+
38
+ it 'returns the collection' do
39
+ described_class.collection(collection_name).should == collection
40
+ end
41
+ end
42
+ end
43
+
44
+ it 'can get the items for a given collection' do
45
+ described_class.items(collection_name).should == items
46
+ end
47
+
48
+ it 'can return the db' do
49
+ described_class.db.should == db
50
+ end
51
+
52
+ it 'can get all collections' do
53
+ described_class.collections.should == collections
54
+ end
55
+ end
@@ -1,11 +1,10 @@
1
- require_relative '../../lib/mince_dynamo_db/data_store'
2
-
3
- describe MinceDynamoDb::DataStore do
4
- subject { described_class.instance }
1
+ require_relative '../../lib/dynamo_db/interface'
5
2
 
3
+ describe Mince::DynamoDb::Interface do
6
4
  let(:db) { mock 'dynamo db connection', tables: { collection_name => collection } }
7
5
  let(:mince_dynamo_db_connection) { mock 'mince dynamo db connection', connection: db }
8
6
  let(:collection) { mock 'some collection', items: items, schema_loaded?: true }
7
+ let(:collections) { mock }
9
8
  let(:collection_name) { 'some_collection_name'}
10
9
  let(:primary_key) { mock 'primary key'}
11
10
  let(:mock_id) { mock 'id' }
@@ -18,16 +17,10 @@ describe MinceDynamoDb::DataStore do
18
17
  let(:items) { mock 'items' }
19
18
 
20
19
  before do
21
- MinceDynamoDb::DataSanitizer.stub(:prepare_hash_for_storage).with(data).and_return(sanitized_data)
22
- MinceDynamoDb::Connection.stub(:instance => mince_dynamo_db_connection)
23
- end
24
-
25
- it 'uses the correct collection' do
26
- subject.collection(collection_name).should == collection
27
- end
28
-
29
- it 'has a primary key identifier' do
30
- described_class.primary_key_identifier.should == 'id'
20
+ Mince::DynamoDb::DataStore.stub(:items).with(collection_name).and_return(items)
21
+ Mince::DynamoDb::DataStore.stub(:collection).with(collection_name).and_return(collection)
22
+ Mince::DynamoDb::DataStore.stub(collections: collections, db: db)
23
+ Mince::DynamoDb::DataSanitizer.stub(:prepare_hash_for_storage).with(data).and_return(sanitized_data)
31
24
  end
32
25
 
33
26
  describe "Generating a primary key" do
@@ -50,21 +43,21 @@ describe MinceDynamoDb::DataStore do
50
43
  it 'can write to the collection' do
51
44
  items.should_receive(:create).with(sanitized_data).and_return(return_data)
52
45
 
53
- subject.add(collection_name, data).should == return_data
46
+ described_class.add(collection_name, data).should == return_data
54
47
  end
55
48
 
56
49
  it 'can read from the collection' do
57
50
  item_attributes = mock 'attributes for an item'
58
51
  item = mock 'item', attributes: item_attributes
59
- collection.stub(items: [item])
52
+ items.should_receive(:select).and_return([item])
60
53
 
61
- subject.find_all(collection_name).should == [item_attributes]
54
+ described_class.find_all(collection_name).should == [item_attributes]
62
55
  end
63
56
 
64
57
  it 'can replace a record' do
65
58
  items.should_receive(:put).with(sanitized_data)
66
59
 
67
- subject.replace(collection_name, data)
60
+ described_class.replace(collection_name, data)
68
61
  end
69
62
 
70
63
  it 'can get one document' do
@@ -73,7 +66,7 @@ describe MinceDynamoDb::DataStore do
73
66
 
74
67
  items.should_receive(:where).with(field => value).and_return([return_data])
75
68
 
76
- subject.find(collection_name, field, value).should == attributes
69
+ described_class.find(collection_name, field, value).should == attributes
77
70
  end
78
71
 
79
72
  it 'can delete a record that matches a criteria' do
@@ -81,7 +74,7 @@ describe MinceDynamoDb::DataStore do
81
74
  items.should_receive(:where).with(params).and_return([return_data])
82
75
  return_data.should_receive(:delete)
83
76
 
84
- subject.delete_by_params(collection_name, params)
77
+ described_class.delete_by_params(collection_name, params)
85
78
  end
86
79
 
87
80
  it 'can clear the data store' do
@@ -96,19 +89,19 @@ describe MinceDynamoDb::DataStore do
96
89
  item.should_receive(:delete)
97
90
  item2.should_receive(:delete)
98
91
 
99
- subject.clear
92
+ described_class.clear
100
93
  end
101
94
 
102
95
  it 'can get all records of a specific key value' do
103
96
  items.should_receive(:where).with("key" => "value").and_return([return_data])
104
97
 
105
- subject.get_all_for_key_with_value(collection_name, "key", "value").should == [attributes]
98
+ described_class.get_all_for_key_with_value(collection_name, "key", "value").should == [attributes]
106
99
  end
107
100
 
108
101
  it 'can get a record of a specific key value' do
109
102
  items.should_receive(:where).with({"key" => "value"}).and_return([return_data])
110
103
 
111
- subject.get_for_key_with_value(collection_name, "key", "value").should == attributes
104
+ described_class.get_for_key_with_value(collection_name, "key", "value").should == attributes
112
105
  end
113
106
 
114
107
  it 'can get all records where a value includes any of a set of values' do
@@ -116,7 +109,7 @@ describe MinceDynamoDb::DataStore do
116
109
  items.should_receive(:where).with(:key1).and_return(filter)
117
110
  filter.should_receive(:in).with([1,2,4]).and_return([return_data])
118
111
 
119
- subject.containing_any(collection_name, "key1", [1,2,4]).should == [attributes]
112
+ described_class.containing_any(collection_name, "key1", [1,2,4]).should == [attributes]
120
113
  end
121
114
 
122
115
  it 'can get all records where the array includes a value' do
@@ -124,23 +117,23 @@ describe MinceDynamoDb::DataStore do
124
117
  items.should_receive(:where).with(:key).and_return(filter)
125
118
  filter.should_receive(:contains).with('value').and_return([return_data])
126
119
 
127
- subject.array_contains(collection_name, "key", "value").should == [attributes]
120
+ described_class.array_contains(collection_name, "key", "value").should == [attributes]
128
121
  end
129
122
 
130
123
  it 'can push a value to an array for a specific record' do
131
124
  value_to_push = mock 'value to push to the record'
132
125
  sanitized_value_to_push = mock 'sanitized value to push to the record'
133
- MinceDynamoDb::DataSanitizer.stub(:prepare_field_for_storage).with(value_to_push).and_return(sanitized_value_to_push)
126
+ Mince::DynamoDb::DataSanitizer.stub(:prepare_field_for_storage).with(value_to_push).and_return(sanitized_value_to_push)
134
127
  items.should_receive(:where).with("key" => "value").and_return([return_data])
135
128
  attributes.should_receive(:add).with(:array_key => [sanitized_value_to_push])
136
129
 
137
- subject.push_to_array(collection_name, :key, "value", :array_key, value_to_push)
130
+ described_class.push_to_array(collection_name, :key, "value", :array_key, value_to_push)
138
131
  end
139
132
 
140
133
  it 'can remove a value from an array for a specific record' do
141
134
  items.should_receive(:where).with("key" => "value").and_return([return_data])
142
135
  attributes.should_receive(:delete).with(:array_key => ["value_to_remove"])
143
136
 
144
- subject.remove_from_array(collection_name, :key, "value", :array_key, "value_to_remove")
137
+ described_class.remove_from_array(collection_name, :key, "value", :array_key, "value_to_remove")
145
138
  end
146
139
  end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mince_dynamo_db
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
5
- prerelease:
4
+ version: 2.0.0.pre.1
5
+ prerelease: 6
6
6
  platform: ruby
7
7
  authors:
8
8
  - Matt Simpson
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-07 00:00:00.000000000 Z
12
+ date: 2012-11-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk
@@ -43,6 +43,22 @@ dependencies:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
45
  version: '3.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '0.9'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
46
62
  - !ruby/object:Gem::Dependency
47
63
  name: rspec
48
64
  requirement: !ruby/object:Gem::Requirement
@@ -107,6 +123,38 @@ dependencies:
107
123
  - - ~>
108
124
  - !ruby/object:Gem::Version
109
125
  version: 2.1.1
126
+ - !ruby/object:Gem::Dependency
127
+ name: fake_dynamo
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ - !ruby/object:Gem::Dependency
143
+ name: mince
144
+ requirement: !ruby/object:Gem::Requirement
145
+ none: false
146
+ requirements:
147
+ - - ~>
148
+ - !ruby/object:Gem::Version
149
+ version: 2.0.0.pre.2
150
+ type: :development
151
+ prerelease: false
152
+ version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
154
+ requirements:
155
+ - - ~>
156
+ - !ruby/object:Gem::Version
157
+ version: 2.0.0.pre.2
110
158
  description: Lightweight ORM for Amazon's DynamoDB with Ruby Apps
111
159
  email:
112
160
  - matt@railsgrammer.com
@@ -117,21 +165,25 @@ files:
117
165
  - .gitignore
118
166
  - .rspec
119
167
  - .rvmrc
168
+ - .travis.yml
120
169
  - Gemfile
121
170
  - Guardfile
122
171
  - README.md
123
172
  - Rakefile
124
- - lib/mince_dynamo_db.rb
125
- - lib/mince_dynamo_db/connection.rb
126
- - lib/mince_dynamo_db/data_sanitizer.rb
127
- - lib/mince_dynamo_db/data_store.rb
128
- - lib/mince_dynamo_db/version.rb
173
+ - lib/dynamo_db.rb
174
+ - lib/dynamo_db/config.rb
175
+ - lib/dynamo_db/connection.rb
176
+ - lib/dynamo_db/data_sanitizer.rb
177
+ - lib/dynamo_db/data_store.rb
178
+ - lib/dynamo_db/interface.rb
179
+ - lib/dynamo_db/version.rb
129
180
  - mince_dynamo_db.gemspec
130
- - spec/integration/persisting_to_dynamodb_test.rb
131
- - spec/lib/connection_spec.rb
132
- - spec/lib/data_sanitizer_spec.rb
133
- - spec/lib/data_store_spec.rb
134
- - spec/support/aws_example.yml
181
+ - spec/integration/mince_interface_spec.rb
182
+ - spec/units/config_spec.rb
183
+ - spec/units/connection_spec.rb
184
+ - spec/units/data_sanitizer_spec.rb
185
+ - spec/units/data_store_spec.rb
186
+ - spec/units/interface_spec.rb
135
187
  homepage: https://github.com/coffeencoke/mince_dynamo_db
136
188
  licenses: []
137
189
  post_install_message:
@@ -144,12 +196,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
144
196
  - - ! '>='
145
197
  - !ruby/object:Gem::Version
146
198
  version: '0'
199
+ segments:
200
+ - 0
201
+ hash: -4539691897538722192
147
202
  required_rubygems_version: !ruby/object:Gem::Requirement
148
203
  none: false
149
204
  requirements:
150
- - - ! '>='
205
+ - - ! '>'
151
206
  - !ruby/object:Gem::Version
152
- version: '0'
207
+ version: 1.3.1
153
208
  requirements: []
154
209
  rubyforge_project: mince_dynamo_db
155
210
  rubygems_version: 1.8.24
@@ -157,9 +212,10 @@ signing_key:
157
212
  specification_version: 3
158
213
  summary: Lightweight ORM for Amazon's DynamoDB with Ruby Apps
159
214
  test_files:
160
- - spec/integration/persisting_to_dynamodb_test.rb
161
- - spec/lib/connection_spec.rb
162
- - spec/lib/data_sanitizer_spec.rb
163
- - spec/lib/data_store_spec.rb
164
- - spec/support/aws_example.yml
215
+ - spec/integration/mince_interface_spec.rb
216
+ - spec/units/config_spec.rb
217
+ - spec/units/connection_spec.rb
218
+ - spec/units/data_sanitizer_spec.rb
219
+ - spec/units/data_store_spec.rb
220
+ - spec/units/interface_spec.rb
165
221
  has_rdoc:
@@ -1,3 +0,0 @@
1
- require 'mince_dynamo_db/connection'
2
- require 'mince_dynamo_db/data_store'
3
- require "mince_dynamo_db/version"
@@ -1,24 +0,0 @@
1
- require 'aws'
2
- require 'singleton'
3
-
4
- module MinceDynamoDb
5
- class Connection
6
- include Singleton
7
-
8
- attr_reader :connection
9
-
10
- def initialize
11
- @connection = AWS::DynamoDB.new access_key_id: access_key_id, secret_access_key: secret_access_key
12
- end
13
-
14
- private
15
-
16
- def access_key_id
17
- AWS.config.access_key_id
18
- end
19
-
20
- def secret_access_key
21
- AWS.config.secret_access_key
22
- end
23
- end
24
- end
@@ -1,11 +0,0 @@
1
- module MinceDynamoDb
2
- module DataSanitizer
3
- def self.prepare_field_for_storage(field)
4
- field.is_a?(Numeric) ? field : field.to_s
5
- end
6
-
7
- def self.prepare_hash_for_storage(hash)
8
- hash.each{|key, value| hash[key] = prepare_field_for_storage(value) }
9
- end
10
- end
11
- end
@@ -1,269 +0,0 @@
1
- require 'singleton'
2
- require 'digest'
3
- require 'active_support/hash_with_indifferent_access'
4
-
5
- require_relative 'connection'
6
- require_relative 'data_sanitizer'
7
-
8
- module MinceDynamoDb # :nodoc:
9
- # = Mince DynamoDb Data Store
10
- #
11
- # Mince DynamoDb Data Store stores and retrieves data from a DynamoDB table. It supports the same methods
12
- # as the following libraries:
13
- #
14
- # Mince::
15
- # A lightweight ruby library to store and retrieve data from MongoDB (https://github.com/asynchrony/mince)
16
- # HashyDb::
17
- # A lightweight ruby library to store and retrieve data from a hash in-memory. (https://github.com/asynchrony/hashy_db)
18
- #
19
- # Using this library offers more extensibility and growth for your application. If at any point in time
20
- # you want to support a different database for all, or even one, of your classes, you only need to implement
21
- # the few methods defined in this class.
22
- #
23
- # You can use the {https://github.com/asynchrony/mince_data_model Mince Data Model} as a helper library to
24
- # provide support in writing an application using these data persistance libraries.
25
- #
26
- # To use the Data Store, define your DynamoDB access_key_id and secret_access_key. If you are using rails
27
- # you can do this by creating a file at config/aws.yml with the following contents:
28
- #
29
- # development:
30
- # access_key_id: REPLACE_WITH_ACCESS_KEY_ID
31
- # secret_access_key: REPLACE_WITH_SECRET_ACCESS_KEY
32
- #
33
- # Otherwise, you can set the configurations like so:
34
- #
35
- # require 'aws'
36
- #
37
- # AWS.config.access_key_id = REPLACE_WITH_ACCESS_KEY_ID
38
- # AWS.config.secret_access_key = REPLACE_WITH_SECRET_ACCESS_KEY
39
- #
40
- # View the aws documentation for more details at http://docs.amazonwebservices.com/AWSRubySDK/latest/frames.html
41
- #
42
- # Once you have the settings configured, you can start storing and retrieving data:
43
- #
44
- # data_store = MinceDynamoDb::DataStore.instance
45
- # data_store.add 'fruits', id: '1', name: 'Shnawzberry', color: 'redish', quantity: '20'
46
- # data_store.get_for_key_with_value 'fruits', :color, 'redish'
47
- #
48
- # @author Matt Simpson
49
- class DataStore
50
- include Singleton
51
-
52
-
53
-
54
- # Not yet implemented
55
- def update_field_with_value(*args)
56
- raise %(The method `MinceDynamoDb::DataStore.singleton.update_field_with_value` is not implemented, you should implement it for us!)
57
- end
58
-
59
- def delete_field(collection_name, field)
60
- raise %(The method `MinceDynamoDb::DataStore.singleton.delete_field` is not implemented, you should implement it for us!)
61
- end
62
-
63
- def delete_collection(collection_name)
64
- raise %(The method `MinceDynamoDb::DataStore.singleton.delete_collection` is not implemented, you should implement it for us!)
65
- end
66
-
67
- def increment_field_by_amount(collection_name, id, field_name, amount)
68
- raise %(The method `MinceDynamoDb::DataStore.singleton.increment_field_by_amount` is not implemented, you should implement it for us!)
69
- end
70
-
71
-
72
-
73
- # Returns the primary key identifier for records. This is necessary because not all databases use the same
74
- # primary key.
75
- #
76
- # @return [String] the name of the primary key field.
77
- def self.primary_key_identifier
78
- 'id'
79
- end
80
-
81
- # Generates a unique ID for a database record
82
- #
83
- # @note This is necessary because different databases use different types of primary key values. Thus, each mince
84
- # implementation must define how to generate a unique id.
85
- #
86
- # @param [#to_s] salt any object that responds to #to_s
87
- # @return [String] A unique id based on the salt and the current time
88
- def self.generate_unique_id(salt)
89
- Digest::SHA256.hexdigest("#{Time.current.utc}#{salt}")[0..6]
90
- end
91
-
92
- # Inserts one record into a collection.
93
- #
94
- # @param [String] collection_name the name of the collection
95
- # @param [Hash] hash a hash of data to be added to the collection
96
- def add(collection_name, hash)
97
- hash.delete_if{|k,v| v.nil? }
98
- items(collection_name).create(sanitized_hash(hash))
99
- end
100
-
101
- # Replaces a record in the collection based on the primary key's value. The hash must contain a key, defined
102
- # by the +primary_key_identifier+ method, with a value. If a record in the data store is found with that key and
103
- # value, the entire record will be replaced with the given hash.
104
- #
105
- # @param [String] collection_name the name of the collection
106
- # @param [Hash] hash a hash to replace the record in the collection with
107
- def replace(collection_name, hash)
108
- items(collection_name).put(sanitized_hash(hash))
109
- end
110
-
111
- # Gets all records that have the value for a given key.
112
- #
113
- # @param [String] collection_name the name of the collection
114
- # @param [String] key the key, or field, to get a record for
115
- # @param [*] value the value to get a record for
116
- # @return [Array] an array of records that match the key and value
117
- def get_all_for_key_with_value(collection_name, key, value)
118
- get_by_params(collection_name, key => value)
119
- end
120
-
121
- # Gets the first record that has the value for a given key.
122
- #
123
- # @param [String] collection_name the name of the collection
124
- # @param [String] key the key to find a record by
125
- # @param [String] value the value to find a record by
126
- # @return [Hash] a hash for the record found by the key and value in the collection
127
- def get_for_key_with_value(collection_name, key, value)
128
- get_all_for_key_with_value(collection_name, key.to_s, value).first
129
- end
130
-
131
- # Gets all records that have all of the keys and values in the given hash.
132
- #
133
- # @param [String] collection_name the name of the collection
134
- # @param [Hash] hash a hash to get a record for
135
- # @return [Array] an array of all records matching the given hash for the collection
136
- def get_by_params(collection_name, hash)
137
- hash = HashWithIndifferentAccess.new(hash)
138
- array_to_hash(items(collection_name).where(hash).select)
139
- end
140
-
141
- # Gets all records for a collection
142
- #
143
- # @param [String] collection_name the name of the collection
144
- # @return [Array] all records for the given collection name
145
- def find_all(collection_name)
146
- array_to_hash(items(collection_name).select)
147
- end
148
-
149
- # Gets a record matching a key and value
150
- #
151
- # @param [String] collection_name the name of the collection
152
- # @param [String] key the key to find a record by
153
- # @param [*] value a value the find a record by
154
- # @return [Hash] a record that matches the given key and value
155
- def find(collection_name, key, value)
156
- get_for_key_with_value(collection_name, key, value)
157
- end
158
-
159
- # Pushes a value to a record's key that is an array
160
- #
161
- # @param [String] collection_name the name of the collection
162
- # @param [String] identifying_key the field used to find the record
163
- # @param [*] identifying_value the value used to find the record
164
- # @param [String] array_key the field to push an array to
165
- # @param [*] value_to_push the value to push to the array
166
- def push_to_array(collection_name, identifying_key, identifying_value, array_key, value_to_push)
167
- item = items(collection_name).where(identifying_key.to_s => identifying_value).first
168
- item.attributes.add(array_key => [sanitized_field(value_to_push)])
169
- end
170
-
171
- # Removes a value from a record's key that is an array
172
- #
173
- # @param [String] collection_name the name of the collection
174
- # @param [String] identifying_key the field used to find the record
175
- # @param [*] identifying_value the value used to find the record
176
- # @param [String] array_key the field to push an array from
177
- # @param [*] value_to_remove the value to remove from the array
178
- def remove_from_array(collection_name, identifying_key, identifying_value, array_key, value_to_remove)
179
- item = items(collection_name).where(identifying_key.to_s => identifying_value).select.first
180
- item.attributes.delete(array_key => [value_to_remove])
181
- end
182
-
183
- # Returns all records where the given key contains any of the values provided
184
- #
185
- # @param [String] collection_name the name of the collection
186
- # @param [String] key the key to find the record by
187
- # @param [Array] values an array of values that the record could contain
188
- # @return [Array] all records that contain any of the values given
189
- def containing_any(collection_name, key, values)
190
- array_to_hash items(collection_name).where(key.to_sym).in(values).select
191
- end
192
-
193
- # Returns all records where the given key contains the given value
194
- #
195
- # @param [String] collection_name the name of the collection
196
- # @param [String] key the key to find records by
197
- # @param [*] value the value to find a record by
198
- # @return [Array] all records where the key contains the given value
199
- def array_contains(collection_name, key, value)
200
- array_to_hash items(collection_name).where(key.to_sym).contains(value).select
201
- end
202
-
203
- # Deletes a record that matches the given criteria from the data store.
204
- def delete_by_params(collection_name, params)
205
- item = items(collection_name).where(params).select.first
206
- item.delete
207
- end
208
-
209
- # Clears the data store.
210
- # Mainly used for rolling back the data store in tests.
211
- def clear
212
- db.tables.each do |t|
213
- t.hash_key unless t.schema_loaded? # to load the schema
214
- t.items.each do |i|
215
- i.delete
216
- end
217
- end
218
- end
219
-
220
- # Returns the collection, or table, for a given collection name
221
- #
222
- # The schema must be loaded before any queries are made against a collection
223
- # There are a couple ways to do this, from digging in their documentation about
224
- # this topic. One is to have the schema loaded in memory, which is not recommended
225
- # for production code, the other way is to call hash_key :) not sure why this works.
226
- #
227
- # @param [String] collection_name the name of the collection
228
- # @return [AWS::DynamoDB::Table] the AWS::DynamoDB::Table for the given collection_name
229
- def collection(collection_name)
230
- db.tables[collection_name.to_s].tap do |c|
231
- c.hash_key unless c.schema_loaded?
232
- end
233
- end
234
-
235
- private
236
-
237
- def sanitized_field(field)
238
- DataSanitizer.prepare_field_for_storage(field)
239
- end
240
-
241
- def sanitized_hash(hash)
242
- DataSanitizer.prepare_hash_for_storage(hash)
243
- end
244
-
245
- # Takes a DynamoDB item and returns the attributes of that item as a hash
246
- def to_hash(item)
247
- item.attributes if item
248
- end
249
-
250
- # Takes an array of DynamoDB items and returns the attributes of each item as a hash.
251
- # calls
252
- def array_to_hash(array)
253
- array.map{|a| to_hash(a) }
254
- end
255
-
256
- # Returns the database object which comes from MinceDynamoDb::Connection
257
- def db
258
- MinceDynamoDb::Connection.instance.connection
259
- end
260
-
261
- def collections
262
- db.tables
263
- end
264
-
265
- def items(collection_name)
266
- collection(collection_name).items
267
- end
268
- end
269
- end
@@ -1,3 +0,0 @@
1
- module MinceDynamoDb
2
- VERSION = "1.3.1"
3
- end
@@ -1,18 +0,0 @@
1
- require_relative '../../lib/mince_dynamo_db'
2
- require 'aws'
3
-
4
- describe 'Testing mince_dynamo_db/data_store while actually hitting dynamo db:' do
5
- AWS.config.access_key_id = 'enter_access_key_id_here'
6
- AWS.config.secret_access_key = 'enter_secret_access_key_here'
7
-
8
- # Ensure clear db before tests
9
- before(:all) do
10
- MinceDynamoeDb::DataStore.instance.clear
11
- end
12
-
13
- # Ensure clear db after tests
14
- after(:all) do
15
- MinceDynamoeDb::DataStore.instance.clear
16
- end
17
- end
18
-
File without changes