dynomite 1.2.7 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +17 -2
- data/CHANGELOG.md +18 -0
- data/Gemfile +1 -5
- data/LICENSE.txt +22 -0
- data/README.md +6 -190
- data/Rakefile +13 -1
- data/dynomite.gemspec +9 -2
- data/exe/dynomite +14 -0
- data/lib/dynomite/associations/association.rb +126 -0
- data/lib/dynomite/associations/belongs_to.rb +35 -0
- data/lib/dynomite/associations/has_and_belongs_to_many.rb +19 -0
- data/lib/dynomite/associations/has_many.rb +19 -0
- data/lib/dynomite/associations/has_one.rb +19 -0
- data/lib/dynomite/associations/many_association.rb +257 -0
- data/lib/dynomite/associations/single_association.rb +157 -0
- data/lib/dynomite/associations.rb +248 -0
- data/lib/dynomite/autoloader.rb +25 -0
- data/lib/dynomite/cli.rb +48 -0
- data/lib/dynomite/client.rb +118 -0
- data/lib/dynomite/command.rb +89 -0
- data/lib/dynomite/completer/script.rb +6 -0
- data/lib/dynomite/completer/script.sh +10 -0
- data/lib/dynomite/completer.rb +159 -0
- data/lib/dynomite/config.rb +39 -0
- data/lib/dynomite/core.rb +18 -19
- data/lib/dynomite/engine.rb +45 -0
- data/lib/dynomite/erb.rb +5 -3
- data/lib/dynomite/error.rb +12 -0
- data/lib/dynomite/help/completion.md +20 -0
- data/lib/dynomite/help/completion_script.md +3 -0
- data/lib/dynomite/help/migrate.md +3 -0
- data/lib/dynomite/help.rb +9 -0
- data/lib/dynomite/install.rb +4 -0
- data/lib/dynomite/item/abstract.rb +15 -0
- data/lib/dynomite/item/components.rb +33 -0
- data/lib/dynomite/item/dsl.rb +101 -0
- data/lib/dynomite/item/id.rb +41 -0
- data/lib/dynomite/item/indexes/finder.rb +58 -0
- data/lib/dynomite/item/indexes/index.rb +21 -0
- data/lib/dynomite/item/indexes/primary_index.rb +18 -0
- data/lib/dynomite/item/indexes.rb +25 -0
- data/lib/dynomite/item/locking.rb +53 -0
- data/lib/dynomite/item/magic_fields.rb +66 -0
- data/lib/dynomite/item/primary_key.rb +85 -0
- data/lib/dynomite/item/query/delegates.rb +28 -0
- data/lib/dynomite/item/query/params/base.rb +42 -0
- data/lib/dynomite/item/query/params/expression_attribute.rb +79 -0
- data/lib/dynomite/item/query/params/filter.rb +41 -0
- data/lib/dynomite/item/query/params/function/attribute_exists.rb +21 -0
- data/lib/dynomite/item/query/params/function/attribute_type.rb +30 -0
- data/lib/dynomite/item/query/params/function/base.rb +33 -0
- data/lib/dynomite/item/query/params/function/begins_with.rb +32 -0
- data/lib/dynomite/item/query/params/function/contains.rb +7 -0
- data/lib/dynomite/item/query/params/function/size_fn.rb +37 -0
- data/lib/dynomite/item/query/params/helpers.rb +94 -0
- data/lib/dynomite/item/query/params/key_condition.rb +34 -0
- data/lib/dynomite/item/query/params.rb +115 -0
- data/lib/dynomite/item/query/partiql/executer.rb +72 -0
- data/lib/dynomite/item/query/partiql.rb +67 -0
- data/lib/dynomite/item/query/relation/chain.rb +125 -0
- data/lib/dynomite/item/query/relation/comparision_expression.rb +21 -0
- data/lib/dynomite/item/query/relation/comparision_map.rb +19 -0
- data/lib/dynomite/item/query/relation/delete.rb +38 -0
- data/lib/dynomite/item/query/relation/ids.rb +21 -0
- data/lib/dynomite/item/query/relation/math.rb +19 -0
- data/lib/dynomite/item/query/relation/where_field.rb +32 -0
- data/lib/dynomite/item/query/relation/where_group.rb +78 -0
- data/lib/dynomite/item/query/relation.rb +127 -0
- data/lib/dynomite/item/query.rb +7 -0
- data/lib/dynomite/item/read/find.rb +196 -0
- data/lib/dynomite/item/read/find_with_event.rb +42 -0
- data/lib/dynomite/item/read.rb +90 -0
- data/lib/dynomite/item/sti.rb +43 -0
- data/lib/dynomite/item/table_namespace.rb +43 -0
- data/lib/dynomite/item/typecaster.rb +106 -0
- data/lib/dynomite/item/waiter_methods.rb +18 -0
- data/lib/dynomite/item/write/base.rb +15 -0
- data/lib/dynomite/item/write/delete_item.rb +14 -0
- data/lib/dynomite/item/write/put_item.rb +99 -0
- data/lib/dynomite/item/write/update_item.rb +73 -0
- data/lib/dynomite/item/write.rb +204 -0
- data/lib/dynomite/item.rb +113 -286
- data/lib/dynomite/migration/dsl/accessor.rb +19 -0
- data/lib/dynomite/migration/dsl/index/base.rb +42 -0
- data/lib/dynomite/migration/dsl/index/gsi.rb +59 -0
- data/lib/dynomite/migration/dsl/index/lsi.rb +27 -0
- data/lib/dynomite/migration/dsl/index.rb +72 -0
- data/lib/dynomite/migration/dsl/primary_key.rb +62 -0
- data/lib/dynomite/migration/dsl/provisioned_throughput.rb +38 -0
- data/lib/dynomite/migration/dsl.rb +89 -142
- data/lib/dynomite/migration/file_info.rb +28 -0
- data/lib/dynomite/migration/generator.rb +30 -16
- data/lib/dynomite/migration/helpers.rb +7 -0
- data/lib/dynomite/migration/internal/migrate/create_schema_migrations.rb +17 -0
- data/lib/dynomite/migration/internal/models/schema_migration.rb +6 -0
- data/lib/dynomite/migration/runner.rb +178 -0
- data/lib/dynomite/migration/templates/create_table.rb +7 -23
- data/lib/dynomite/migration/templates/delete_table.rb +7 -0
- data/lib/dynomite/migration/templates/update_table.rb +3 -18
- data/lib/dynomite/migration.rb +53 -10
- data/lib/dynomite/reserved_words.rb +13 -3
- data/lib/dynomite/seed.rb +12 -0
- data/lib/dynomite/types.rb +22 -0
- data/lib/dynomite/version.rb +1 -1
- data/lib/dynomite/waiter.rb +40 -0
- data/lib/dynomite.rb +11 -17
- data/lib/generators/application_item/application_item_generator.rb +30 -0
- data/lib/generators/application_item/templates/application_item.rb.tt +4 -0
- data/lib/jets/commands/dynamodb_command.rb +29 -0
- data/lib/jets/commands/help/generate.md +33 -0
- data/lib/jets/commands/help/migrate.md +3 -0
- metadata +201 -17
- data/docs/migrations/long-example.rb +0 -127
- data/docs/migrations/short-example.rb +0 -40
- data/lib/dynomite/db_config.rb +0 -121
- data/lib/dynomite/errors.rb +0 -15
- data/lib/dynomite/log.rb +0 -15
- data/lib/dynomite/migration/common.rb +0 -86
- data/lib/dynomite/migration/dsl/base_secondary_index.rb +0 -73
- data/lib/dynomite/migration/dsl/global_secondary_index.rb +0 -4
- data/lib/dynomite/migration/dsl/local_secondary_index.rb +0 -8
- data/lib/dynomite/migration/executor.rb +0 -38
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0609ff0c2a5e95266162de30b16a2c10a9be1255f1ce4ed23672fea369dc67b0'
|
4
|
+
data.tar.gz: a7e2eb874c3be932c85e04f027d7c5da778bf6cc9671e1d173a169e62109ad83
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 872e734331f516ae93d2c9a89452258ed448058597bfff3854e82d68b6e80bfe8a18a61a65167c3435c20776d8a58f01288787c441a2738cb126ee22db4620ca
|
7
|
+
data.tar.gz: 2f572680129343676ed4a92a9aa58d979f494d2da1fb26592681959ef14a4bd670c0f5cf9ac8705e611ce1d09384ccd78f56b16fbbd6285344c25f74557f9620
|
data/.gitignore
CHANGED
@@ -1,10 +1,25 @@
|
|
1
|
+
.bundle
|
2
|
+
.config
|
3
|
+
*.gem
|
4
|
+
*.rbc
|
5
|
+
/_yardoc/
|
1
6
|
/.bundle/
|
2
7
|
/.yardoc
|
3
|
-
/Gemfile.lock
|
4
|
-
/_yardoc/
|
5
8
|
/coverage/
|
6
9
|
/doc/
|
10
|
+
/Gemfile.lock
|
7
11
|
/pkg/
|
8
12
|
/spec/reports/
|
9
13
|
/tmp/
|
14
|
+
coverage
|
15
|
+
doc/
|
16
|
+
Gemfile.lock
|
17
|
+
InstalledFiles
|
18
|
+
lib/bundler/man
|
19
|
+
pkg
|
20
|
+
rdoc
|
21
|
+
spec/reports
|
22
|
+
test/tmp
|
23
|
+
test/version_tmp
|
24
|
+
tmp
|
10
25
|
vendor/bundle
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,24 @@
|
|
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
|
+
## [2.0.0] - 2023-12-03
|
7
|
+
- [#35](https://github.com/tongueroo/dynomite/pull/35) ActiveModel compatible
|
8
|
+
- Breaking change interface to be ActiveModel compatible
|
9
|
+
- ActiveModel: validations, callbacks, etc
|
10
|
+
- Use zeitwerk for autoloading
|
11
|
+
- Typecast support for DateTime-like objects. Store date as iso8601 string.
|
12
|
+
- Remove config/dynamodb.yml in favor of Dynomite.configure for use with initializers
|
13
|
+
- namespace separator default is _ instead of -
|
14
|
+
- Dynomite.logger introduced
|
15
|
+
- arel like where query builder interface
|
16
|
+
- finder methods: all, first, last, find_by, find, count
|
17
|
+
- index finder: automatically use query over scan with where when possible
|
18
|
+
- organize query to read and write ruby files
|
19
|
+
- Migrations: improved migrate command. No need to specify files.
|
20
|
+
- namespaced schema_migrations table tracks ran migrations.
|
21
|
+
- Favor ondemand provisioning vs explicit provisioned_throughput
|
22
|
+
- Standalone dynamodb cli to generate migrations and run them
|
23
|
+
|
6
24
|
## [1.2.7] - 2022-06-12
|
7
25
|
- [#23](https://github.com/tongueroo/dynomite/pull/23) #where method refactor to allow Model.index_name('index').where(...)
|
8
26
|
- [#24](https://github.com/tongueroo/dynomite/pull/24) Add get_endpoint_ip to db_config.rb
|
data/Gemfile
CHANGED
@@ -2,9 +2,5 @@ source "https://rubygems.org"
|
|
2
2
|
|
3
3
|
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
4
4
|
|
5
|
-
# Specify your gem
|
5
|
+
# Specify your gem dependencies in dynomite.gemspec
|
6
6
|
gemspec
|
7
|
-
|
8
|
-
group :development, :test do
|
9
|
-
gem "activemodel"
|
10
|
-
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) Tung Nguyen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,197 +1,13 @@
|
|
1
1
|
# Dynomite
|
2
2
|
|
3
|
-
[![
|
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.
|
8
|
-
|
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:
|
10
|
-
|
11
|
-
## Jets Docs
|
12
|
-
|
13
|
-
* [Database DynamoDB](https://rubyonjets.com/docs/database/dynamodb/)
|
14
|
-
|
15
|
-
## Examples
|
16
|
-
|
17
|
-
First define a class:
|
18
|
-
|
19
|
-
```ruby
|
20
|
-
class Post < Dynomite::Item
|
21
|
-
# partition_key "id" # optional, defaults to id
|
22
|
-
|
23
|
-
column :id, :title, :desc
|
24
|
-
end
|
25
|
-
```
|
26
|
-
|
27
|
-
### Create
|
28
|
-
|
29
|
-
```ruby
|
30
|
-
post = Post.new
|
31
|
-
post = post.replace(title: "test title")
|
32
|
-
post.attrs # {"id" => "generated-id", title" => "test title"}
|
33
|
-
```
|
34
|
-
|
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.
|
36
|
-
|
37
|
-
```ruby
|
38
|
-
post = Post.new(id: "myid", title: "my title")
|
39
|
-
post.replace
|
40
|
-
post.attrs # {"id" => "myid", title" => "my title"}
|
41
|
-
```
|
42
|
-
|
43
|
-
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.
|
44
|
-
|
45
|
-
### Find
|
46
|
-
|
47
|
-
```ruby
|
48
|
-
post = Post.find("myid")
|
49
|
-
post.attrs = post.attrs.deep_merge("desc": "my desc") # keeps title field
|
50
|
-
post.replace
|
51
|
-
post.attrs # {"id" => "myid", title" => "my title", desc: "my desc"}
|
52
|
-
```
|
53
|
-
|
54
|
-
The convenience `attrs` method performs a deep_merge:
|
55
|
-
|
56
|
-
```ruby
|
57
|
-
post = Post.find("myid")
|
58
|
-
post.attrs("desc": "my desc 2") # <= does a deep_merge
|
59
|
-
post.replace
|
60
|
-
post.attrs # {"id" => "myid", title" => "my title", desc: "my desc 2"}
|
61
|
-
```
|
62
|
-
|
63
|
-
Note, a race condition edge case can exist when several concurrent replace
|
64
|
-
calls are happening. This is why the interface is called replace to
|
65
|
-
emphasize that possibility.
|
66
|
-
|
67
|
-
### Delete
|
68
|
-
|
69
|
-
```ruby
|
70
|
-
resp = Post.delete("myid") # dynamodb client resp
|
71
|
-
# or
|
72
|
-
post = Post.find("myid")
|
73
|
-
resp = post.delete # dynamodb client resp
|
74
|
-
```
|
75
|
-
|
76
|
-
### Scan
|
77
|
-
|
78
|
-
```ruby
|
79
|
-
options = {}
|
80
|
-
posts = Post.scan(options)
|
81
|
-
posts # Array of Post items. [Post.new, Post.new, ...]
|
82
|
-
```
|
83
|
-
|
84
|
-
### Query
|
85
|
-
|
86
|
-
```ruby
|
87
|
-
posts = Post.query(
|
88
|
-
index_name: 'category-index',
|
89
|
-
expression_attribute_names: { "#category_name" => "category" },
|
90
|
-
expression_attribute_values: { ":category_value" => "Entertainment" },
|
91
|
-
key_condition_expression: "#category_name = :category_value",
|
92
|
-
)
|
93
|
-
posts # Array of Post items. [Post.new, Post.new, ...]
|
94
|
-
```
|
95
|
-
|
96
|
-
### Where
|
97
|
-
|
98
|
-
The where could be prettied up. Appreciate any pull requests.
|
99
|
-
|
100
|
-
```ruby
|
101
|
-
Post.where({category: "Drama"}, {index_name: "category-index"})
|
102
|
-
```
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/dynomite.svg)](http://badge.fury.io/rb/dynomite)
|
103
4
|
|
104
|
-
|
105
|
-
|
106
|
-
## Column Lists
|
107
|
-
|
108
|
-
You can define your column list using the `column` method inside your item class. This gives you
|
109
|
-
a possibility to access your column fields using getters and setters. Also, any predefined column
|
110
|
-
can be passed to `ActiveModel::Validations` (see Validations).
|
111
|
-
|
112
|
-
```ruby
|
113
|
-
class Post < Dynomite::Item
|
114
|
-
column :id, :name
|
115
|
-
end
|
116
|
-
|
117
|
-
post = Post.new
|
118
|
-
post.name = "My First Post"
|
119
|
-
post.replace
|
120
|
-
|
121
|
-
puts post.id # 1962DE7D852298C5CDC809C0FEF50D8262CEDF09
|
122
|
-
puts post.name # "My First Post"
|
123
|
-
```
|
124
|
-
|
125
|
-
Note that any column not defined using the `column` method can still be accessed using the `attrs`
|
126
|
-
method.
|
127
|
-
|
128
|
-
## Validations
|
129
|
-
|
130
|
-
You can add validations known from ActiveRecord to your Dynomite items.
|
131
|
-
Just add `include ActiveModel::Validations` at the top of your item class.
|
132
|
-
|
133
|
-
```ruby
|
134
|
-
class Post < Dynomite::Item
|
135
|
-
include ActiveModel::Validations
|
136
|
-
|
137
|
-
column :id, :name # needed
|
138
|
-
|
139
|
-
validates :id, presence: true
|
140
|
-
validates :name, presence: true
|
141
|
-
end
|
142
|
-
```
|
143
|
-
|
144
|
-
**Be sure to define all validated columns using `column` method**.
|
145
|
-
|
146
|
-
Validations are executed by default as soon as you call the `replace` method, returning `false` on
|
147
|
-
failure. It also can be ran manually using the `valid?` method just like with ActiveRecord models.
|
148
|
-
|
149
|
-
|
150
|
-
## Migration Support
|
151
|
-
|
152
|
-
Dynomite supports ActiveRecord-like migrations. Here's a short example:
|
153
|
-
|
154
|
-
```ruby
|
155
|
-
class CreateCommentsMigration < Dynomite::Migration
|
156
|
-
def up
|
157
|
-
create_table :comments do |t|
|
158
|
-
t.partition_key "post_id:string" # required
|
159
|
-
t.sort_key "created_at:string" # optional
|
160
|
-
t.provisioned_throughput(5) # sets both read and write, defaults to 5 when not set
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
```
|
165
|
-
|
166
|
-
More examples are in the [docs/migrations](docs/migrations) folder.
|
167
|
-
|
168
|
-
## Installation
|
169
|
-
|
170
|
-
Add this line to your application's Gemfile:
|
171
|
-
|
172
|
-
```ruby
|
173
|
-
gem 'dynomite'
|
174
|
-
```
|
175
|
-
|
176
|
-
And then execute:
|
177
|
-
|
178
|
-
$ bundle
|
179
|
-
|
180
|
-
Or install it yourself as:
|
181
|
-
|
182
|
-
$ gem install dynomite
|
183
|
-
|
184
|
-
## Development
|
185
|
-
|
186
|
-
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.
|
187
|
-
|
188
|
-
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).
|
5
|
+
[![BoltOps Badge](https://img.boltops.com/boltops/badges/boltops-badge.png)](https://www.boltops.com)
|
189
6
|
|
190
|
-
|
7
|
+
[![BoltOps Learn Badge](https://img.boltops.com/boltops-learn/boltops-learn.png)](https://learn.boltops.com)
|
191
8
|
|
192
|
-
|
9
|
+
A DynamoDB ORM that is ActiveModel compatible.
|
193
10
|
|
194
|
-
|
11
|
+
## Dynomite Docs
|
195
12
|
|
196
|
-
*
|
197
|
-
* implement `post.update` with `db.update_item` in a Ruby-ish way
|
13
|
+
* [Database Dynomite](https://rubyonjets.com/docs/database/dynamodb/)
|
data/Rakefile
CHANGED
@@ -1,2 +1,14 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
task default: :spec
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new
|
7
|
+
|
8
|
+
require_relative "lib/dynomite"
|
9
|
+
require "cli_markdown"
|
10
|
+
desc "Generates cli reference docs as markdown"
|
11
|
+
task :docs do
|
12
|
+
mkdir_p "docs/_includes"
|
13
|
+
CliMarkdown::Creator.create_all(cli_class: Dynomite::CLI, cli_name: "dynomite")
|
14
|
+
end
|
data/dynomite.gemspec
CHANGED
@@ -9,9 +9,9 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["Tung Nguyen"]
|
10
10
|
spec.email = ["tongueroo@gmail.com"]
|
11
11
|
|
12
|
-
spec.summary =
|
13
|
-
spec.description = %q{ActiveRecord-ish Dynamodb Model}
|
12
|
+
spec.summary = "ActiveRecord-ish DynamoDB ORM"
|
14
13
|
spec.homepage = "https://github.com/tongueroo/dynomite"
|
14
|
+
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
17
17
|
f.match(%r{^(test|spec|features)/})
|
@@ -20,11 +20,18 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
+
spec.add_dependency "activemodel"
|
23
24
|
spec.add_dependency "activesupport"
|
24
25
|
spec.add_dependency "aws-sdk-dynamodb"
|
26
|
+
spec.add_dependency "memoist"
|
25
27
|
spec.add_dependency "rainbow"
|
28
|
+
spec.add_dependency "thor"
|
29
|
+
spec.add_dependency "zeitwerk"
|
26
30
|
|
27
31
|
spec.add_development_dependency "bundler"
|
32
|
+
spec.add_development_dependency "byebug"
|
33
|
+
spec.add_development_dependency "cli_markdown"
|
34
|
+
spec.add_development_dependency "nokogiri"
|
28
35
|
spec.add_development_dependency "rake"
|
29
36
|
spec.add_development_dependency "rspec"
|
30
37
|
end
|
data/exe/dynomite
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Trap ^C
|
4
|
+
Signal.trap("INT") {
|
5
|
+
puts "\nCtrl-C detected. Exiting..."
|
6
|
+
sleep 0.1
|
7
|
+
exit
|
8
|
+
}
|
9
|
+
|
10
|
+
$:.unshift(File.expand_path("../../lib", __FILE__))
|
11
|
+
require "dynomite"
|
12
|
+
require "dynomite/cli"
|
13
|
+
|
14
|
+
Dynomite::CLI.start(ARGV)
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Dynomite
|
2
|
+
# The base association module which all associations include. Every association has two very important components: the source and
|
3
|
+
# the target. The source is the object which is calling the association information. It always has the target_ids inside of an attribute on itself.
|
4
|
+
# The target is the object which is referencing by this association.
|
5
|
+
module Associations
|
6
|
+
module Association
|
7
|
+
attr_accessor :name, :options, :source, :loaded
|
8
|
+
|
9
|
+
# Create a new association.
|
10
|
+
#
|
11
|
+
# @param [Class] source the source record of the association; that is, the record that you already have
|
12
|
+
# @param [Symbol] name the name of the association
|
13
|
+
# @param [Hash] options optional parameters for the association
|
14
|
+
# @option options [Class] :class the target class of the association; that is, the class to which the association objects belong
|
15
|
+
# @option options [Symbol] :class_name the name of the target class of the association; only this or Class is necessary
|
16
|
+
# @option options [Symbol] :inverse_of the name of the association on the target class
|
17
|
+
# @option options [Symbol] :foreign_key the name of the field for belongs_to association
|
18
|
+
#
|
19
|
+
# @return [Dynomite::Association] the actual association instance itself
|
20
|
+
def initialize(source, name, options)
|
21
|
+
@source = source
|
22
|
+
@name = name
|
23
|
+
@options = options
|
24
|
+
@loaded = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def coerce_to_id(object)
|
28
|
+
object.respond_to?(:partition_key) ? object.partition_key : object
|
29
|
+
end
|
30
|
+
|
31
|
+
def coerce_to_item(object)
|
32
|
+
object.is_a?(String) ? target_class.find(object) : object
|
33
|
+
end
|
34
|
+
|
35
|
+
def loaded?
|
36
|
+
@loaded
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_target; end
|
40
|
+
|
41
|
+
def target
|
42
|
+
unless loaded?
|
43
|
+
@target = find_target
|
44
|
+
@loaded = true
|
45
|
+
end
|
46
|
+
|
47
|
+
@target
|
48
|
+
end
|
49
|
+
|
50
|
+
def reader_target
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def reset
|
55
|
+
@target = nil
|
56
|
+
@loaded = false
|
57
|
+
end
|
58
|
+
|
59
|
+
def declaration_field_name
|
60
|
+
"#{name}_ids"
|
61
|
+
end
|
62
|
+
|
63
|
+
def declaration_field_type
|
64
|
+
:set
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
# The target class name, either inferred through the association's name or specified in options.
|
70
|
+
def target_class_name
|
71
|
+
options[:class_name] || name.to_s.classify
|
72
|
+
end
|
73
|
+
|
74
|
+
# The target class, either inferred through the association's name or specified in options.
|
75
|
+
def target_class
|
76
|
+
options[:class] || target_class_name.constantize
|
77
|
+
end
|
78
|
+
|
79
|
+
# The target attribute: that is, the attribute on each object of the association that should reference the source.
|
80
|
+
def target_attribute
|
81
|
+
# In simple case it's equivalent to
|
82
|
+
# "#{target_association}_ids".to_sym if target_association
|
83
|
+
if target_association
|
84
|
+
target_options = target_class.associations[target_association]
|
85
|
+
assoc = Dynomite::Associations.const_get(target_options[:type].to_s.camelcase).new(nil, target_association, target_options)
|
86
|
+
assoc.send(:source_attribute)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# The ids in the target association.
|
91
|
+
def target_ids
|
92
|
+
target.send(target_attribute) || Set.new
|
93
|
+
end
|
94
|
+
|
95
|
+
# The ids in the target association.
|
96
|
+
def source_class
|
97
|
+
source.class
|
98
|
+
end
|
99
|
+
|
100
|
+
# The source's association attribute: the name of the association with _ids afterwards, like "users_ids".
|
101
|
+
def source_attribute
|
102
|
+
declaration_field_name.to_sym
|
103
|
+
end
|
104
|
+
|
105
|
+
# The ids in the source association.
|
106
|
+
def source_ids
|
107
|
+
# handle case when we store scalar value instead of collection (when foreign_key option is specified)
|
108
|
+
Array(source.send(source_attribute)).compact.to_set || Set.new
|
109
|
+
end
|
110
|
+
|
111
|
+
# Create a new instance of the target class without trying to add it to the association. This creates a base, that caller can update before setting or adding it.
|
112
|
+
#
|
113
|
+
# @param attributes [Hash] attribute values for the new object
|
114
|
+
#
|
115
|
+
# @return [Dynomite::Item] the newly-created object
|
116
|
+
def build(attributes = {})
|
117
|
+
target_class.build(attributes)
|
118
|
+
end
|
119
|
+
|
120
|
+
def association_method_name(name)
|
121
|
+
name = name.to_s.end_with?("_association") ? name : "#{name}_association"
|
122
|
+
name.starts_with?("_") ? name : "_#{name}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Dynomite
|
2
|
+
# The belongs_to association. For belongs_to, we reference only a single target instead of multiple records; that target is the
|
3
|
+
# item to which the association item is associated.
|
4
|
+
module Associations
|
5
|
+
class BelongsTo
|
6
|
+
include SingleAssociation
|
7
|
+
|
8
|
+
def declaration_field_type
|
9
|
+
if options[:foreign_key]
|
10
|
+
target_class.attributes[target_class.partition_key][:type]
|
11
|
+
else
|
12
|
+
:set
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
# Find the target association, either has_many or has_one. Uses either options[:inverse_of] or the source class name and default parsing to
|
19
|
+
# return the most likely name for the target association.
|
20
|
+
def target_association
|
21
|
+
has_many_key_name = options[:inverse_of] || source.class.to_s.underscore.pluralize.to_sym
|
22
|
+
has_one_key_name = options[:inverse_of] || source.class.to_s.underscore.to_sym
|
23
|
+
unless target_class.associations[has_many_key_name].nil?
|
24
|
+
method_name = association_method_name(has_many_key_name)
|
25
|
+
return method_name if target_class.associations[has_many_key_name][:type] == :has_many
|
26
|
+
end
|
27
|
+
|
28
|
+
unless target_class.associations[has_one_key_name].nil?
|
29
|
+
method_name = association_method_name(has_one_key_name)
|
30
|
+
return method_name if target_class.associations[has_one_key_name][:type] == :has_one
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dynomite
|
2
|
+
module Associations
|
3
|
+
class HasAndBelongsToMany
|
4
|
+
include ManyAssociation
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
# Find the target association, always another :has_and_belongs_to_many association. Uses either options[:inverse_of] or the source class name
|
9
|
+
# and default parsing to return the most likely name for the target association.
|
10
|
+
def target_association
|
11
|
+
key_name = options[:inverse_of] || source.class.to_s.pluralize.underscore.to_sym
|
12
|
+
guess = target_class.associations[key_name]
|
13
|
+
return nil if guess.nil? || guess[:type] != :has_and_belongs_to_many
|
14
|
+
|
15
|
+
association_method_name(key_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dynomite
|
2
|
+
module Associations
|
3
|
+
class HasMany
|
4
|
+
include ManyAssociation
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
# Find the target association, always a :belongs_to association. Uses either options[:inverse_of] or the source class name
|
9
|
+
# and default parsing to return the most likely name for the target association.
|
10
|
+
def target_association
|
11
|
+
key_name = options[:inverse_of] || source.class.to_s.singularize.underscore.to_sym
|
12
|
+
guess = target_class.associations[key_name]
|
13
|
+
return nil if guess.nil? || guess[:type] != :belongs_to
|
14
|
+
|
15
|
+
association_method_name(key_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dynomite
|
2
|
+
module Associations
|
3
|
+
class HasOne
|
4
|
+
include SingleAssociation
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
# Find the target association, always a :belongs_to association. Uses either options[:inverse_of] or the source class name
|
9
|
+
# and default parsing to return the most likely name for the target association.
|
10
|
+
def target_association
|
11
|
+
key_name = options[:inverse_of] || source.class.to_s.singularize.underscore.to_sym
|
12
|
+
guess = target_class.associations[key_name]
|
13
|
+
return nil if guess.nil? || guess[:type] != :belongs_to
|
14
|
+
|
15
|
+
association_method_name(key_name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|