mince_dynamo_db 1.3.1 → 2.0.0.pre.1

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