mince_dynamo_db 2.0.0.pre.1 → 2.0.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ module MinceDynamoDb
2
+ require_relative 'mince_dynamo_db/config'
3
+ require_relative 'mince_dynamo_db/connection'
4
+ require_relative 'mince_dynamo_db/data_sanitizer'
5
+ require_relative 'mince_dynamo_db/data_store'
6
+ require_relative 'mince_dynamo_db/interface'
7
+ require_relative 'mince_dynamo_db/version'
8
+ end
@@ -0,0 +1,62 @@
1
+ module MinceDynamoDb # :nodoc:
2
+ require 'singleton'
3
+ require 'aws'
4
+
5
+ # = DynamoDb Config
6
+ #
7
+ # Config specifies the configuration settings
8
+ #
9
+ # @author Matt Simpson
10
+ class Config
11
+ include Singleton
12
+
13
+ # Returns the primary key identifier for records. This is necessary because not all databases use the same
14
+ # primary key.
15
+ #
16
+ # @return [Symbol] the name of the primary key field.
17
+ def self.primary_key
18
+ instance.primary_key
19
+ end
20
+
21
+ # Returns the secret access key used for authenticating with Amazon's DynamoDB service
22
+ #
23
+ # Can either be set explicitely, or uses AWS.config by default
24
+ def self.secret_access_key
25
+ instance.secret_access_key
26
+ end
27
+
28
+ def self.access_key_id
29
+ instance.access_key_id
30
+ end
31
+
32
+ def self.secret_access_key=(val)
33
+ instance.secret_access_key = val
34
+ end
35
+
36
+ def self.access_key_id=(val)
37
+ instance.access_key_id = val
38
+ end
39
+
40
+ attr_reader :primary_key
41
+
42
+ def initialize
43
+ @primary_key = :id
44
+ end
45
+
46
+ def access_key_id
47
+ AWS.config.access_key_id
48
+ end
49
+
50
+ def access_key_id=(id)
51
+ AWS.config.access_key_id = id
52
+ end
53
+
54
+ def secret_access_key
55
+ AWS.config.secret_access_key
56
+ end
57
+
58
+ def secret_access_key=(key)
59
+ AWS.config.secret_access_key = key
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,19 @@
1
+ module MinceDynamoDb
2
+ require 'singleton'
3
+ require_relative 'config'
4
+ require 'aws/dynamo_db'
5
+
6
+ class Connection
7
+ include Singleton
8
+
9
+ def self.connection
10
+ instance.connection
11
+ end
12
+
13
+ attr_accessor :connection
14
+
15
+ def connection
16
+ @connection ||= AWS::DynamoDB.new(access_key_id: Config.access_key_id, secret_access_key: Config.secret_access_key)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
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
@@ -0,0 +1,42 @@
1
+ module MinceDynamoDb # :nodoc:
2
+ require 'singleton'
3
+ require_relative 'connection'
4
+
5
+ class DataStore
6
+ include Singleton
7
+
8
+ # Returns the collection, or table, for a given collection name
9
+ #
10
+ # The schema must be loaded before any queries are made against a collection
11
+ # There are a couple ways to do this, from digging in their documentation about
12
+ # this topic. One is to have the schema loaded in memory, which is not recommended
13
+ # for production code, the other way is to call hash_key :) not sure why this works.
14
+ #
15
+ # @param [String] collection_name the name of the collection
16
+ # @return [AWS::DynamoDB::Table] the AWS::DynamoDB::Table for the given collection_name
17
+ def self.collection(collection_name)
18
+ collections[collection_name.to_s].tap do |c|
19
+ c.hash_key unless c.schema_loaded?
20
+ end
21
+ end
22
+
23
+ # Returns the database object which comes from MinceDynamoDb::Connection
24
+ def self.db
25
+ instance.db
26
+ end
27
+
28
+ def self.collections
29
+ db.tables
30
+ end
31
+
32
+ def self.items(collection_name)
33
+ collection(collection_name).items
34
+ end
35
+
36
+ attr_accessor :db
37
+
38
+ def db
39
+ @db ||= Connection.connection
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,230 @@
1
+ module MinceDynamoDb # :nodoc:
2
+ require 'digest'
3
+ require 'active_support/hash_with_indifferent_access'
4
+ require_relative 'data_store'
5
+ require_relative 'data_sanitizer'
6
+ require_relative 'config'
7
+
8
+ module Interface
9
+ # Not yet implemented
10
+ def self.update_field_with_value(*args)
11
+ raise %(The method `MinceDynamoDb::DataStore.singleton.update_field_with_value` is not implemented, you should implement it for us!)
12
+ end
13
+
14
+ def self.delete_field(collection_name, field)
15
+ raise %(The method `MinceDynamoDb::DataStore.singleton.delete_field` is not implemented, you should implement it for us!)
16
+ end
17
+
18
+ def self.delete_collection(collection_name)
19
+ raise %(The method `MinceDynamoDb::DataStore.singleton.delete_collection` is not implemented, you should implement it for us!)
20
+ end
21
+
22
+ def self.increment_field_by_amount(collection_name, id, field_name, amount)
23
+ raise %(The method `MinceDynamoDb::DataStore.singleton.increment_field_by_amount` is not implemented, you should implement it for us!)
24
+ end
25
+
26
+
27
+
28
+ # Returns the primary key identifier for records. This is necessary because not all databases use the same
29
+ # primary key.
30
+ #
31
+ # @return [String] the name of the primary key field.
32
+ def self.primary_key_identifier
33
+ Config.primary_key
34
+ end
35
+
36
+ # Generates a unique ID for a database record
37
+ #
38
+ # @note This is necessary because different databases use different types of primary key values. Thus, each mince
39
+ # implementation must define how to generate a unique id.
40
+ #
41
+ # @param [#to_s] salt any object that responds to #to_s
42
+ # @return [String] A unique id based on the salt and the current time
43
+ def self.generate_unique_id(salt)
44
+ Digest::SHA256.hexdigest("#{Time.current.utc}#{salt}")[0..6]
45
+ end
46
+
47
+ # Inserts one record into a collection.
48
+ #
49
+ # @param [String] collection_name the name of the collection
50
+ # @param [Hash] hash a hash of data to be added to the collection
51
+ def self.add(collection_name, hash)
52
+ hash.delete_if{|k,v| v.nil? }
53
+ items(collection_name).create(sanitized_hash(hash))
54
+ end
55
+
56
+ # Replaces a record in the collection based on the primary key's value. The hash must contain a key, defined
57
+ # by the +primary_key_identifier+ method, with a value. If a record in the data store is found with that key and
58
+ # value, the entire record will be replaced with the given hash.
59
+ #
60
+ # @param [String] collection_name the name of the collection
61
+ # @param [Hash] hash a hash to replace the record in the collection with
62
+ def self.replace(collection_name, hash)
63
+ items(collection_name).put(sanitized_hash(hash))
64
+ end
65
+
66
+ # Gets all records that have the value for a given key.
67
+ #
68
+ # @param [String] collection_name the name of the collection
69
+ # @param [String] key the key, or field, to get a record for
70
+ # @param [*] value the value to get a record for
71
+ # @return [Array] an array of records that match the key and value
72
+ def self.get_all_for_key_with_value(collection_name, key, value)
73
+ get_by_params(collection_name, key => value)
74
+ end
75
+
76
+ # Gets the first record that has the value for a given key.
77
+ #
78
+ # @param [String] collection_name the name of the collection
79
+ # @param [String] key the key to find a record by
80
+ # @param [String] value the value to find a record by
81
+ # @return [Hash] a hash for the record found by the key and value in the collection
82
+ def self.get_for_key_with_value(collection_name, key, value)
83
+ get_all_for_key_with_value(collection_name, key.to_s, value).first
84
+ end
85
+
86
+ # Gets all records that have all of the keys and values in the given hash.
87
+ #
88
+ # @param [String] collection_name the name of the collection
89
+ # @param [Hash] hash a hash to get a record for
90
+ # @return [Array] an array of all records matching the given hash for the collection
91
+ def self.get_by_params(collection_name, hash)
92
+ hash = HashWithIndifferentAccess.new(hash)
93
+ array_to_hash(items(collection_name).where(hash).select)
94
+ end
95
+
96
+ # Gets all records for a collection
97
+ #
98
+ # @param [String] collection_name the name of the collection
99
+ # @return [Array] all records for the given collection name
100
+ def self.find_all(collection_name)
101
+ array_to_hash(items(collection_name).select)
102
+ end
103
+
104
+ # Gets a record matching a key and value
105
+ #
106
+ # @param [String] collection_name the name of the collection
107
+ # @param [String] key the key to find a record by
108
+ # @param [*] value a value the find a record by
109
+ # @return [Hash] a record that matches the given key and value
110
+ def self.find(collection_name, key, value)
111
+ get_for_key_with_value(collection_name, key, value)
112
+ end
113
+
114
+ # Pushes a value to a record's key that is an array
115
+ #
116
+ # @param [String] collection_name the name of the collection
117
+ # @param [String] identifying_key the field used to find the record
118
+ # @param [*] identifying_value the value used to find the record
119
+ # @param [String] array_key the field to push an array to
120
+ # @param [*] value_to_push the value to push to the array
121
+ def self.push_to_array(collection_name, identifying_key, identifying_value, array_key, value_to_push)
122
+ item = items(collection_name).where(identifying_key.to_s => identifying_value).first
123
+ item.attributes.add(array_key => [sanitized_field(value_to_push)])
124
+ end
125
+
126
+ # Removes a value from a record's key that is an array
127
+ #
128
+ # @param [String] collection_name the name of the collection
129
+ # @param [String] identifying_key the field used to find the record
130
+ # @param [*] identifying_value the value used to find the record
131
+ # @param [String] array_key the field to push an array from
132
+ # @param [*] value_to_remove the value to remove from the array
133
+ def self.remove_from_array(collection_name, identifying_key, identifying_value, array_key, value_to_remove)
134
+ item = items(collection_name).where(identifying_key.to_s => identifying_value).select.first
135
+ item.attributes.delete(array_key => [value_to_remove])
136
+ end
137
+
138
+ # Returns all records where the given key contains any of the values provided
139
+ #
140
+ # @param [String] collection_name the name of the collection
141
+ # @param [String] key the key to find the record by
142
+ # @param [Array] values an array of values that the record could contain
143
+ # @return [Array] all records that contain any of the values given
144
+ def self.containing_any(collection_name, key, values)
145
+ array_to_hash items(collection_name).where(key.to_sym).in(values).select
146
+ end
147
+
148
+ # Returns all records where the given key contains the given value
149
+ #
150
+ # @param [String] collection_name the name of the collection
151
+ # @param [String] key the key to find records by
152
+ # @param [*] value the value to find a record by
153
+ # @return [Array] all records where the key contains the given value
154
+ def self.array_contains(collection_name, key, value)
155
+ array_to_hash items(collection_name).where(key.to_sym).contains(value).select
156
+ end
157
+
158
+ # Deletes a record that matches the given criteria from the data store.
159
+ def self.delete_by_params(collection_name, params)
160
+ item = items(collection_name).where(params).select.first
161
+ item.delete
162
+ end
163
+
164
+ # Clears the data store.
165
+ # Mainly used for rolling back the data store in tests.
166
+ def self.clear
167
+ db.tables.each do |t|
168
+ t.hash_key unless t.schema_loaded? # to load the schema
169
+ t.items.each do |i|
170
+ i.delete
171
+ end
172
+ end
173
+ end
174
+
175
+ def self.set_data(data)
176
+ clear
177
+
178
+ data.each do |key, records|
179
+ records.each do |record|
180
+ add key, record
181
+ end
182
+ end
183
+ end
184
+
185
+ # Returns the collection, or table, for a given collection name
186
+ #
187
+ # The schema must be loaded before any queries are made against a collection
188
+ # There are a couple ways to do this, from digging in their documentation about
189
+ # this topic. One is to have the schema loaded in memory, which is not recommended
190
+ # for production code, the other way is to call hash_key :) not sure why this works.
191
+ #
192
+ # @param [String] collection_name the name of the collection
193
+ # @return [AWS::DynamoDB::Table] the AWS::DynamoDB::Table for the given collection_name
194
+ def self.collection(collection_name)
195
+ DataStore.collection(collection_name)
196
+ end
197
+
198
+ def self.sanitized_field(field)
199
+ DataSanitizer.prepare_field_for_storage(field)
200
+ end
201
+
202
+ def self.sanitized_hash(hash)
203
+ DataSanitizer.prepare_hash_for_storage(hash)
204
+ end
205
+
206
+ # Takes a DynamoDB item and returns the attributes of that item as a hash
207
+ def self.to_hash(item)
208
+ item.attributes if item
209
+ end
210
+
211
+ # Takes an array of DynamoDB items and returns the attributes of each item as a hash.
212
+ # calls
213
+ def self.array_to_hash(array)
214
+ array.map{|a| to_hash(a) }
215
+ end
216
+
217
+ # Returns the database object which comes from MinceDynamoDb::Connection
218
+ def self.db
219
+ DataStore.db
220
+ end
221
+
222
+ def self.collections
223
+ DataStore.collections
224
+ end
225
+
226
+ def self.items(collection_name)
227
+ DataStore.items(collection_name)
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,19 @@
1
+ module MinceDynamoDb
2
+ module Version
3
+ def self.major
4
+ 2
5
+ end
6
+
7
+ def self.minor
8
+ 0
9
+ end
10
+
11
+ def self.patch
12
+ "0.pre.2"
13
+ end
14
+ end
15
+
16
+ def self.version
17
+ [Version.major, Version.minor, Version.patch].join(".")
18
+ end
19
+ end
@@ -1,16 +1,16 @@
1
- require_relative '../../lib/dynamo_db'
1
+ require_relative '../../lib/mince_dynamo_db'
2
2
  require 'mince/shared_examples/interface_example'
3
3
 
4
4
  describe 'Mince Interface with DynamoDb' do
5
- pending do
5
+ pending 'need to write a setup and teardown to create the db and tables needed' do
6
6
  before do
7
7
  AWS.config(
8
8
  :use_ssl => false,
9
- :dynamo_db_endpoint => 'localhost',
9
+ :mince_dynamo_db_endpoint => 'localhost',
10
10
  :access_key_id => "xxx",
11
11
  :secret_access_key => "xxx"
12
12
  )
13
- Mince::Config.interface = Mince::DynamoDb::Interface
13
+ Mince::Config.interface = MinceDynamoDb::Interface
14
14
  end
15
15
 
16
16
  it_behaves_like 'a mince interface'
@@ -1,6 +1,6 @@
1
- require_relative '../../lib/dynamo_db/config'
1
+ require_relative '../../lib/mince_dynamo_db/config'
2
2
 
3
- describe Mince::DynamoDb::Config do
3
+ describe MinceDynamoDb::Config do
4
4
  let(:secret_access_key) { mock }
5
5
  let(:access_key_id) { mock }
6
6
 
@@ -1,6 +1,6 @@
1
- require_relative '../../lib/dynamo_db/connection'
1
+ require_relative '../../lib/mince_dynamo_db/connection'
2
2
 
3
- describe Mince::DynamoDb::Connection do
3
+ describe MinceDynamoDb::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 Mince::DynamoDb::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
- Mince::DynamoDb::Config.stub(access_key_id: access_key_id, secret_access_key: secret_access_key)
12
+ MinceDynamoDb::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
@@ -1,8 +1,8 @@
1
- require_relative '../../lib/dynamo_db/data_sanitizer'
1
+ require_relative '../../lib/mince_dynamo_db/data_sanitizer'
2
2
 
3
3
  require 'date'
4
4
 
5
- describe Mince::DynamoDb::DataSanitizer do
5
+ describe MinceDynamoDb::DataSanitizer do
6
6
  it 'converts a Time object to a string' do
7
7
  value = Time.now
8
8
 
@@ -1,6 +1,6 @@
1
- require_relative '../../lib/dynamo_db/data_store'
1
+ require_relative '../../lib/mince_dynamo_db/data_store'
2
2
 
3
- describe Mince::DynamoDb::DataStore do
3
+ describe MinceDynamoDb::DataStore do
4
4
  let(:db) { mock 'db connection', tables: collections }
5
5
  let(:collections) { { collection_name => collection } }
6
6
  let(:collection) { mock 'some collection', items: items, schema_loaded?: true }
@@ -1,6 +1,6 @@
1
- require_relative '../../lib/dynamo_db/interface'
1
+ require_relative '../../lib/mince_dynamo_db/interface'
2
2
 
3
- describe Mince::DynamoDb::Interface do
3
+ describe MinceDynamoDb::Interface do
4
4
  let(:db) { mock 'dynamo db connection', tables: { collection_name => collection } }
5
5
  let(:mince_dynamo_db_connection) { mock 'mince dynamo db connection', connection: db }
6
6
  let(:collection) { mock 'some collection', items: items, schema_loaded?: true }
@@ -17,10 +17,10 @@ describe Mince::DynamoDb::Interface do
17
17
  let(:items) { mock 'items' }
18
18
 
19
19
  before do
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)
20
+ MinceDynamoDb::DataStore.stub(:items).with(collection_name).and_return(items)
21
+ MinceDynamoDb::DataStore.stub(:collection).with(collection_name).and_return(collection)
22
+ MinceDynamoDb::DataStore.stub(collections: collections, db: db)
23
+ MinceDynamoDb::DataSanitizer.stub(:prepare_hash_for_storage).with(data).and_return(sanitized_data)
24
24
  end
25
25
 
26
26
  describe "Generating a primary key" do
@@ -123,7 +123,7 @@ describe Mince::DynamoDb::Interface do
123
123
  it 'can push a value to an array for a specific record' do
124
124
  value_to_push = mock 'value to push to the record'
125
125
  sanitized_value_to_push = mock 'sanitized value to push to the record'
126
- Mince::DynamoDb::DataSanitizer.stub(:prepare_field_for_storage).with(value_to_push).and_return(sanitized_value_to_push)
126
+ MinceDynamoDb::DataSanitizer.stub(:prepare_field_for_storage).with(value_to_push).and_return(sanitized_value_to_push)
127
127
  items.should_receive(:where).with("key" => "value").and_return([return_data])
128
128
  attributes.should_receive(:add).with(:array_key => [sanitized_value_to_push])
129
129
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mince_dynamo_db
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.pre.1
4
+ version: 2.0.0.pre.2
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-01 00:00:00.000000000 Z
12
+ date: 2012-11-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: aws-sdk
@@ -123,22 +123,6 @@ dependencies:
123
123
  - - ~>
124
124
  - !ruby/object:Gem::Version
125
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
126
  - !ruby/object:Gem::Dependency
143
127
  name: mince
144
128
  requirement: !ruby/object:Gem::Requirement
@@ -162,22 +146,13 @@ executables: []
162
146
  extensions: []
163
147
  extra_rdoc_files: []
164
148
  files:
165
- - .gitignore
166
- - .rspec
167
- - .rvmrc
168
- - .travis.yml
169
- - Gemfile
170
- - Guardfile
171
- - README.md
172
- - Rakefile
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
180
- - mince_dynamo_db.gemspec
149
+ - lib/mince_dynamo_db.rb
150
+ - lib/mince_dynamo_db/config.rb
151
+ - lib/mince_dynamo_db/connection.rb
152
+ - lib/mince_dynamo_db/data_sanitizer.rb
153
+ - lib/mince_dynamo_db/data_store.rb
154
+ - lib/mince_dynamo_db/interface.rb
155
+ - lib/mince_dynamo_db/version.rb
181
156
  - spec/integration/mince_interface_spec.rb
182
157
  - spec/units/config_spec.rb
183
158
  - spec/units/connection_spec.rb
@@ -198,7 +173,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
198
173
  version: '0'
199
174
  segments:
200
175
  - 0
201
- hash: -4539691897538722192
176
+ hash: -471291814637283281
202
177
  required_rubygems_version: !ruby/object:Gem::Requirement
203
178
  none: false
204
179
  requirements:
data/.gitignore DELETED
@@ -1,6 +0,0 @@
1
- *.gem
2
- .yardoc
3
- doc
4
- .bundle
5
- Gemfile.lock
6
- pkg/*
data/.rspec DELETED
@@ -1 +0,0 @@
1
- --colour
data/.rvmrc DELETED
@@ -1 +0,0 @@
1
- rvm use 1.9.3
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
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/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- # Specify your gem's dependencies in mince_dynamo_db.gemspec
4
- gemspec
data/Guardfile DELETED
@@ -1,13 +0,0 @@
1
- # A sample Guardfile
2
- # More info at https://github.com/guard/guard#readme
3
- #
4
-
5
- guard 'rspec', :version => 2 do
6
- watch(%r{^spec/lib/.+_spec\.rb$})
7
- watch(%r{^lib/.+\.rb$})
8
- watch(%r{^lib/mince_dynamo_db/.+\.rb$})
9
- watch(%r{^lib/(.+)\.rb}) { |m| "spec/lib/#{m[1]}_spec.rb" }
10
- watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
11
- watch(%r{^spec/support/shared_examples/(.+)\.rb$}) { "spec" }
12
- end
13
-
data/README.md DELETED
@@ -1,59 +0,0 @@
1
- # What is mince dynamo db?
2
-
3
- Light weight ORM to persist data to an Amazon DynamoDB database.
4
-
5
- Provides a very light weight interface for storing and retreiving information to DynamoDB.
6
-
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
-
9
- [@github](https://github.com/coffeencoke/mince_dynamo_db)
10
- [@rubygems](https://rubygems.org/gems/mince_dynamo_db)
11
-
12
- # How to use
13
-
14
- view the [example mince rails app](https://github.com/coffeencoke/mince_rails_example) to see how to use this.
15
-
16
- <pre>
17
- # Add a book to the books collection
18
- MinceDynamoDb::DataStore.instance.add 'books', title: 'The World In Photographs', publisher: 'National Geographic'
19
-
20
- # Retrieve all records from the books collection
21
- MinceDynamoDb::DataStore.instance.find_all 'books'
22
-
23
- # Replace a specific book
24
- MinceDynamoDb::DataStore.instance.replace 'books', id: 1, title: 'A World In Photographs', publisher: 'National Geographic'
25
- </pre>
26
-
27
- View the docs for MinceDynamoDb::DataStore for all methods available.
28
-
29
- Use with [mince data model](https://github.com/asynchrony/mince_data_model) to make it easy to change from one data storage to another, like [Hashy Db](https://github.com/asynchrony/hashy_db), a Hash data persistence implementation, or [Mince](https://github.com/asynchrony/mince), a MongoDB implementation.
30
-
31
- # Why would you want this?
32
-
33
- - To defer choosing your database until you know most about your application.
34
- - Provides assitance in designing a database agnostic architecture.
35
- - When used along with [Hashy Db](https://github.com/asynchrony/hashy_db) it offers very little technical dependencies. Use Hashy Db in development mode so that you can clone the repo and develop, and run tests, cucumbers without databases, migrations, etc. Then in production mode, switch to Mince Dynamo DB.
36
-
37
- If you are able to switch between Hashy Db and Mince Dynamo Db, your application will be more open to new and improved database in the future, or as your application evolves you aren't tied to a database.
38
-
39
-
40
- # Todo
41
-
42
- - Write integration specs
43
- - Do not use singleton for data store
44
- - Refactor data store
45
- - Remove dependency on Active Support
46
-
47
- # Contribute
48
-
49
- - fork into a topic branch, write specs, make a pull request.
50
-
51
- # Owners
52
-
53
- Matt Simpson - [@railsgrammer](https://twitter.com/railsgrammer)
54
-
55
- # Contributors
56
-
57
- - Your name here!
58
-
59
- ![Mince Some App](https://github.com/coffeencoke/gist-files/raw/master/images/mince%20garlic.png)
data/Rakefile DELETED
@@ -1,12 +0,0 @@
1
- require "bundler/gem_tasks"
2
-
3
- begin
4
- require 'rspec/core/rake_task'
5
-
6
- desc 'Default: run specs.'
7
- task :default => :spec
8
-
9
- desc "Run specs"
10
- RSpec::Core::RakeTask.new
11
- rescue LoadError
12
- end
data/lib/dynamo_db.rb DELETED
@@ -1,10 +0,0 @@
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
@@ -1,56 +0,0 @@
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
@@ -1,21 +0,0 @@
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
@@ -1,13 +0,0 @@
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
@@ -1,44 +0,0 @@
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
@@ -1,232 +0,0 @@
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
@@ -1,22 +0,0 @@
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,31 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "dynamo_db/version"
4
-
5
- Gem::Specification.new do |s|
6
- s.name = "mince_dynamo_db"
7
- s.version = Mince::DynamoDb.version
8
- s.authors = ["Matt Simpson"]
9
- s.email = ["matt@railsgrammer.com"]
10
- s.homepage = "https://github.com/coffeencoke/mince_dynamo_db"
11
- s.summary = %q{Lightweight ORM for Amazon's DynamoDB with Ruby Apps}
12
- s.description = %q{Lightweight ORM for Amazon's DynamoDB with Ruby Apps}
13
-
14
- s.rubyforge_project = "mince_dynamo_db"
15
-
16
- s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
- s.require_paths = ["lib"]
20
-
21
- s.add_dependency 'aws-sdk', "~> 1.3.7"
22
- s.add_dependency 'activesupport', "~> 3.0"
23
-
24
- s.add_development_dependency('rake', '~> 0.9')
25
- s.add_development_dependency "rspec", "~> 2.8.0"
26
- s.add_development_dependency "guard-rspec", "~> 0.6.0"
27
- s.add_development_dependency "yard", "~> 0.7.5"
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"
31
- end