dynamini 1.8.2 → 1.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 13893f0fc6ee9e9871a4627e5becc6d28586faa2
4
- data.tar.gz: 47ad7abf1223ebc4e7372540a109146adf59644e
3
+ metadata.gz: 356f8de542ea075c952843efbed411bbd65f223d
4
+ data.tar.gz: 97f98ccc45f1e5dcf93ed8f66628cc647c1018fb
5
5
  SHA512:
6
- metadata.gz: 98a97520da94e1396c0a78c8f0d609d4caadb48ce0adaa2f9c778e1e79b75e73478124aa93e65d9b3f0823472f35ffc31a656a0d81c83f7a1f28f0629f194462
7
- data.tar.gz: 93936a76f1efe1b2dd22d4f30d05d3cc4019a8bc53e3ca0ee3d59886c2cc58ec5f70d53a3e78633d5e88b9815f664d6595917be2a413ee2739d15c6359f9d38a
6
+ metadata.gz: 7cc1c405737dc4e50018be9b2141aab9d1af70263a929c2d18c3d274b92266b8c10feec881f5a7121a307a1f22032121020b6fca5441a9a13966d7d43315c593
7
+ data.tar.gz: e8a6743e11f1fc7ff9a3cb8e17795a273b45ce0ac4cd30b9a2f0324746b68cbdbe53cd9f048cc415adf445637d8ed8470fb8bfac0db93ab000f06f3491c5ca0c
@@ -0,0 +1,7 @@
1
+ .idea
2
+ *.gem
3
+ dynamini.iml
4
+ /.ruby-version
5
+ /.ruby-gemset
6
+ .DS_Store
7
+ /spec/.DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format Fuubar
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ script: bundle exec rspec
3
+ rvm:
4
+ - 2.0.0
5
+ - 2.2.1
6
+ notifications:
7
+ email:
8
+ recipients:
9
+ - dev@retailcommon.com
10
+ on_success: change
11
+ on_failure: always
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,101 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ dynamini (1.9.1)
5
+ activemodel (>= 3, < 5.0)
6
+ aws-sdk (~> 2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activemodel (4.2.5)
12
+ activesupport (= 4.2.5)
13
+ builder (~> 3.1)
14
+ activesupport (4.2.5)
15
+ i18n (~> 0.7)
16
+ json (~> 1.7, >= 1.7.7)
17
+ minitest (~> 5.1)
18
+ thread_safe (~> 0.3, >= 0.3.4)
19
+ tzinfo (~> 1.1)
20
+ aws-sdk (2.2.5)
21
+ aws-sdk-resources (= 2.2.5)
22
+ aws-sdk-core (2.2.5)
23
+ jmespath (~> 1.0)
24
+ aws-sdk-resources (2.2.5)
25
+ aws-sdk-core (= 2.2.5)
26
+ builder (3.2.2)
27
+ coderay (1.1.0)
28
+ diff-lcs (1.2.5)
29
+ ffi (1.9.10)
30
+ formatador (0.2.5)
31
+ fuubar (2.0.0)
32
+ rspec (~> 3.0)
33
+ ruby-progressbar (~> 1.4)
34
+ guard (2.13.0)
35
+ formatador (>= 0.2.4)
36
+ listen (>= 2.7, <= 4.0)
37
+ lumberjack (~> 1.0)
38
+ nenv (~> 0.1)
39
+ notiffany (~> 0.0)
40
+ pry (>= 0.9.12)
41
+ shellany (~> 0.0)
42
+ thor (>= 0.18.1)
43
+ guard-compat (1.2.1)
44
+ guard-rspec (4.6.4)
45
+ guard (~> 2.1)
46
+ guard-compat (~> 1.1)
47
+ rspec (>= 2.99.0, < 4.0)
48
+ guard-shell (0.7.1)
49
+ guard (>= 2.0.0)
50
+ guard-compat (~> 1.0)
51
+ i18n (0.7.0)
52
+ jmespath (1.1.3)
53
+ json (1.8.3)
54
+ listen (3.0.3)
55
+ rb-fsevent (>= 0.9.3)
56
+ rb-inotify (>= 0.9)
57
+ lumberjack (1.0.9)
58
+ method_source (0.8.2)
59
+ minitest (5.8.3)
60
+ nenv (0.2.0)
61
+ notiffany (0.0.8)
62
+ nenv (~> 0.1)
63
+ shellany (~> 0.0)
64
+ pry (0.10.3)
65
+ coderay (~> 1.1.0)
66
+ method_source (~> 0.8.1)
67
+ slop (~> 3.4)
68
+ rb-fsevent (0.9.6)
69
+ rb-inotify (0.9.5)
70
+ ffi (>= 0.5.0)
71
+ rspec (3.3.0)
72
+ rspec-core (~> 3.3.0)
73
+ rspec-expectations (~> 3.3.0)
74
+ rspec-mocks (~> 3.3.0)
75
+ rspec-core (3.3.2)
76
+ rspec-support (~> 3.3.0)
77
+ rspec-expectations (3.3.1)
78
+ diff-lcs (>= 1.2.0, < 2.0)
79
+ rspec-support (~> 3.3.0)
80
+ rspec-mocks (3.3.2)
81
+ diff-lcs (>= 1.2.0, < 2.0)
82
+ rspec-support (~> 3.3.0)
83
+ rspec-support (3.3.0)
84
+ ruby-progressbar (1.7.5)
85
+ shellany (0.0.1)
86
+ slop (3.6.0)
87
+ thor (0.19.1)
88
+ thread_safe (0.3.5)
89
+ tzinfo (1.2.2)
90
+ thread_safe (~> 0.1)
91
+
92
+ PLATFORMS
93
+ ruby
94
+
95
+ DEPENDENCIES
96
+ dynamini!
97
+ fuubar (~> 2)
98
+ guard-rspec
99
+ guard-shell
100
+ pry (~> 0)
101
+ rspec (~> 3)
@@ -0,0 +1,79 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.("routing/#{m[1]}_routing"),
51
+ rspec.spec.("controllers/#{m[1]}_controller"),
52
+ rspec.spec.("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ watch(%r{^lib\/[a-zA-Z]+\/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
57
+
58
+ # Rails config changes
59
+ watch(rails.spec_helper) { rspec.spec_dir }
60
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
61
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
62
+
63
+ # Capybara features specs
64
+ watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
65
+ watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
66
+
67
+ # Turnip features and steps
68
+ watch(%r{^spec/acceptance/(.+)\.feature$})
69
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
70
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
71
+ end
72
+ end
73
+
74
+ # Add files and commands to this file, like the example:
75
+ # watch(%r{file/path}) { `command(s)` }
76
+ #
77
+ guard :shell do
78
+ watch(/(.*).txt/) {|m| `tail #{m[0]}` }
79
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 47colborne
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,214 @@
1
+ ## Dynamini
2
+ Dynamini is a lightweight DynamoDB interface designed as a drop-in replacement for ActiveRecord. This gem powers part of our stack at yroo.com.
3
+
4
+ [![Build Status](https://travis-ci.org/47colborne/dynamini.svg?branch=master)](https://travis-ci.org/47colborne/dynamini)
5
+ [![Code Climate](https://codeclimate.com/github/47colborne/dynamini/badges/gpa.svg)](https://codeclimate.com/github/47colborne/dynamini)
6
+ [![Gem Version](https://badge.fury.io/rb/dynamini.svg)](https://badge.fury.io/rb/dynamini)
7
+
8
+ ## The Basics
9
+ This gem provides an opinionated interface, set up to let you use Amazon's DynamoDB at its most efficient. That means traditional relational DB functions like WHERE, GROUP BY, and HAVING aren't provided, since these trigger table scans that defeat the performance gains realized by switching to Dynamo in the first place. Use this gem when you have an relational table with too much concurrent activity, resulting in constant table locking. After you've moved your data to Dynamo, and installed and configured Dynamini, the following ActiveRecord functions will be preserved:
10
+
11
+ Class methods:
12
+ * create(attributes)
13
+ * create!(attributes)
14
+ * find(hash_key, range_key)
15
+ * exists?(hash_key, range_key)
16
+ * find_or_new(hash_key, range_key)
17
+ * import(model_array)
18
+
19
+ Note: The range_key arguments are only necessary if your DynamoDB table is configured with a range key.
20
+
21
+ Instance methods:
22
+ * new(attributes)
23
+ * ==(object)
24
+ * assign_attributes(attributes)
25
+ * update_attributes(attributes)
26
+ * update_attribute(attribute, value)
27
+ * save
28
+ * save!
29
+ * delete
30
+ * touch
31
+ * changes
32
+ * changed
33
+ * _was (e.g. model.foo_was, model.bar_was)
34
+ * new_record?
35
+ * updated_at
36
+ * created_at
37
+
38
+ We've included ActiveModel::Validations, so any validators will still work and be triggered by the save/create methods.
39
+ There are also some new functions specific to DynamoDB's API:
40
+
41
+ * batch_find([keys]) - to retrieve multiple objects at once.
42
+ * enqueue_for_save(attributes) - to add your object to the batch write queue, which automatically sends a batch_save at length 25.
43
+ * flush_queue! - to send the items in batch_save queue before reaching length 25.
44
+ * increment!({attribute1: amount, attribute2: amount}) - to update your record using DynamoDB's Atomic Counter functionality. (For more information, see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithItems.html#WorkingWithItems.AtomicCounters )
45
+
46
+ ## Configuration
47
+ In application.rb, or in initializers/dynamini.rb, include your AWS settings like so:
48
+
49
+ ```ruby
50
+ Dynamini.configure do |config|
51
+ config.aws_region = '[AWS region containing your DynamoDB instance]'
52
+ config.access_key_id = '[access_key_id for your AWS account]'
53
+ config.secret_access_key = '[secret_access_key for your AWS account]'
54
+ end
55
+ ```
56
+
57
+ Then set up your model. You'll need to have it inherit from Dynamini::Base, then identify the primary key and table name to match your DynamoDB setup.
58
+
59
+ Here's what a sample model looks like. This one includes a range key - sometimes your table will only need a hash key. If you aren't sure how or why to use range keys (also known as sort keys) with your DynamoDB instance, check here for help: http://stackoverflow.com/a/27348364
60
+
61
+ ```ruby
62
+ class Vehicle < Dynamini::Base
63
+ set_table_name 'cars-dev' # must match the table name configured in AWS
64
+ set_hash_key :model # defaults to :id if not set
65
+ set_range_key :vin # must be set if your AWS table is configured with a range key
66
+
67
+ # ...All the rest of your class methods, instance methods, and validators
68
+ end
69
+ ```
70
+
71
+ ## Datatype Handling
72
+ There are a few quirks about how the Dynamo Ruby SDK stores data. It stores numeric values as BigDecimal objects, symbols as strings, and doesn't accept ruby Date or Time objects. To save you from having to convert your data to the correct type before saving and after retrieval, you can use the :handle helper for automatic type conversion. You can also use this to specify default values for your fields. Here's how it works:
73
+
74
+ ```ruby
75
+ class Vehicle < Dynamini::Base
76
+ set_hash_key :vin
77
+ handle :top_speed, :integer, default: 80
78
+ end
79
+
80
+ car = Vehicle.new(vin: '43H1R')
81
+ car.top_speed
82
+ > 80
83
+ car.top_speed = 90
84
+ car.save
85
+ Vehicle.find('43H1R').top_speed
86
+ > 90
87
+ # This would return BigDecimal(90) without the handle helper.
88
+ ```
89
+
90
+ Defaults are optional - without a default, a handled field without a value assigned to it will return nil like any other field.
91
+
92
+ The following datatypes are supported by handle:
93
+ * :integer
94
+ * :float
95
+ * :symbol
96
+ * :boolean
97
+ * :date
98
+ * :time
99
+ * :string
100
+
101
+ Booleans and strings don't actually need to be translated, but you can set up defaults for those fields this way.
102
+ The magic fields updated_at and created_at are handled as :time by default.
103
+
104
+ ## Querying With Range Keys
105
+
106
+ Dynamini includes a query function that's much more narrow than ActiveRecord's where function. It's designed to retrieve a selection of records that belong to a given hash key but have various range key values. To use .query, your table needs to be configured with a range key, and you need to :handle that range field as a fundamentally numeric type - integer, float, date, or time. If your range key field isn't numeric, you won't be able to .query, but you'll still be able to .find your records normally.
107
+
108
+ Query takes three arguments; a mandatory :hash_key, an optional :start, and an optional :end. Here's how you'd use it to find daily temperature data for a given city, selecting for specific date ranges:
109
+
110
+ ```ruby
111
+ class DailyWeather < Dynamini::Base
112
+ set_hash_key :city
113
+ set_range_key :record_date
114
+ handle :temperature, :integer
115
+ handle :record_date, :date
116
+ end
117
+
118
+ # Seeding our dataset...
119
+ A = DailyWeather.create!(city: "Toronto", record_date: Date.new(2015,10,08), temperature: 15)
120
+ B = DailyWeather.create!(city: "Toronto", record_date: Date.new(2015,10,09), temperature: 17)
121
+ C = DailyWeather.create!(city: "Toronto", record_date: Date.new(2015,10,10), temperature: 12)
122
+ D = DailyWeather.create!(city: "Seville", record_date: Date.new(2015,10,10), temperature: 30)
123
+
124
+ DailyWeather.query(hash_key: "Toronto")
125
+ > [A, B, C]
126
+
127
+ DailyWeather.query(hash_key: "Seville")
128
+ > [D]
129
+
130
+ DailyWeather.query(hash_key: "Bangkok")
131
+ > []
132
+
133
+ DailyWeather.query(hash_key: "Toronto", start: Date.new(2015,10,09))
134
+ > [B, C]
135
+
136
+ DailyWeather.query(hash_key: "Toronto", end: Date.new(2015,10,08))
137
+ > [A]
138
+
139
+ DailyWeather.query(hash_key: "Toronto", start: Date.new(2015,10,08), end: Date.new(2015,10,09))
140
+ > [A, B]
141
+ ```
142
+
143
+ ## Array Support
144
+ You can save arrays to your Dynamini model. If you've :handled that attribute, it will attempt to convert its contents to the correct datatype when setting and getting. Here's how it works:
145
+
146
+ ```ruby
147
+ class Vehicle < Dynamini::Base
148
+ set_hash_key :vin
149
+ handle :parts, :symbol, default: []
150
+ end
151
+
152
+ car = Vehicle.new(vin: 'H3LL0')
153
+ car.parts
154
+ > []
155
+
156
+ car.parts = 'wheel'
157
+ car.parts
158
+ > :wheel
159
+
160
+ car.parts = ['wheel', 'brakes', 'seat']
161
+ car.parts
162
+ > [:wheel, :brakes, :seat]
163
+
164
+ # This line will raise an error since 5 cannot be converted to a symbol.
165
+ car.parts = ['wheel', 'brakes', 5]
166
+
167
+ # That multitype array can be saved to a non-:handled attribute.
168
+ car.stuff = ['wheel', 'brakes', 5]
169
+ car.stuff
170
+ > ['wheel', 'brakes', 5]
171
+ # But then you won't have any type conversion.
172
+ car.save
173
+ Vehicle.find('H3LLO').stuff
174
+ > ['wheel', 'brakes', BigDecimal(5)]
175
+ ```
176
+
177
+ ## Testing
178
+ There's a test client included with this gem, meaning you don't have to connect to a real Dynamo instance when testing.
179
+ You could also use this in development if you dont have a real Dynamo instance yet, but the data saved to it won't persist through a server restart.
180
+ To activate this feature, just call:
181
+ ```ruby
182
+ Vehicle.in_memory = true
183
+ ```
184
+ After which any internal API calls will be replaced with calls to Dynamini::TestClient.
185
+
186
+ The test client will not reset its database unless you tell it to, like so:
187
+ ```ruby
188
+ Vehicle.client.reset
189
+ ```
190
+
191
+ So, for instance, to get Rspec working with your test suite the way your ActiveRecord model behaved, add these lines to your spec_helper.rb:
192
+ ```ruby
193
+ config.before(:all) {
194
+ Vehicle.in_memory = true
195
+ }
196
+ config.after(:each) {
197
+ Vehicle.client.reset # Large test suites will be very slow and unpredictable otherwise!
198
+ }
199
+ ```
200
+
201
+ ## Things to remember
202
+ * Since DynamoDB is schemaless, your model will respond to any method that looks like a reader, meaning model.foo will return nil.
203
+ * You can also write any arbitrary attribute to your model.
204
+ * Other models in your app cannot have a has_one or has_many relationship with your Dynamini model, since these would require a table scan. Your other models can still use belongs_to.
205
+ * If you change the primary key value on an instance of your model, then resave it, you'll have two copies in your database.
206
+ * If you use non-numeric strings for your primary key, remember to change your foreign key columns on related objects to be string type.
207
+ * You might want to conditionally set the table name for your model based on the Rails.env, enabling separate tables for development and production.
208
+
209
+ ## Coming Soon
210
+ * Conditional updates ( http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.SpecifyingConditions.html )
211
+
212
+ ## Contributing
213
+
214
+ If you'd like to contribute, pull requests are welcome!