dynamodb_model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7f4086919c79934456c55927f2c127cb983f3f16
4
+ data.tar.gz: 00da845314f039d065ff7c122c7a7f7d85d33e08
5
+ SHA512:
6
+ metadata.gz: c47587fa7275602096f3626175abfaf96e8f1c2e538c20558bcee5dc15c69218d92c3dc04c916afad8871a369e0fb1c2a8b84c866e80def1ec316fbffe870be4
7
+ data.tar.gz: 4b9f9f3754e5d8ec6fd8b6492c40d877fa9d3d4c4b2a310f4addbf677046725c30534a81fc9c78fc57b4c4282db310f77280f483a067eb946c8ba205ecd40d88
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --color
3
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in dynamodb_model.gemspec
6
+ gemspec
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # DynamodbModel
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 and the [item_spec.rb](spec/lib/dynamodb_model/item_spec.rb) explain it best:
4
+
5
+ ## Examples
6
+
7
+ First define a class:
8
+
9
+ ```ruby
10
+ class Post < DynamodbModel::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")
59
+ resp # dynamodb client resp
60
+ ```
61
+
62
+ ### Scan
63
+
64
+ ```ruby
65
+ options = {}
66
+ posts = Post.scan(options)
67
+ posts # Array of Post items. [Post.new, Post.new, ...]
68
+ ```
69
+
70
+ ## Installation
71
+
72
+ Add this line to your application's Gemfile:
73
+
74
+ ```ruby
75
+ gem 'dynamodb_model'
76
+ ```
77
+
78
+ And then execute:
79
+
80
+ $ bundle
81
+
82
+ Or install it yourself as:
83
+
84
+ $ gem install dynamodb_model
85
+
86
+ ## Development
87
+
88
+ 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.
89
+
90
+ 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).
91
+
92
+ ## Contributing
93
+
94
+ Bug reports and pull requests are welcome on GitHub at https://github.com/tongueroo/dynamodb_model.
95
+
96
+ TODO
97
+
98
+ * implement Post.query
99
+ * implement `post.update` with `db.update_item` in a Ruby-ish way
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dynamodb_model"
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,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "dynamodb_model/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "dynamodb_model"
8
+ spec.version = DynamodbModel::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/dynamodb_model"
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
+
25
+ spec.add_development_dependency "bundler"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec"
28
+ end
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.expand_path("../", __FILE__))
2
+ require "dynamodb_model/version"
3
+
4
+ module DynamodbModel
5
+ autoload :Migration, "dynamodb_model/migration"
6
+ autoload :Dsl, "dynamodb_model/dsl"
7
+ autoload :DbConfig, "dynamodb_model/db_config"
8
+ autoload :Item, "dynamodb_model/item"
9
+ end
@@ -0,0 +1,45 @@
1
+ module DynamodbModel::DbConfig
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ end
5
+
6
+ def db
7
+ self.class.db
8
+ end
9
+
10
+ module ClassMethods
11
+ @@db = nil
12
+ def db
13
+ return @@db if @@db
14
+
15
+ config = db_config
16
+ endpoint = ENV['DYNAMODB_ENDPOINT'] || config['endpoint']
17
+ Aws.config.update(endpoint: endpoint) if endpoint
18
+
19
+ @@db ||= Aws::DynamoDB::Client.new
20
+ end
21
+
22
+ # useful for specs
23
+ def db=(db)
24
+ @@db = db
25
+ end
26
+
27
+ def db_config
28
+ if defined?(Jets)
29
+ YAML.load_file("#{Jets.root}config/dynamodb.yml")[Jets.env] || {}
30
+ else
31
+ config_path = ENV['DYNAMODB_MODEL_CONFIG'] || "./config/dynamodb.yml"
32
+ env = ENV['DYNAMODB_MODEL_ENV'] || "development"
33
+ YAML.load_file(config_path)[env] || {}
34
+ end
35
+ end
36
+
37
+ @table_namespace = nil
38
+ def table_namespace
39
+ return @table_namespace if @table_namespace
40
+
41
+ config = db_config
42
+ @table_namespace = config['table_namespace']
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,207 @@
1
+ require "active_support/core_ext/hash"
2
+ require "aws-sdk-dynamodb"
3
+ require "digest"
4
+ require "yaml"
5
+
6
+ # The modeling is ActiveRecord-ish but not exactly because DynamoDB is a
7
+ # different type of database.
8
+ #
9
+ # Examples:
10
+ #
11
+ # post = Post.new
12
+ # post = post.replace(title: "test title")
13
+ #
14
+ # post.attrs[:id] now contain a generaetd unique partition_key id.
15
+ # Usually the partition_key is 'id'. You can set your own unique id also:
16
+ #
17
+ # post = Post.new(id: "myid", title: "my title")
18
+ # post.replace
19
+ #
20
+ # Note that the replace method replaces the entire item, so you
21
+ # need to merge the attributes if you want to keep the other attributes.
22
+ #
23
+ # post = Post.find("myid")
24
+ # post.attrs = post.attrs.deep_merge("desc": "my desc") # keeps title field
25
+ # post.replace
26
+ #
27
+ # The convenience `attrs` method performs a deep_merge:
28
+ #
29
+ # post = Post.find("myid")
30
+ # post.attrs("desc": "my desc") # <= does a deep_merge
31
+ # post.replace
32
+ #
33
+ # Note, a race condition edge case can exist when several concurrent replace
34
+ # calls are happening. This is why the interface is called replace to
35
+ # emphasis that possibility.
36
+ # TODO: implement post.update with db.update_item in a Ruby-ish way.
37
+ #
38
+ module DynamodbModel
39
+ class Item
40
+ include DbConfig
41
+
42
+ def initialize(attrs={})
43
+ @attrs = attrs
44
+ end
45
+
46
+ # Defining our own reader so we can do a deep merge if user passes in attrs
47
+ def attrs(*args)
48
+ case args.size
49
+ when 0
50
+ ActiveSupport::HashWithIndifferentAccess.new(@attrs)
51
+ when 1
52
+ attributes = args[0] # Hash
53
+ if attributes.empty?
54
+ ActiveSupport::HashWithIndifferentAccess.new
55
+ else
56
+ @attrs = attrs.deep_merge!(attributes)
57
+ end
58
+ end
59
+ end
60
+
61
+ # Not using method_missing to allow usage of dot notation and assign
62
+ # @attrs because it might hide actual missing methods errors.
63
+ # DynamoDB attrs can go many levels deep so it makes less make sense to
64
+ # use to dot notation.
65
+
66
+ # The method is named replace to clearly indicate that the item is
67
+ # fully replaced.
68
+ def replace
69
+ attrs = self.class.replace(@attrs)
70
+ @attrs = attrs # refresh attrs because it now has the id
71
+ end
72
+
73
+ def find(id)
74
+ self.class.find(id)
75
+ end
76
+
77
+ def table_name
78
+ self.class.table_name
79
+ end
80
+
81
+ def partition_key
82
+ self.class.partition_key
83
+ end
84
+
85
+ def to_attrs
86
+ @attrs
87
+ end
88
+
89
+ # Longer hand methods for completeness.
90
+ # Internallly encourage the shorter attrs method.
91
+ def attributes=(attributes)
92
+ @attributes = attributes
93
+ end
94
+
95
+ def attributes
96
+ @attributes
97
+ end
98
+
99
+ # AWS Docs examples: http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GettingStarted.Ruby.04.html
100
+ # Usage:
101
+ #
102
+ # Post.scan(
103
+ # expression_attribute_names: {"#updated_at"=>"updated_at"},
104
+ # filter_expression: "#updated_at between :start_time and :end_time",
105
+ # expression_attribute_values: {
106
+ # ":start_time" => "2010-01-01T00:00:00",
107
+ # ":end_time" => "2020-01-01T00:00:00"
108
+ # }
109
+ # )
110
+ #
111
+ # TODO: pretty lame interface, improve it somehow. Maybe:
112
+ #
113
+ # Post.scan(filter: "updated_at between :start_time and :end_time")
114
+ #
115
+ # which automatically maps the structure.
116
+ def self.scan(params={})
117
+ puts("Should not use scan for production. It's slow and expensive. You should create either a LSI or GSI and use query the index instead.")
118
+
119
+ defaults = {
120
+ table_name: table_name
121
+ }
122
+ params = defaults.merge(params)
123
+ resp = db.scan(params)
124
+ resp.items.map {|i| Post.new(i) }
125
+ end
126
+
127
+ def self.replace(attrs)
128
+ # Automatically adds some attributes:
129
+ # partition key unique id
130
+ # created_at and updated_at timestamps. Timestamp format from AWS docs: http://amzn.to/2z98Bdc
131
+ defaults = {
132
+ partition_key => Digest::SHA1.hexdigest([Time.now, rand].join)
133
+ }
134
+ item = defaults.merge(attrs)
135
+ item["created_at"] ||= Time.now.utc.strftime('%Y-%m-%dT%TZ')
136
+ item["updated_at"] = Time.now.utc.strftime('%Y-%m-%dT%TZ')
137
+
138
+ # put_item full replaces the item
139
+ resp = db.put_item(
140
+ table_name: table_name,
141
+ item: item
142
+ )
143
+
144
+ # The resp does not contain the attrs. So might as well return
145
+ # the original item with the generated partition_key value
146
+ item
147
+ end
148
+
149
+ def self.find(id)
150
+ resp = db.get_item(
151
+ table_name: table_name,
152
+ key: {partition_key => id}
153
+ )
154
+ attributes = resp.item # unwraps the item's attributes
155
+ Post.new(attributes) if attributes
156
+ end
157
+
158
+ # Two ways to use the delete method:
159
+ #
160
+ # 1. Specify the key as a String. In this case the key will is the partition_key
161
+ # set on the model.
162
+ # MyModel.delete("728e7b5df40b93c3ea6407da8ac3e520e00d7351")
163
+ #
164
+ # 2. Specify the key as a Hash, you can arbitrarily specific the key structure this way
165
+ # MyModel.delete("728e7b5df40b93c3ea6407da8ac3e520e00d7351")
166
+ #
167
+ # options is provided in case you want to specific condition_expression or
168
+ # expression_attribute_values.
169
+ def self.delete(key_object, options={})
170
+ if key_object.is_a?(String)
171
+ key = {
172
+ partition_key => key_object
173
+ }
174
+ else # it should be a Hash
175
+ key = key_object
176
+ end
177
+
178
+ params = {
179
+ table_name: table_name,
180
+ key: key
181
+ }
182
+ # In case you want to specify condition_expression or expression_attribute_values
183
+ params = params.merge(options)
184
+
185
+ resp = db.delete_item(params)
186
+ end
187
+
188
+ # When called with an argument we'll set the internal @partition_key value
189
+ # When called without an argument just retun it.
190
+ # class Comment < DynamodbModel::Item
191
+ # partition_key "post_id"
192
+ # end
193
+ def self.partition_key(*args)
194
+ case args.size
195
+ when 0
196
+ @partition_key || "id" # defaults to id
197
+ when 1
198
+ @partition_key = args[0].to_s
199
+ end
200
+ end
201
+
202
+ def self.table_name
203
+ @table_name = self.name.pluralize.underscore
204
+ [table_namespace, @table_name].reject {|s| s.nil? || s.empty?}.join('-')
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,17 @@
1
+ module DynamodbModel
2
+ class Migration
3
+ autoload :Dsl, "dynamodb_model/migration/dsl"
4
+
5
+ class << self
6
+ def up
7
+ puts "Running up migration for #{self.class.name}"
8
+ end
9
+
10
+ def create_table(table_name)
11
+ dsl = Dsl.new(table_name)
12
+ yield(dsl)
13
+ dsl.execute
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,112 @@
1
+ class DynamodbModel::Migration
2
+ class Dsl
3
+ include DynamodbModel::DbConfig
4
+
5
+ ATTRIBUTE_TYPE_MAP = {
6
+ 'string' => 'S',
7
+ 'number' => 'N',
8
+ 'binary' => 'B',
9
+ 's' => 'S',
10
+ 'n' => 'N',
11
+ 'b' => 'B',
12
+ }
13
+
14
+ attr_accessor :key_schema, :attribute_definitions
15
+ # db is the dynamodb client
16
+ def initialize(table_name)
17
+ @table_name = table_name
18
+ @key_schema = []
19
+ @attribute_definitions = []
20
+ @provisioned_throughput = {
21
+ read_capacity_units: 10,
22
+ write_capacity_units: 10
23
+ }
24
+ end
25
+
26
+ # http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Types/KeySchemaElement.html
27
+ # partition_key is required
28
+ def partition_key(identifier)
29
+ adjust_schema_and_attributes(identifier, "hash")
30
+ end
31
+
32
+ # sort_key is optional
33
+ def sort_key(identifier)
34
+ adjust_schema_and_attributes(identifier, "range")
35
+ end
36
+
37
+ # Parameters:
38
+ # identifier: "id:string" or "id"
39
+ # key_type: "hash" or "range"
40
+ #
41
+ # Adjusts the parameters for create_table to add the
42
+ # partition_key and sort_key
43
+ def adjust_schema_and_attributes(identifier, key_type)
44
+ name, attribute_type = identifier.split(':')
45
+ attribute_type = "string" if attribute_type.nil?
46
+
47
+ partition_key = {
48
+ attribute_name: name,
49
+ key_type: key_type.upcase
50
+ }
51
+ @key_schema << partition_key
52
+
53
+ attribute_definition = {
54
+ attribute_name: name,
55
+ attribute_type: ATTRIBUTE_TYPE_MAP[attribute_type]
56
+ }
57
+ @attribute_definitions << attribute_definition
58
+ end
59
+
60
+ # t.provisioned_throughput(5) # both
61
+ # t.provisioned_throughput(:read, 5)
62
+ # t.provisioned_throughput(:write, 5)
63
+ # t.provisioned_throughput(:both, 5)
64
+ def provisioned_throughput(*params)
65
+ case params.size
66
+ when 2
67
+ capacity_type, capacity_units = params
68
+ when 1
69
+ arg = params[0]
70
+ if arg.is_a?(Hash)
71
+ @provisioned_throughput = arg # set directly
72
+ return
73
+ else # assume parameter is an Integer
74
+ capacity_type = :both
75
+ capacity_units = arg
76
+ end
77
+ when 0 # reader method
78
+ return @provisioned_throughput
79
+ end
80
+
81
+ map = {
82
+ read: :read_capacity_units,
83
+ write: :write_capacity_units,
84
+ }
85
+
86
+ if capacity_type = :both
87
+ @provisioned_throughput[map[:read]] = capacity_units
88
+ @provisioned_throughput[map[:write]] = capacity_units
89
+ else
90
+ @provisioned_throughput[capacity_type] = capacity_units
91
+ end
92
+ end
93
+
94
+ def execute
95
+ params = {
96
+ table_name: @table_ame,
97
+ key_schema: @key_schema,
98
+ attribute_definitions: @attribute_definitions,
99
+ provisioned_throughput: @provisioned_throughput
100
+ }
101
+ begin
102
+ result = db.create_table(params)
103
+
104
+ puts 'DynamoDB Table: proj-posts Status: ' +
105
+ result.table_description.table_status;
106
+ rescue Aws::DynamoDB::Errors::ServiceError => error
107
+ puts 'Unable to create table:'
108
+ puts error.message
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,3 @@
1
+ module DynamodbModel
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamodb_model
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tung Nguyen
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-11-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: ActiveRecord-ish Dynamodb Model
70
+ email:
71
+ - tongueroo@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - Gemfile
79
+ - README.md
80
+ - Rakefile
81
+ - bin/console
82
+ - bin/setup
83
+ - dynamodb_model.gemspec
84
+ - lib/dynamodb_model.rb
85
+ - lib/dynamodb_model/db_config.rb
86
+ - lib/dynamodb_model/item.rb
87
+ - lib/dynamodb_model/migration.rb
88
+ - lib/dynamodb_model/migration/dsl.rb
89
+ - lib/dynamodb_model/version.rb
90
+ homepage: https://github.com/tongueroo/dynamodb_model
91
+ licenses: []
92
+ metadata: {}
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubyforge_project:
109
+ rubygems_version: 2.6.14
110
+ signing_key:
111
+ specification_version: 4
112
+ summary: ActiveRecord-ish Dynamodb Model
113
+ test_files: []