dynomite 1.2.4 → 1.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +13 -1
- data/README.md +16 -8
- data/docs/migrations/long-example.rb +5 -1
- data/lib/dynomite/db_config.rb +17 -3
- data/lib/dynomite/erb.rb +2 -2
- data/lib/dynomite/item.rb +16 -29
- data/lib/dynomite/migration/dsl.rb +25 -5
- data/lib/dynomite/migration/executor.rb +1 -1
- data/lib/dynomite/migration/generator.rb +1 -1
- data/lib/dynomite/migration/templates/create_table.rb +1 -0
- data/lib/dynomite/query.rb +48 -0
- data/lib/dynomite/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a0639976cc739134d6670945d099f3584b39f88f058e056678591a42c09be84
|
4
|
+
data.tar.gz: 6ad8c28d9147a175cf940ec756d808b66a948c27be74239ab95dc3b589dd9d74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f7f4e2dcb3163c4cc60065c3d0f74b835bd4763f6295ae00d8f92576f2c57afcc969a5b4bf0c727d9b8a45ab17e52ae573319a819cf3f7c0974cf29a91b19ac
|
7
|
+
data.tar.gz: c37b011a90f943e6e04fbdfa248dee7b8bcf1dae762ae5175c976e02325e462118715d8340f5d42022e62a40ab1b77aa80af532544e255eed2c2fec89ecd6aa8
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -3,8 +3,20 @@
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
|
5
5
|
|
6
|
+
## [1.2.7] - 2022-06-12
|
7
|
+
- [#23](https://github.com/tongueroo/dynomite/pull/23) #where method refactor to allow Model.index_name('index').where(...)
|
8
|
+
- [#24](https://github.com/tongueroo/dynomite/pull/24) Add get_endpoint_ip to db_config.rb
|
9
|
+
- [#26](https://github.com/tongueroo/dynomite/pull/26) change pay_per_use to pay_per_request
|
10
|
+
- [#27](https://github.com/tongueroo/dynomite/pull/27) Fixed message that tells how to install dynamodb-local
|
11
|
+
|
12
|
+
## [1.2.6]
|
13
|
+
- Implement the `PAY_PER_USE` billing mode for table creations and updates. See [DynamoDB On Demand](https://aws.amazon.com/blogs/aws/amazon-dynamodb-on-demand-no-capacity-planning-and-pay-per-request-pricing/).
|
14
|
+
|
15
|
+
## [1.2.5]
|
16
|
+
- use correct color method
|
17
|
+
|
6
18
|
## [1.2.4]
|
7
|
-
- #16 add rainbow gem dependency for
|
19
|
+
- #16 add rainbow gem dependency for color method
|
8
20
|
- #17 fix table names for models with namespaces
|
9
21
|
|
10
22
|
## [1.2.3]
|
data/README.md
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
# Dynomite
|
2
2
|
|
3
|
-
|
3
|
+
[![BoltOps Badge](https://img.boltops.com/boltops/badges/boltops-badge.png)](https://www.boltops.com)
|
4
|
+
|
5
|
+
NOTE: Am looking for maintainers to help with this gem. Send me an email!
|
6
|
+
|
7
|
+
IMPORTANT: The next major version of Dynomite will be ActiveModel compatible. A POC is in the [edge](https://github.com/tongueroo/dynomite/tree/edge) branch. It's still very rough and experimental. Would not recommend using it yet, but wanted to note it.
|
4
8
|
|
5
9
|
A simple wrapper library to make DynamoDB usage a little more friendly. The modeling is ActiveRecord-ish but not exactly because DynamoDB is a different type of database. Examples below explain it best:
|
6
10
|
|
11
|
+
## Jets Docs
|
12
|
+
|
13
|
+
* [Database DynamoDB](https://rubyonjets.com/docs/database/dynamodb/)
|
14
|
+
|
7
15
|
## Examples
|
8
16
|
|
9
17
|
First define a class:
|
@@ -11,7 +19,7 @@ First define a class:
|
|
11
19
|
```ruby
|
12
20
|
class Post < Dynomite::Item
|
13
21
|
# partition_key "id" # optional, defaults to id
|
14
|
-
|
22
|
+
|
15
23
|
column :id, :title, :desc
|
16
24
|
end
|
17
25
|
```
|
@@ -21,7 +29,7 @@ end
|
|
21
29
|
```ruby
|
22
30
|
post = Post.new
|
23
31
|
post = post.replace(title: "test title")
|
24
|
-
post.attrs # {"id" => "generated-id", title" => "
|
32
|
+
post.attrs # {"id" => "generated-id", title" => "test title"}
|
25
33
|
```
|
26
34
|
|
27
35
|
`post.attrs[:id]` now contain a generated unique partition_key id. Usually the partition_key is 'id'. You can set your own unique id also by specifying id.
|
@@ -112,7 +120,7 @@ post.replace
|
|
112
120
|
|
113
121
|
puts post.id # 1962DE7D852298C5CDC809C0FEF50D8262CEDF09
|
114
122
|
puts post.name # "My First Post"
|
115
|
-
```
|
123
|
+
```
|
116
124
|
|
117
125
|
Note that any column not defined using the `column` method can still be accessed using the `attrs`
|
118
126
|
method.
|
@@ -125,15 +133,15 @@ Just add `include ActiveModel::Validations` at the top of your item class.
|
|
125
133
|
```ruby
|
126
134
|
class Post < Dynomite::Item
|
127
135
|
include ActiveModel::Validations
|
128
|
-
|
136
|
+
|
129
137
|
column :id, :name # needed
|
130
|
-
|
138
|
+
|
131
139
|
validates :id, presence: true
|
132
140
|
validates :name, presence: true
|
133
141
|
end
|
134
|
-
```
|
142
|
+
```
|
135
143
|
|
136
|
-
**Be sure to define all validated columns using `column` method**.
|
144
|
+
**Be sure to define all validated columns using `column` method**.
|
137
145
|
|
138
146
|
Validations are executed by default as soon as you call the `replace` method, returning `false` on
|
139
147
|
failure. It also can be ran manually using the `valid?` method just like with ActiveRecord models.
|
@@ -55,6 +55,9 @@ class CreateCommentsMigration < Dynomite::Migration
|
|
55
55
|
# read_capacity_units: 5,
|
56
56
|
# write_capacity_units: 5
|
57
57
|
# )
|
58
|
+
|
59
|
+
# set the billing mode to on-demand (NOTE: this overrides provisioned_throughput)
|
60
|
+
# t.billing_mode(:pay_per_request)
|
58
61
|
end
|
59
62
|
end
|
60
63
|
end
|
@@ -62,6 +65,8 @@ end
|
|
62
65
|
class UpdateCommentsMigration < Dynomite::Migration
|
63
66
|
def up
|
64
67
|
update_table :comments do |t|
|
68
|
+
# You can update from provisioned_throughput to on-demand pricing
|
69
|
+
# t.billing_mode(:pay_per_request)
|
65
70
|
|
66
71
|
# t.global_secondary_index do
|
67
72
|
# t.gsi(METHOD, INDEX_NAME) do
|
@@ -120,4 +125,3 @@ class UpdateCommentsMigration < Dynomite::Migration
|
|
120
125
|
end
|
121
126
|
end
|
122
127
|
end
|
123
|
-
|
data/lib/dynomite/db_config.rb
CHANGED
@@ -42,10 +42,24 @@ module Dynomite::DbConfig
|
|
42
42
|
# This wastes less of the users time.
|
43
43
|
def check_dynamodb_local!(endpoint)
|
44
44
|
return unless endpoint && endpoint.include?("8000")
|
45
|
-
|
46
|
-
|
45
|
+
|
46
|
+
host, port = endpoint.gsub("http://", "").split(":")
|
47
|
+
ip = get_endpoint_ip(host)
|
48
|
+
unless ip
|
49
|
+
raise "You have configured your app to use DynamoDB local, but it is not running on the host: #{host}."
|
50
|
+
end
|
51
|
+
|
52
|
+
open = port_open?(ip, 8000, 0.2)
|
47
53
|
unless open
|
48
|
-
raise "You have configured your app to use DynamoDB local, but it is not running. Please start DynamoDB local. Example: brew cask
|
54
|
+
raise "You have configured your app to use DynamoDB local, but it is not running. Please start DynamoDB local. Example: brew install --cask dynamodb-local && dynamodb-local"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_endpoint_ip(host)
|
59
|
+
begin
|
60
|
+
IPSocket.getaddress(host)
|
61
|
+
rescue SocketError
|
62
|
+
false # Can return anything you want here
|
49
63
|
end
|
50
64
|
end
|
51
65
|
|
data/lib/dynomite/erb.rb
CHANGED
@@ -26,7 +26,7 @@ class Dynomite::Erb
|
|
26
26
|
error_info ||= e.backtrace.grep(/\(erb\)/)[0]
|
27
27
|
raise unless error_info # unable to find the (erb):xxx: error line
|
28
28
|
line = error_info.split(':')[1].to_i
|
29
|
-
log "Error evaluating ERB template on line #{line.to_s.
|
29
|
+
log "Error evaluating ERB template on line #{line.to_s.color(:red)} of: #{path.sub(/^\.\//, '').color(:green)}"
|
30
30
|
|
31
31
|
template_lines = template.split("\n")
|
32
32
|
context = 5 # lines of context
|
@@ -35,7 +35,7 @@ class Dynomite::Erb
|
|
35
35
|
template_lines[top..bottom].each_with_index do |line_content, index|
|
36
36
|
line_number = top+index+1
|
37
37
|
if line_number == line
|
38
|
-
printf("%#{spacing}d %s\n".
|
38
|
+
printf("%#{spacing}d %s\n".color(:red), line_number, line_content)
|
39
39
|
else
|
40
40
|
printf("%#{spacing}d %s\n", line_number, line_content)
|
41
41
|
end
|
data/lib/dynomite/item.rb
CHANGED
@@ -4,6 +4,7 @@ require "digest"
|
|
4
4
|
require "yaml"
|
5
5
|
|
6
6
|
require "dynomite/reserved_words"
|
7
|
+
require "dynomite/query"
|
7
8
|
|
8
9
|
# The modeling is ActiveRecord-ish but not exactly because DynamoDB is a
|
9
10
|
# different type of database.
|
@@ -170,9 +171,17 @@ module Dynomite
|
|
170
171
|
resp.items.map {|i| self.new(i) }
|
171
172
|
end
|
172
173
|
|
174
|
+
# Creates a new chainable ActiveRecord Query-style instance with a certain index_name.
|
175
|
+
#
|
176
|
+
# Post.index_name("category-index").where(category: "Drama")
|
177
|
+
#
|
178
|
+
def self.index_name(name)
|
179
|
+
_new_query.index_name(name)
|
180
|
+
end
|
181
|
+
|
173
182
|
# Translates simple query searches:
|
174
183
|
#
|
175
|
-
# Post.where(
|
184
|
+
# Post.index_name("category-index").where(category: "Drama")
|
176
185
|
#
|
177
186
|
# translates to
|
178
187
|
#
|
@@ -183,34 +192,8 @@ module Dynomite
|
|
183
192
|
# expression_attribute_values: { ":category_value" => category },
|
184
193
|
# key_condition_expression: "#category_name = :category_value",
|
185
194
|
# )
|
186
|
-
|
187
|
-
|
188
|
-
#
|
189
|
-
# Post.where({category: "Drama"}, {index_name: "category-index"})
|
190
|
-
# VS
|
191
|
-
# Post.where(category: "Drama").index_name("category-index")
|
192
|
-
def self.where(attributes, options={})
|
193
|
-
raise "attributes.size == 1 only supported for now" if attributes.size != 1
|
194
|
-
|
195
|
-
attr_name = attributes.keys.first
|
196
|
-
attr_value = attributes[attr_name]
|
197
|
-
|
198
|
-
# params = {
|
199
|
-
# expression_attribute_names: { "#category_name" => "category" },
|
200
|
-
# expression_attribute_values: { ":category_value" => "Entertainment" },
|
201
|
-
# key_condition_expression: "#category_name = :category_value",
|
202
|
-
# }
|
203
|
-
name_key, value_key = "##{attr_name}_name", ":#{attr_name}_value"
|
204
|
-
params = {
|
205
|
-
expression_attribute_names: { name_key => attr_name },
|
206
|
-
expression_attribute_values: { value_key => attr_value },
|
207
|
-
key_condition_expression: "#{name_key} = #{value_key}",
|
208
|
-
}
|
209
|
-
# Allow direct access to override params passed to dynamodb query options.
|
210
|
-
# This is is how index_name is passed:
|
211
|
-
params = params.merge(options)
|
212
|
-
|
213
|
-
query(params)
|
195
|
+
def self.where(attributes)
|
196
|
+
_new_query.where(attributes)
|
214
197
|
end
|
215
198
|
|
216
199
|
def self.replace(attrs)
|
@@ -343,5 +326,9 @@ module Dynomite
|
|
343
326
|
@attrs[name.to_s] = value
|
344
327
|
end
|
345
328
|
end
|
329
|
+
|
330
|
+
def self._new_query
|
331
|
+
Dynomite::Query.new(self, {})
|
332
|
+
end
|
346
333
|
end
|
347
334
|
end
|
@@ -1,5 +1,12 @@
|
|
1
1
|
class Dynomite::Migration
|
2
2
|
class Dsl
|
3
|
+
ATTRIBUTES = %i[
|
4
|
+
key_schema
|
5
|
+
attribute_definitions
|
6
|
+
table_name
|
7
|
+
billing_mode
|
8
|
+
].freeze
|
9
|
+
|
3
10
|
autoload :Common, "dynomite/migration/common"
|
4
11
|
autoload :BaseSecondaryIndex, "dynomite/migration/dsl/base_secondary_index"
|
5
12
|
autoload :LocalSecondaryIndex, "dynomite/migration/dsl/local_secondary_index"
|
@@ -8,15 +15,16 @@ class Dynomite::Migration
|
|
8
15
|
include Dynomite::DbConfig
|
9
16
|
include Common
|
10
17
|
|
11
|
-
attr_accessor
|
12
|
-
|
18
|
+
attr_accessor(*ATTRIBUTES)
|
19
|
+
|
13
20
|
def initialize(method_name, table_name, &block)
|
14
21
|
@method_name = method_name
|
15
22
|
@table_name = table_name
|
16
23
|
@block = block
|
17
24
|
|
18
|
-
# Dsl fills in
|
25
|
+
# Dsl fills in attributes in as methods are called within the block.
|
19
26
|
# Attributes for both create_table and updated_table:
|
27
|
+
@billing_mode = 'PROVISIONED'
|
20
28
|
@attribute_definitions = []
|
21
29
|
@provisioned_throughput = {
|
22
30
|
read_capacity_units: 5,
|
@@ -31,6 +39,14 @@ class Dynomite::Migration
|
|
31
39
|
@lsi_indexes = []
|
32
40
|
end
|
33
41
|
|
42
|
+
# t.billing_mode(:pay_per_request)
|
43
|
+
# t.billing_mode(:provisioned) # default value
|
44
|
+
def billing_mode(mode = nil)
|
45
|
+
return @billing_mode if mode.nil?
|
46
|
+
|
47
|
+
@billing_mode = mode.to_s.upcase
|
48
|
+
end
|
49
|
+
|
34
50
|
# t.gsi(:create) do |i|
|
35
51
|
# i.partition_key "category:string"
|
36
52
|
# i.sort_key "created_at:string" # optional
|
@@ -81,9 +97,10 @@ class Dynomite::Migration
|
|
81
97
|
table_name: namespaced_table_name,
|
82
98
|
key_schema: @key_schema,
|
83
99
|
attribute_definitions: @attribute_definitions,
|
84
|
-
|
100
|
+
billing_mode: @billing_mode
|
85
101
|
}
|
86
102
|
|
103
|
+
params[:provisioned_throughput] = @provisioned_throughput if @billing_mode == 'PROVISIONED'
|
87
104
|
params[:local_secondary_indexes] = lsi_secondary_index_creates unless @lsi_indexes.empty?
|
88
105
|
params[:global_secondary_indexes] = gsi_secondary_index_creates unless @gsi_indexes.empty?
|
89
106
|
params
|
@@ -95,12 +112,15 @@ class Dynomite::Migration
|
|
95
112
|
params = {
|
96
113
|
table_name: namespaced_table_name,
|
97
114
|
attribute_definitions: @attribute_definitions,
|
115
|
+
billing_mode: @billing_mode
|
98
116
|
# update table take values only some values for the "parent" table
|
99
117
|
# no key_schema, update_table does not handle key_schema for the "parent" table
|
100
118
|
}
|
101
119
|
# only set "parent" table provisioned_throughput if user actually invoked
|
102
120
|
# it in the dsl
|
103
|
-
|
121
|
+
if @provisioned_throughput_set_called && @billing_mode == 'PROVISIONED'
|
122
|
+
params[:provisioned_throughput] = @provisioned_throughput
|
123
|
+
end
|
104
124
|
params[:global_secondary_index_updates] = global_secondary_index_updates
|
105
125
|
params
|
106
126
|
end
|
@@ -31,7 +31,7 @@ class Dynomite::Migration
|
|
31
31
|
|
32
32
|
puts "DynamoDB Table: #{@table_name} Status: #{result.table_description.table_status}"
|
33
33
|
rescue Aws::DynamoDB::Errors::ServiceError => error
|
34
|
-
puts "Unable to #{@method_name.to_s.gsub('_',' ')}: #{error.message}".
|
34
|
+
puts "Unable to #{@method_name.to_s.gsub('_',' ')}: #{error.message}".color(:red)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
@@ -31,7 +31,7 @@ class Dynomite::Migration
|
|
31
31
|
table_name: table_name,
|
32
32
|
partition_key: @options[:partition_key],
|
33
33
|
sort_key: @options[:sort_key],
|
34
|
-
provisioned_throughput: @options[:provisioned_throughput] || 5
|
34
|
+
provisioned_throughput: @options[:provisioned_throughput] || 5
|
35
35
|
)
|
36
36
|
end
|
37
37
|
|
@@ -5,6 +5,7 @@ class <%= @migration_class_name %> < Dynomite::Migration
|
|
5
5
|
<% if @sort_key # so extra spaces are not added when generated -%>
|
6
6
|
t.sort_key "<%= @sort_key %>" # optional
|
7
7
|
<% end -%>
|
8
|
+
t.billing_mode(:PROVISIONED)
|
8
9
|
t.provisioned_throughput(<%= @provisioned_throughput %>) # sets both read and write, defaults to 5 when not set
|
9
10
|
|
10
11
|
# Instead of using partition_key and sort_key you can set the
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Dynomite
|
2
|
+
class Query
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(item, params)
|
6
|
+
@item = item
|
7
|
+
@params = params
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(item)
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def inspect
|
15
|
+
"#<Dynomite::Query [#{first(2).map(&:inspect).join(', ')}, ...]>"
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&block)
|
19
|
+
run_query.each(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def index_name(name)
|
23
|
+
self.class.new(@item, @params.merge(index_name: name))
|
24
|
+
end
|
25
|
+
|
26
|
+
def where(attributes)
|
27
|
+
raise "attributes.size == 1 only supported for now" if attributes.size != 1
|
28
|
+
|
29
|
+
attr_name = attributes.keys.first
|
30
|
+
attr_value = attributes[attr_name]
|
31
|
+
|
32
|
+
name_key, value_key = "##{attr_name}_name", ":#{attr_name}_value"
|
33
|
+
params = {
|
34
|
+
expression_attribute_names: { name_key => attr_name },
|
35
|
+
expression_attribute_values: { value_key => attr_value },
|
36
|
+
key_condition_expression: "#{name_key} = #{value_key}",
|
37
|
+
}
|
38
|
+
|
39
|
+
self.class.new(@item, @params.merge(params))
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def run_query
|
45
|
+
@query ||= @item.query(@params)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/dynomite/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynomite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tung Nguyen
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-06-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -129,6 +129,7 @@ files:
|
|
129
129
|
- lib/dynomite/migration/generator.rb
|
130
130
|
- lib/dynomite/migration/templates/create_table.rb
|
131
131
|
- lib/dynomite/migration/templates/update_table.rb
|
132
|
+
- lib/dynomite/query.rb
|
132
133
|
- lib/dynomite/reserved_words.rb
|
133
134
|
- lib/dynomite/version.rb
|
134
135
|
homepage: https://github.com/tongueroo/dynomite
|
@@ -149,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
149
150
|
- !ruby/object:Gem::Version
|
150
151
|
version: '0'
|
151
152
|
requirements: []
|
152
|
-
rubygems_version: 3.
|
153
|
+
rubygems_version: 3.3.12
|
153
154
|
signing_key:
|
154
155
|
specification_version: 4
|
155
156
|
summary: ActiveRecord-ish Dynamodb Model
|