dynomite 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +6 -0
- data/README.md +141 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/migrations/long-example.rb +123 -0
- data/docs/migrations/short-example.rb +36 -0
- data/dynomite.gemspec +29 -0
- data/lib/dynomite.rb +23 -0
- data/lib/dynomite/core.rb +25 -0
- data/lib/dynomite/db_config.rb +101 -0
- data/lib/dynomite/erb.rb +53 -0
- data/lib/dynomite/item.rb +291 -0
- data/lib/dynomite/log.rb +15 -0
- data/lib/dynomite/migration.rb +27 -0
- data/lib/dynomite/migration/common.rb +86 -0
- data/lib/dynomite/migration/dsl.rb +172 -0
- data/lib/dynomite/migration/dsl/base_secondary_index.rb +72 -0
- data/lib/dynomite/migration/dsl/global_secondary_index.rb +4 -0
- data/lib/dynomite/migration/dsl/local_secondary_index.rb +8 -0
- data/lib/dynomite/migration/executor.rb +30 -0
- data/lib/dynomite/migration/generator.rb +68 -0
- data/lib/dynomite/migration/templates/create_table.rb +32 -0
- data/lib/dynomite/migration/templates/update_table.rb +26 -0
- data/lib/dynomite/version.rb +3 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 65cbe54a9b7faa1698f39915100793cb5c004725f16d2bd3cb5b73c55b87a92f
|
4
|
+
data.tar.gz: 0ceb719e4386c73ef6fd4ea108d7114d09e1da1bfab35d8695203422b998ea56
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2c36491debda7b2697e0f44442925420d29de096e5d28c73ae234cbb7f1dd939d491b965305fd7627f1700c6182752db8c7dff032e2502dfe8425a34b4291af5
|
7
|
+
data.tar.gz: 88991c61e3cd38d5b08eeb3c4e951ddb7b5a7bb3b26bae0bfac90140d3f370a7147a39af2c1b80c6f086921bd5e0c424a5e2e4f3a6d4551c48f52acfd207d3c2
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/CHANGELOG.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Change Log
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
This project *tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
|
5
|
+
|
6
|
+
## [1.0.5]
|
7
|
+
- fix jets dynamodb:migrate tip
|
8
|
+
|
9
|
+
## [1.0.4]
|
10
|
+
- Add and use log method instead of puts to write to stderr by default
|
11
|
+
|
12
|
+
## [1.0.3]
|
13
|
+
- rename APP_ROOT to JETS_ROOT
|
14
|
+
|
15
|
+
## [1.0.2]
|
16
|
+
- to_json for json rendering
|
17
|
+
|
18
|
+
## [1.0.1]
|
19
|
+
- Check dynamodb local is running when configured
|
20
|
+
|
21
|
+
## [1.0.0]
|
22
|
+
- LSI support
|
23
|
+
- automatically infer table_name
|
24
|
+
- automatically infer create_table and update_table migrations types
|
25
|
+
|
26
|
+
## [0.3.0]
|
27
|
+
- DSL methods now available: create_table, update_table
|
28
|
+
- Also can add GSI indexes within update_table with: i.gsi
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
# Dynomite
|
2
|
+
|
3
|
+
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:
|
4
|
+
|
5
|
+
## Examples
|
6
|
+
|
7
|
+
First define a class:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class Post < Dynomite::Item
|
11
|
+
# partition_key "id" # optional, defaults to id
|
12
|
+
end
|
13
|
+
```
|
14
|
+
|
15
|
+
### Create
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
post = Post.new
|
19
|
+
post = post.replace(title: "test title")
|
20
|
+
post.attrs # {"id" => "generated-id", title" => "my title"}
|
21
|
+
```
|
22
|
+
|
23
|
+
`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.
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
post = Post.new(id: "myid", title: "my title")
|
27
|
+
post.replace
|
28
|
+
post.attrs # {"id" => "myid", title" => "my title"}
|
29
|
+
```
|
30
|
+
|
31
|
+
Note that the replace method replaces the entire item, so you need to merge the attributes if you want to keep the other attributes. Know this is weird, but this is how DynamoDB works.
|
32
|
+
|
33
|
+
### Find
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
post = Post.find("myid")
|
37
|
+
post.attrs = post.attrs.deep_merge("desc": "my desc") # keeps title field
|
38
|
+
post.replace
|
39
|
+
post.attrs # {"id" => "myid", title" => "my title", desc: "my desc"}
|
40
|
+
```
|
41
|
+
|
42
|
+
The convenience `attrs` method performs a deep_merge:
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
post = Post.find("myid")
|
46
|
+
post.attrs("desc": "my desc 2") # <= does a deep_merge
|
47
|
+
post.replace
|
48
|
+
post.attrs # {"id" => "myid", title" => "my title", desc: "my desc 2"}
|
49
|
+
```
|
50
|
+
|
51
|
+
Note, a race condition edge case can exist when several concurrent replace
|
52
|
+
calls are happening. This is why the interface is called replace to
|
53
|
+
emphasis that possibility.
|
54
|
+
|
55
|
+
### Delete
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
resp = Post.delete("myid") # dynamodb client resp
|
59
|
+
# or
|
60
|
+
post = Post.find("myid")
|
61
|
+
resp = post.delete # dynamodb client resp
|
62
|
+
```
|
63
|
+
|
64
|
+
### Scan
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
options = {}
|
68
|
+
posts = Post.scan(options)
|
69
|
+
posts # Array of Post items. [Post.new, Post.new, ...]
|
70
|
+
```
|
71
|
+
|
72
|
+
### Query
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
posts = Post.query(
|
76
|
+
index_name: 'category-index',
|
77
|
+
expression_attribute_names: { "#category_name" => "category" },
|
78
|
+
expression_attribute_values: { ":category_value" => "Entertainment" },
|
79
|
+
key_condition_expression: "#category_name = :category_value",
|
80
|
+
)
|
81
|
+
posts # Array of Post items. [Post.new, Post.new, ...]
|
82
|
+
```
|
83
|
+
|
84
|
+
### Where
|
85
|
+
|
86
|
+
The where could be prettied up. Appreciate any pull requests.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
Post.where({category: "Drama"}, {index_name: "category-index"})
|
90
|
+
```
|
91
|
+
|
92
|
+
Examples are also in [item_spec.rb](spec/lib/dynomite/item_spec.rb).
|
93
|
+
|
94
|
+
## Migration Support
|
95
|
+
|
96
|
+
Dynomite supports ActiveRecord-like migrations. Here's a short example:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
class CreateCommentsMigration < Dynomite::Migration
|
100
|
+
def up
|
101
|
+
create_table :comments do |t|
|
102
|
+
t.partition_key "post_id:string" # required
|
103
|
+
t.sort_key "created_at:string" # optional
|
104
|
+
t.provisioned_throughput(5) # sets both read and write, defaults to 5 when not set
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
More examples are in the [docs/migrations](docs/migrations) folder.
|
111
|
+
|
112
|
+
## Installation
|
113
|
+
|
114
|
+
Add this line to your application's Gemfile:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
gem 'dynomite'
|
118
|
+
```
|
119
|
+
|
120
|
+
And then execute:
|
121
|
+
|
122
|
+
$ bundle
|
123
|
+
|
124
|
+
Or install it yourself as:
|
125
|
+
|
126
|
+
$ gem install dynomite
|
127
|
+
|
128
|
+
## Development
|
129
|
+
|
130
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
131
|
+
|
132
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
133
|
+
|
134
|
+
## Contributing
|
135
|
+
|
136
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tongueroo/dynomite.
|
137
|
+
|
138
|
+
### TODO
|
139
|
+
|
140
|
+
* improve Post.where. Something like `Post.index_name("user_id").where(category_name: "Entertainment")` would be nice.
|
141
|
+
* implement `post.update` with `db.update_item` in a Ruby-ish way
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "dynomite"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,123 @@
|
|
1
|
+
# Note: table name created will be namespaced based on
|
2
|
+
# Dynomite::Migration.table_namespace. This can be set in
|
3
|
+
# config/dynamodb.yml
|
4
|
+
#
|
5
|
+
# development:
|
6
|
+
# table_namespace: "mynamespace"
|
7
|
+
#
|
8
|
+
# This results in:
|
9
|
+
# create_table "posts"
|
10
|
+
# Produces:
|
11
|
+
# table name: "mynamespace-posts"
|
12
|
+
#
|
13
|
+
# When you're in a in Jets project you can set the namespace based on
|
14
|
+
# Jets.config.table_namespace, which is based on the project name and
|
15
|
+
# a short version of the environment. Example:
|
16
|
+
#
|
17
|
+
# `config/dynamodb.yml`:
|
18
|
+
# development:
|
19
|
+
# table_namespace: <%= Jets.config.table_namespace %>
|
20
|
+
#
|
21
|
+
# If your project_name is demo and environment is production:
|
22
|
+
# create_table "posts" => table name: "demo-prod-posts"
|
23
|
+
#
|
24
|
+
# If your project_name is proj and environment is staging:
|
25
|
+
# create_table "posts" => table name: "demo-stag-posts"
|
26
|
+
#
|
27
|
+
# If your project_name is proj and environment is development:
|
28
|
+
# create_table "posts" => table name: "demo-dev-posts"
|
29
|
+
#
|
30
|
+
# If the table_namespace is set to a blank string or nil, then a namespace
|
31
|
+
# will not be prepended at all.
|
32
|
+
|
33
|
+
class CreateCommentsMigration < Dynomite::Migration
|
34
|
+
def up
|
35
|
+
create_table :comments do |t|
|
36
|
+
t.partition_key "post_id:string" # required
|
37
|
+
t.sort_key "created_at:string" # optional
|
38
|
+
t.provisioned_throughput(5) # sets both read and write, defaults to 5 when not set
|
39
|
+
|
40
|
+
# Instead of using partition_key and sort_key you can set the
|
41
|
+
# key schema directly also
|
42
|
+
# t.key_schema([
|
43
|
+
# {attribute_name: "id", :key_type=>"HASH"},
|
44
|
+
# {attribute_name: "created_at", :key_type=>"RANGE"}
|
45
|
+
# ])
|
46
|
+
# t.attribute_definitions([
|
47
|
+
# {attribute_name: "id", attribute_type: "N"},
|
48
|
+
# {attribute_name: "created_at", attribute_type: "S"}
|
49
|
+
# ])
|
50
|
+
|
51
|
+
# other ways to set provisioned_throughput
|
52
|
+
# t.provisioned_throughput(:read, 10)
|
53
|
+
# t.provisioned_throughput(:write, 10)
|
54
|
+
# t.provisioned_throughput(
|
55
|
+
# read_capacity_units: 5,
|
56
|
+
# write_capacity_units: 5
|
57
|
+
# )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class UpdateCommentsMigration < Dynomite::Migration
|
63
|
+
def up
|
64
|
+
update_table :comments do |t|
|
65
|
+
|
66
|
+
# t.global_secondary_index do
|
67
|
+
# t.gsi(METHOD, INDEX_NAME) do
|
68
|
+
|
69
|
+
# You normally create an index like so:
|
70
|
+
#
|
71
|
+
# t.gsi(:create) do |i|
|
72
|
+
# i.partition_key = "post_id:string" # partition_key is required
|
73
|
+
# i.sort_key = "updated_at:string" # sort_key is optional
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# The index name will be inferred from the partition_key and sort_key when
|
77
|
+
# not explicitly set. Examples:
|
78
|
+
#
|
79
|
+
# index_name = "#{partition_key}-#{sort_key}-index"
|
80
|
+
# index_name = "post_id-index" # no sort key
|
81
|
+
# index_name = "post_id-updated_at-index" # has sort key
|
82
|
+
#
|
83
|
+
# The inference allows you to not have to worry about the index
|
84
|
+
# naming scheme. You can still set the index_name explicitly like so:
|
85
|
+
#
|
86
|
+
# t.gsi(:create, "post_id-updated_at-index") do |i|
|
87
|
+
# i.partition_key = "post_id:string" # partition_key is required
|
88
|
+
# i.sort_key = "updated_at:string" # sort_key is optional
|
89
|
+
# end
|
90
|
+
#
|
91
|
+
t.gsi(:create) do |i|
|
92
|
+
i.partition_key "post_id:string"
|
93
|
+
i.sort_key "updated_at:string" # optional
|
94
|
+
|
95
|
+
# translates to
|
96
|
+
# i.key_schema({...})
|
97
|
+
# also makes sure that the schema_keys are added to the attributes_definitions
|
98
|
+
|
99
|
+
# t.projected_attributes(:all) # default if not called
|
100
|
+
# t.projected_attributes(:keys_only) # other ways to call
|
101
|
+
# t.projected_attributes([:id, :body, :tags, :updated_at])
|
102
|
+
# translates to:
|
103
|
+
# Valid Values: ALL | KEYS_ONLY | INCLUDE
|
104
|
+
# t.projection(
|
105
|
+
# projection_type: :all, # defaults to all
|
106
|
+
# )
|
107
|
+
# t.projection(
|
108
|
+
# projection_type: :include, # defaults to all
|
109
|
+
# non_key_attributes: [:id, :body, :tags, :updated_at], # defaults to all
|
110
|
+
# )
|
111
|
+
|
112
|
+
i.provisioned_throughput(10)
|
113
|
+
end
|
114
|
+
|
115
|
+
t.gsi(:update, "category-index") do |i|
|
116
|
+
i.provisioned_throughput(10)
|
117
|
+
end
|
118
|
+
|
119
|
+
t.gsi(:delete, "category-index")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class CreateCommentsMigration < Dynomite::Migration
|
2
|
+
def up
|
3
|
+
create_table :comments do |t|
|
4
|
+
t.partition_key "post_id:string" # required
|
5
|
+
t.sort_key "created_at:string" # optional
|
6
|
+
t.provisioned_throughput(5) # sets both read and write, defaults to 5 when not set
|
7
|
+
|
8
|
+
t.lsi do |i|
|
9
|
+
i.partition_key "user_id:string"
|
10
|
+
i.sort_key "updated_at:string" # optional
|
11
|
+
|
12
|
+
i.provisioned_throughput(10)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class UpdateCommentsMigration < Dynomite::Migration
|
19
|
+
def up
|
20
|
+
update_table :comments do |t|
|
21
|
+
t.gsi(:create) do |i|
|
22
|
+
i.partition_key "post_id:string"
|
23
|
+
i.sort_key "updated_at:string" # optional
|
24
|
+
|
25
|
+
i.provisioned_throughput(10)
|
26
|
+
end
|
27
|
+
|
28
|
+
t.gsi(:update, "update-me-index") do |i|
|
29
|
+
i.provisioned_throughput(10)
|
30
|
+
end
|
31
|
+
|
32
|
+
t.gsi(:delete, "delete-me-index")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
data/dynomite.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "dynomite/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dynomite"
|
8
|
+
spec.version = Dynomite::VERSION
|
9
|
+
spec.authors = ["Tung Nguyen"]
|
10
|
+
spec.email = ["tongueroo@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{ActiveRecord-ish Dynamodb Model}
|
13
|
+
spec.description = %q{ActiveRecord-ish Dynamodb Model}
|
14
|
+
spec.homepage = "https://github.com/tongueroo/dynomite"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
|
+
f.match(%r{^(test|spec|features)/})
|
18
|
+
end
|
19
|
+
spec.bindir = "exe"
|
20
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_dependency "activesupport"
|
24
|
+
spec.add_dependency "aws-sdk-dynamodb"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler"
|
27
|
+
spec.add_development_dependency "rake"
|
28
|
+
spec.add_development_dependency "rspec"
|
29
|
+
end
|
data/lib/dynomite.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
$:.unshift(File.expand_path("../", __FILE__))
|
2
|
+
require "dynomite/version"
|
3
|
+
|
4
|
+
module Dynomite
|
5
|
+
ATTRIBUTE_TYPES = {
|
6
|
+
'string' => 'S',
|
7
|
+
'number' => 'N',
|
8
|
+
'binary' => 'B',
|
9
|
+
's' => 'S',
|
10
|
+
'n' => 'N',
|
11
|
+
'b' => 'B',
|
12
|
+
}
|
13
|
+
|
14
|
+
autoload :Migration, "dynomite/migration"
|
15
|
+
autoload :Dsl, "dynomite/dsl"
|
16
|
+
autoload :DbConfig, "dynomite/db_config"
|
17
|
+
autoload :Item, "dynomite/item"
|
18
|
+
autoload :Core, "dynomite/core"
|
19
|
+
autoload :Erb, "dynomite/erb"
|
20
|
+
autoload :Log, "dynomite/log"
|
21
|
+
|
22
|
+
extend Core
|
23
|
+
end
|