dynomite 1.2.4 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 55dd0f93e12c5b18e5ae2d1997be0089e60024d4656e15720732b8d8da743d97
4
- data.tar.gz: d8bf1232ace35602f896691af0e1036a31744adff2dec35b4eea7930c3c65ec6
3
+ metadata.gz: 8a0639976cc739134d6670945d099f3584b39f88f058e056678591a42c09be84
4
+ data.tar.gz: 6ad8c28d9147a175cf940ec756d808b66a948c27be74239ab95dc3b589dd9d74
5
5
  SHA512:
6
- metadata.gz: b16044c3d220c9e9a55634faf04a8d9c79a593377be323fca6a20ca9e7bbe8fa6e5e7c60c4f6bdebede938c420ed319272e62f75926c5937cb878714ba5ab2ea
7
- data.tar.gz: c43283a308192ab948e78b278db6f465d162d291fb06ce56e6fccfbbdfa1225cf3cc98ca9c463a01a85aa83ab4b4f4ca5fffc786fce70410e3da68e32a803e88
6
+ metadata.gz: 3f7f4e2dcb3163c4cc60065c3d0f74b835bd4763f6295ae00d8f92576f2c57afcc969a5b4bf0c727d9b8a45ab17e52ae573319a819cf3f7c0974cf29a91b19ac
7
+ data.tar.gz: c37b011a90f943e6e04fbdfa248dee7b8bcf1dae762ae5175c976e02325e462118715d8340f5d42022e62a40ab1b77aa80af532544e255eed2c2fec89ecd6aa8
data/.gitignore CHANGED
@@ -7,3 +7,4 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ vendor/bundle
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 colorize method
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
- NOTE: Am looking for maintainers to help with this gem. Send me an email! Also [dynamoid](https://github.com/Dynamoid/dynamoid) seems like a good option that should be considered delegating to or straight using. Learning on delegation so we can have better default behavior for a DynamoDB model layer for Jets.
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" => "my 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
-
@@ -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
- open = port_open?("127.0.0.1", 8000, 0.2)
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 install dynamodb-local && dynamodb-local"
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.colorize(:red)} of: #{path.sub(/^\.\//, '').colorize(:green)}"
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".colorize(:red), line_number, line_content)
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({category: "Drama"}, index_name: "category-index")
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
- # TODO: Implement nicer where syntax with index_name as a chained method.
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 :key_schema, :attribute_definitions
12
- attr_accessor :table_name
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 atttributes in as methods are called within the block.
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
- provisioned_throughput: @provisioned_throughput
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
- params[:provisioned_throughput] = @provisioned_throughput if @provisioned_throughput_set_called
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}".colorize(:red)
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
@@ -1,3 +1,3 @@
1
1
  module Dynomite
2
- VERSION = "1.2.4"
2
+ VERSION = "1.2.7"
3
3
  end
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
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: 2019-11-13 00:00:00.000000000 Z
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.0.6
153
+ rubygems_version: 3.3.12
153
154
  signing_key:
154
155
  specification_version: 4
155
156
  summary: ActiveRecord-ish Dynamodb Model