dynomite 1.2.7 → 2.0.1

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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +17 -2
  3. data/CHANGELOG.md +21 -0
  4. data/Gemfile +1 -5
  5. data/LICENSE.txt +22 -0
  6. data/README.md +6 -190
  7. data/Rakefile +13 -1
  8. data/dynomite.gemspec +9 -2
  9. data/exe/dynomite +14 -0
  10. data/lib/dynomite/associations/association.rb +126 -0
  11. data/lib/dynomite/associations/belongs_to.rb +35 -0
  12. data/lib/dynomite/associations/has_and_belongs_to_many.rb +19 -0
  13. data/lib/dynomite/associations/has_many.rb +19 -0
  14. data/lib/dynomite/associations/has_one.rb +19 -0
  15. data/lib/dynomite/associations/many_association.rb +257 -0
  16. data/lib/dynomite/associations/single_association.rb +157 -0
  17. data/lib/dynomite/associations.rb +248 -0
  18. data/lib/dynomite/autoloader.rb +25 -0
  19. data/lib/dynomite/cli.rb +48 -0
  20. data/lib/dynomite/client.rb +118 -0
  21. data/lib/dynomite/command.rb +89 -0
  22. data/lib/dynomite/completer/script.rb +6 -0
  23. data/lib/dynomite/completer/script.sh +10 -0
  24. data/lib/dynomite/completer.rb +159 -0
  25. data/lib/dynomite/config.rb +39 -0
  26. data/lib/dynomite/core.rb +18 -19
  27. data/lib/dynomite/engine.rb +15 -0
  28. data/lib/dynomite/erb.rb +5 -3
  29. data/lib/dynomite/error.rb +12 -0
  30. data/lib/dynomite/help/completion.md +20 -0
  31. data/lib/dynomite/help/completion_script.md +3 -0
  32. data/lib/dynomite/help/migrate.md +3 -0
  33. data/lib/dynomite/help.rb +9 -0
  34. data/lib/dynomite/install.rb +4 -0
  35. data/lib/dynomite/item/abstract.rb +15 -0
  36. data/lib/dynomite/item/components.rb +33 -0
  37. data/lib/dynomite/item/dsl.rb +101 -0
  38. data/lib/dynomite/item/id.rb +41 -0
  39. data/lib/dynomite/item/indexes/finder.rb +58 -0
  40. data/lib/dynomite/item/indexes/index.rb +21 -0
  41. data/lib/dynomite/item/indexes/primary_index.rb +18 -0
  42. data/lib/dynomite/item/indexes.rb +25 -0
  43. data/lib/dynomite/item/locking.rb +53 -0
  44. data/lib/dynomite/item/magic_fields.rb +66 -0
  45. data/lib/dynomite/item/primary_key.rb +85 -0
  46. data/lib/dynomite/item/query/delegates.rb +28 -0
  47. data/lib/dynomite/item/query/params/base.rb +42 -0
  48. data/lib/dynomite/item/query/params/expression_attribute.rb +79 -0
  49. data/lib/dynomite/item/query/params/filter.rb +41 -0
  50. data/lib/dynomite/item/query/params/function/attribute_exists.rb +21 -0
  51. data/lib/dynomite/item/query/params/function/attribute_type.rb +30 -0
  52. data/lib/dynomite/item/query/params/function/base.rb +33 -0
  53. data/lib/dynomite/item/query/params/function/begins_with.rb +32 -0
  54. data/lib/dynomite/item/query/params/function/contains.rb +7 -0
  55. data/lib/dynomite/item/query/params/function/size_fn.rb +37 -0
  56. data/lib/dynomite/item/query/params/helpers.rb +94 -0
  57. data/lib/dynomite/item/query/params/key_condition.rb +34 -0
  58. data/lib/dynomite/item/query/params.rb +115 -0
  59. data/lib/dynomite/item/query/partiql/executer.rb +72 -0
  60. data/lib/dynomite/item/query/partiql.rb +67 -0
  61. data/lib/dynomite/item/query/relation/chain.rb +125 -0
  62. data/lib/dynomite/item/query/relation/comparision_expression.rb +21 -0
  63. data/lib/dynomite/item/query/relation/comparision_map.rb +19 -0
  64. data/lib/dynomite/item/query/relation/delete.rb +38 -0
  65. data/lib/dynomite/item/query/relation/ids.rb +21 -0
  66. data/lib/dynomite/item/query/relation/math.rb +19 -0
  67. data/lib/dynomite/item/query/relation/where_field.rb +32 -0
  68. data/lib/dynomite/item/query/relation/where_group.rb +78 -0
  69. data/lib/dynomite/item/query/relation.rb +127 -0
  70. data/lib/dynomite/item/query.rb +7 -0
  71. data/lib/dynomite/item/read/find.rb +196 -0
  72. data/lib/dynomite/item/read/find_with_event.rb +42 -0
  73. data/lib/dynomite/item/read.rb +90 -0
  74. data/lib/dynomite/item/sti.rb +43 -0
  75. data/lib/dynomite/item/table_namespace.rb +43 -0
  76. data/lib/dynomite/item/typecaster.rb +106 -0
  77. data/lib/dynomite/item/waiter_methods.rb +18 -0
  78. data/lib/dynomite/item/write/base.rb +15 -0
  79. data/lib/dynomite/item/write/delete_item.rb +14 -0
  80. data/lib/dynomite/item/write/put_item.rb +99 -0
  81. data/lib/dynomite/item/write/update_item.rb +73 -0
  82. data/lib/dynomite/item/write.rb +204 -0
  83. data/lib/dynomite/item.rb +113 -286
  84. data/lib/dynomite/migration/dsl/accessor.rb +19 -0
  85. data/lib/dynomite/migration/dsl/index/base.rb +42 -0
  86. data/lib/dynomite/migration/dsl/index/gsi.rb +59 -0
  87. data/lib/dynomite/migration/dsl/index/lsi.rb +27 -0
  88. data/lib/dynomite/migration/dsl/index.rb +72 -0
  89. data/lib/dynomite/migration/dsl/primary_key.rb +62 -0
  90. data/lib/dynomite/migration/dsl/provisioned_throughput.rb +38 -0
  91. data/lib/dynomite/migration/dsl.rb +89 -142
  92. data/lib/dynomite/migration/file_info.rb +28 -0
  93. data/lib/dynomite/migration/generator.rb +30 -16
  94. data/lib/dynomite/migration/helpers.rb +7 -0
  95. data/lib/dynomite/migration/internal/migrate/create_schema_migrations.rb +17 -0
  96. data/lib/dynomite/migration/internal/models/schema_migration.rb +6 -0
  97. data/lib/dynomite/migration/runner.rb +179 -0
  98. data/lib/dynomite/migration/templates/create_table.rb +7 -23
  99. data/lib/dynomite/migration/templates/delete_table.rb +7 -0
  100. data/lib/dynomite/migration/templates/update_table.rb +3 -18
  101. data/lib/dynomite/migration.rb +53 -10
  102. data/lib/dynomite/reserved_words.rb +13 -3
  103. data/lib/dynomite/seed.rb +13 -0
  104. data/lib/dynomite/types.rb +22 -0
  105. data/lib/dynomite/version.rb +1 -1
  106. data/lib/dynomite/waiter.rb +40 -0
  107. data/lib/dynomite.rb +11 -17
  108. data/lib/generators/application_item/application_item_generator.rb +30 -0
  109. data/lib/generators/application_item/templates/application_item.rb.tt +4 -0
  110. data/lib/jets/commands/dynamodb_command.rb +29 -0
  111. data/lib/jets/commands/help/generate.md +33 -0
  112. data/lib/jets/commands/help/migrate.md +3 -0
  113. metadata +201 -17
  114. data/docs/migrations/long-example.rb +0 -127
  115. data/docs/migrations/short-example.rb +0 -40
  116. data/lib/dynomite/db_config.rb +0 -121
  117. data/lib/dynomite/errors.rb +0 -15
  118. data/lib/dynomite/log.rb +0 -15
  119. data/lib/dynomite/migration/common.rb +0 -86
  120. data/lib/dynomite/migration/dsl/base_secondary_index.rb +0 -73
  121. data/lib/dynomite/migration/dsl/global_secondary_index.rb +0 -4
  122. data/lib/dynomite/migration/dsl/local_secondary_index.rb +0 -8
  123. data/lib/dynomite/migration/executor.rb +0 -38
@@ -0,0 +1,62 @@
1
+ # Common methods to the *SecondaryIndex classes that handle gsi and lsi methods
2
+ # as well a the Dsl class that handles create_table and update_table methods.
3
+ class Dynomite::Migration::Dsl
4
+ module PrimaryKey
5
+ include Dynomite::Types
6
+
7
+ # http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Types/KeySchemaElement.html
8
+ # partition_key is required
9
+ attr_reader :partition_key_identifier
10
+ def partition_key(identifier)
11
+ identifier = identifier.to_s
12
+ @partition_key_identifier = identifier # for later use. useful for conventional_index_name
13
+ adjust_schema_and_attributes(identifier, "HASH")
14
+ end
15
+
16
+ # sort_key is optional
17
+ attr_reader :sort_key_identifier
18
+ def sort_key(identifier)
19
+ identifier = identifier.to_s
20
+ @sort_key_identifier = identifier # for later use. useful for conventional_index_name
21
+ adjust_schema_and_attributes(identifier, "RANGE")
22
+ end
23
+
24
+ # Use class variable to share across all instances of GlobalSecondaryIndex
25
+ # Note: Even though dynomite merges attribute defintions correctly for
26
+ # multiple indexes, DynamoDB does not allow creating multiple indexes
27
+ # at the same time. Example error:
28
+ # Unable to update table: Subscriber limit exceeded: Only 1 online index can be created or deleted simultaneously per table
29
+ # Leaving the logic in place in case AWS changes this in the future.
30
+
31
+ # Parameters:
32
+ # identifier: "id:string" or "id"
33
+ # key_type: "HASH" OR "RANGE"
34
+ #
35
+ # Adjusts the parameters for create_table to add the
36
+ # partition_key and sort_key
37
+ def adjust_schema_and_attributes(identifier, key_type)
38
+ name, attribute_type = identifier.split(':')
39
+ attribute_type ||= "string" # default to string
40
+
41
+ partition_key = {
42
+ attribute_name: name,
43
+ key_type: key_type.upcase
44
+ }
45
+
46
+ if partition_key[:key_type] == "RANGE"
47
+ @key_schema << partition_key unless @key_schema.include?(partition_key)
48
+ else # HASH - add to beginning
49
+ @key_schema.unshift(partition_key) unless @key_schema.include?(partition_key)
50
+ end
51
+
52
+ attribute_definition = {
53
+ attribute_name: name,
54
+ attribute_type: type_map(attribute_type)
55
+ }
56
+ unless @attribute_definitions.include?(attribute_definition)
57
+ @attribute_definitions << attribute_definition
58
+ end
59
+ @attribute_definitions
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,38 @@
1
+ class Dynomite::Migration::Dsl
2
+ module ProvisionedThroughput
3
+ def billing_mode(v)
4
+ @billing_mode = v
5
+ @provisioned_throughput = nil if @billing_mode.to_s.upcase == "PAY_PER_REQUEST"
6
+ end
7
+
8
+ # t.provisioned_throughput(1) # both
9
+ # t.provisioned_throughput(1,1) # read, write
10
+ # t.provisioned_throughput({
11
+ # read_capacity_units: 1,
12
+ # write_capacity_units: 1
13
+ # }
14
+ def provisioned_throughput(*args)
15
+ if args.size == 0 # reader method
16
+ @provisioned_throughput # early return
17
+ elsif args.first.is_a?(Hash)
18
+ # @provisioned_throughput_set_called useful for update_table
19
+ # only provide a provisioned_throughput settings if explicitly called for update_table
20
+ @provisioned_throughput_set_called = true
21
+ # Case:
22
+ # provisioned_throughput(
23
+ # read_capacity_units: 1,
24
+ # write_capacity_units: 1
25
+ # )
26
+ @provisioned_throughput = arg.first # set directly
27
+ else # assume parameter is an Integer or [Integer, Integer]
28
+ # Case: provisioned_throughput(1)
29
+ # Case: provisioned_throughput(1, 1)
30
+ read_capacity_units, write_capacity_units = args
31
+ @provisioned_throughput = {
32
+ read_capacity_units: read_capacity_units,
33
+ write_capacity_units: write_capacity_units || read_capacity_units,
34
+ }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,35 +1,27 @@
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
-
10
- autoload :Common, "dynomite/migration/common"
11
- autoload :BaseSecondaryIndex, "dynomite/migration/dsl/base_secondary_index"
12
- autoload :LocalSecondaryIndex, "dynomite/migration/dsl/local_secondary_index"
13
- autoload :GlobalSecondaryIndex, "dynomite/migration/dsl/global_secondary_index"
14
-
15
- include Dynomite::DbConfig
16
- include Common
17
-
18
- attr_accessor(*ATTRIBUTES)
19
-
3
+ extend Accessor
4
+ include Dynomite::Client
5
+ include ProvisionedThroughput
6
+ include PrimaryKey
7
+ include Index
8
+ include Helpers
9
+
10
+ attr_accessor :attribute_definitions,
11
+ :key_schema,
12
+ :table_name
20
13
  def initialize(method_name, table_name, &block)
21
14
  @method_name = method_name
22
15
  @table_name = table_name
23
16
  @block = block
24
17
 
25
- # Dsl fills in attributes in as methods are called within the block.
18
+ # Dsl fills in atttributes in as methods are called within the block
19
+ # when parition_key and sort_key are called.
26
20
  # Attributes for both create_table and updated_table:
27
- @billing_mode = 'PROVISIONED'
28
21
  @attribute_definitions = []
29
- @provisioned_throughput = {
30
- read_capacity_units: 5,
31
- write_capacity_units: 5
32
- }
22
+ # dont set billing_mode for update_table. otherwise creating an index can set billing_mode to PAY_PER_REQUEST without user knowing
23
+ @billing_mode = "PAY_PER_REQUEST" if @method_name == :create_table
24
+ @provisioned_throughput = nil
33
25
 
34
26
  # Attributes for create_table only:
35
27
  @key_schema = []
@@ -39,35 +31,10 @@ class Dynomite::Migration
39
31
  @lsi_indexes = []
40
32
  end
41
33
 
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
34
+ def namespaced_table_name
35
+ table_name_with_namespace(table_name)
48
36
  end
49
37
 
50
- # t.gsi(:create) do |i|
51
- # i.partition_key "category:string"
52
- # i.sort_key "created_at:string" # optional
53
- # end
54
- def gsi(action=:create, index_name=nil, &block)
55
- gsi_index = GlobalSecondaryIndex.new(action, index_name, &block)
56
- @gsi_indexes << gsi_index # store @gsi_index for the parent Dsl to use
57
- end
58
- alias_method :global_secondary_index, :gsi
59
-
60
- # t.lsi(:create) do |i|
61
- # i.partition_key "category:string"
62
- # i.sort_key "created_at:string" # optional
63
- # end
64
- def lsi(action=:create, index_name=nil, &block)
65
- # dont need action create but have it to keep the lsi and gsi method consistent
66
- lsi_index = LocalSecondaryIndex.new(index_name, &block)
67
- @lsi_indexes << lsi_index # store @lsi_index for the parent Dsl to use
68
- end
69
- alias_method :local_secondary_index, :lsi
70
-
71
38
  def evaluate
72
39
  return if @evaluated
73
40
  @block.call(self) if @block
@@ -79,136 +46,116 @@ class Dynomite::Migration
79
46
  # executor
80
47
  def params
81
48
  evaluate # lazy evaluation: wait until as long as possible before evaluating code block
82
-
83
- # Not using send because think its clearer in this case
84
- case @method_name
85
- when :create_table
86
- params_create_table
87
- when :update_table
88
- params_update_table
89
- end
49
+ send "params_#{@method_name}" # IE: params_create_table, params_update_table, params_delete_table
90
50
  end
91
51
 
52
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#create_table-instance_method
92
53
  def params_create_table
93
- merge_lsi_attribute_definitions!
94
- merge_gsi_attribute_definitions!
95
-
96
54
  params = {
55
+ billing_mode: @billing_mode.to_s.upcase,
97
56
  table_name: namespaced_table_name,
98
57
  key_schema: @key_schema,
99
- attribute_definitions: @attribute_definitions,
100
- billing_mode: @billing_mode
58
+ provisioned_throughput: @provisioned_throughput,
59
+ tags: @tags, # only for create_table
101
60
  }
61
+ params.reject! { |k,v| v.blank? }
102
62
 
103
- params[:provisioned_throughput] = @provisioned_throughput if @billing_mode == 'PROVISIONED'
104
63
  params[:local_secondary_indexes] = lsi_secondary_index_creates unless @lsi_indexes.empty?
105
64
  params[:global_secondary_indexes] = gsi_secondary_index_creates unless @gsi_indexes.empty?
65
+ params[:attribute_definitions] = attribute_definitions
66
+ params.merge!(common_params)
106
67
  params
107
68
  end
108
69
 
70
+ # https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/DynamoDB/Client.html#update_table-instance_method
71
+ dsl_accessor :replica_updates
109
72
  def params_update_table
110
- merge_gsi_attribute_definitions!
111
-
112
73
  params = {
74
+ billing_mode: @billing_mode.to_s.upcase,
113
75
  table_name: namespaced_table_name,
114
- attribute_definitions: @attribute_definitions,
115
- billing_mode: @billing_mode
116
76
  # update table take values only some values for the "parent" table
117
77
  # no key_schema, update_table does not handle key_schema for the "parent" table
78
+ replica_updates: @replica_updates, # only for update_table
118
79
  }
119
- # only set "parent" table provisioned_throughput if user actually invoked
120
- # it in the dsl
121
- if @provisioned_throughput_set_called && @billing_mode == 'PROVISIONED'
122
- params[:provisioned_throughput] = @provisioned_throughput
123
- end
124
- params[:global_secondary_index_updates] = global_secondary_index_updates
80
+ params.reject! { |k,v| v.blank? }
81
+
82
+ # only set "parent" table provisioned_throughput if user actually invoked it in the dsl
83
+ params[:provisioned_throughput] = @provisioned_throughput if @provisioned_throughput_set_called
84
+ params[:global_secondary_index_updates] = global_secondary_index_updates unless @gsi_indexes.empty?
85
+ params[:attribute_definitions] = attribute_definitions
125
86
  params
126
87
  end
127
88
 
128
- # Goes thorugh all the lsi_indexes that have been built up in memory.
129
- # Find the lsi object that creates an index and then grab the
130
- # attribute_definitions from it.
131
- def merge_lsi_attribute_definitions!
132
- lsi = @lsi_indexes.first # DynamoDB only supports adding one index at a time anyway. The reason @lsi_indexes is an Array is because we're sharing the same class code for LSI and GSI
133
- if lsi
134
- lsi.evaluate # force early evaluate since we need the params to
135
- # add: gsi_attribute_definitions + lsi_attrs
136
- lsi_attrs = lsi.attribute_definitions
137
- end
138
- all_attrs = if lsi_attrs
139
- @attribute_definitions + lsi_attrs
140
- else
141
- @attribute_definitions
142
- end
143
- @attribute_definitions = all_attrs.uniq
89
+ def params_delete_table
90
+ { table_name: namespaced_table_name }
144
91
  end
145
92
 
146
- # maps each lsi to the hash structure expected by dynamodb update_table
147
- # under the global_secondary_index_updates key:
148
- #
149
- # { create: {...} }
150
- # { update: {...} }
151
- # { delete: {...} }
152
- def lsi_secondary_index_creates
153
- @lsi_indexes.map do |lsi|
154
- lsi.params
93
+ def tags(values=nil)
94
+ if values.nil?
95
+ @tags
96
+ else
97
+ if values.is_a?(Hash)
98
+ @tags = values.map do |key,value|
99
+ {key: key.to_s, value: value.to_s}
100
+ end
101
+ else
102
+ @tags = values
103
+ end
155
104
  end
156
105
  end
157
106
 
158
- # maps each lsi to the hash structure expected by dynamodb update_table
159
- # under the global_secondary_index_updates key:
160
- #
161
- # { create: {...} }
162
- # { update: {...} }
163
- # { delete: {...} }
164
- def gsi_secondary_index_creates
165
- @gsi_indexes.map do |gsi|
166
- gsi.params
107
+ # @attribute_definitions only hold the partition_key and sort_key attributes
108
+ # We'll also added the attributes from: 1. lsi index 2. gsi indexes 3. existing attributes for update table
109
+ def attribute_definitions
110
+ # Goes through all the lsi_indexes that have been built up in memory.
111
+ # Find the lsi object that creates an index and then grab the attribute_definitions from it.
112
+ # All lsi indexes are create. There is no update or delete for lsi indexes.
113
+ @lsi_indexes.each do |lsi|
114
+ lsi.attribute_definitions.each do |definition|
115
+ @attribute_definitions << definition unless @attribute_definitions.include?(definition)
116
+ end
167
117
  end
168
- end
169
118
 
170
- # Goes thorugh all the gsi_indexes that have been built up in memory.
171
- # Find the gsi object that creates an index and then grab the
172
- # attribute_definitions from it.
173
- def merge_gsi_attribute_definitions!
174
- gsi_attrs = []
175
-
176
- gsi = @gsi_indexes.find { |i| i.action == :create }
177
- if gsi
178
- gsi.evaluate # force early evaluate since we need the params to
179
- # add: gsi_attribute_definitions + gsi_attrs
180
- gsi_attrs = gsi.attribute_definitions
119
+ # Goes through all the gsi_indexes that have been built up in memory.
120
+ # Find the gsi object that creates an index and then grab the attribute_definitions from it.
121
+ @gsi_indexes.select { |i| i.action == :create }.each do |gsi|
122
+ gsi.attribute_definitions.each do |definition|
123
+ @attribute_definitions << definition unless @attribute_definitions.include?(definition)
124
+ end
181
125
  end
182
126
 
183
127
  # Merge existing attributes for update table
184
- all_gsi_attrs = @method_name == :update_table ?
185
- gsi_attribute_definitions + gsi_attrs :
186
- gsi_attrs
128
+ if @method_name == :update_table
129
+ existing_attributes = desc_table(namespaced_table_name).attribute_definitions
130
+ existing_attributes.each do |attribute_definition|
131
+ definition = attribute_definition.to_h
132
+ @attribute_definitions << definition unless @attribute_definitions.include?(definition)
133
+ end
134
+ end
187
135
 
188
- all_attrs = (@attribute_definitions + all_gsi_attrs).uniq
189
- @attribute_definitions = all_attrs
136
+ @attribute_definitions
190
137
  end
191
138
 
192
- # maps each gsi to the hash structure expected by dynamodb update_table
193
- # under the global_secondary_index_updates key:
194
- #
195
- # { create: {...} }
196
- # { update: {...} }
197
- # { delete: {...} }
198
- def global_secondary_index_updates
199
- @gsi_indexes.map do |gsi|
200
- { gsi.action => gsi.params }
201
- end
139
+ dsl_accessor :stream_specification, :sse_specification
140
+ # common to create_table and update_table
141
+ def common_params
142
+ params = {
143
+ stream_specification: @stream_specification,
144
+ sse_specification: @sse_specification,
145
+ deletion_protection_enabled: deletion_protection_enabled_value,
146
+ }
147
+ params.reject! { |k,v| v.blank? }
202
148
  end
203
149
 
204
- # >> resp = Post.db.describe_table(table_name: "demo-dev-posts")
205
- # >> resp.table.attribute_definitions.map(&:to_h)
206
- # => [{:attribute_name=>"id", :attribute_type=>"S"}]
207
- def gsi_attribute_definitions
208
- return @gsi_attribute_definitions if @gsi_attribute_definitions
150
+ def deletion_protection_enabled(value=true)
151
+ @deletion_protection_enabled = value
152
+ end
153
+ alias deletion_protection deletion_protection_enabled
209
154
 
210
- resp = db.describe_table(table_name: namespaced_table_name)
211
- @gsi_attribute_definitions = resp.table.attribute_definitions.map(&:to_h)
155
+ # To avoid name conflict with DSL t.deletion_protection_enabled
156
+ def deletion_protection_enabled_value
157
+ default = Dynomite.config.migration.deletion_protection_enabled
158
+ @deletion_protection_enabled.nil? ? default : @deletion_protection_enabled
212
159
  end
213
160
  end
214
161
  end
@@ -0,0 +1,28 @@
1
+ class Dynomite::Migration
2
+ class FileInfo
3
+ attr_reader :path
4
+ def initialize(path)
5
+ @path = path
6
+ end
7
+
8
+ def pretty_path
9
+ @path.sub(/^\.\//,'') # remove leading ./
10
+ end
11
+
12
+ def migration_class
13
+ filename = File.basename(@path, '.rb')
14
+ filename = filename.sub(/\d+[-_]/, '') # strip leading timestsamp
15
+ filename.camelize.constantize
16
+ end
17
+
18
+ def version
19
+ filename = File.basename(@path, '.rb')
20
+ md = filename.match(/(\d+)[-_]/)
21
+ md[1].to_i
22
+ end
23
+
24
+ def self.all_files
25
+ Dir.glob("#{Dynomite.root}/dynamodb/migrate/*").to_a.sort
26
+ end
27
+ end
28
+ end
@@ -3,8 +3,6 @@ require "active_support/core_ext/string"
3
3
  class Dynomite::Migration
4
4
  # jets dynamodb:generate posts --partition-key id:string
5
5
  class Generator
6
- include Dynomite::DbConfig
7
-
8
6
  attr_reader :migration_name, :table_name
9
7
  def initialize(migration_name, options)
10
8
  @migration_name = migration_name
@@ -12,21 +10,28 @@ class Dynomite::Migration
12
10
  end
13
11
 
14
12
  def generate
15
- puts "Generating migration for #{@table_name}" unless @options[:quiet]
16
- return if @options[:noop]
13
+ puts "Generating migration" unless @options[:quiet]
14
+ return if ENV['NOOP']
17
15
  create_migration
18
16
  end
19
17
 
20
18
  def create_migration
21
19
  FileUtils.mkdir_p(File.dirname(migration_path))
22
20
  IO.write(migration_path, migration_code)
23
- puts "Migration file created: #{migration_path}. \nTo run:"
24
- puts " jets dynamodb:migrate #{migration_path}"
21
+ pretty_migration_path = migration_path.sub(/^\.\//,'') # remove leading ./
22
+ puts "Migration file created: #{pretty_migration_path}\nTo run:\n\n"
23
+ command = File.basename($0)
24
+ if command == "jets"
25
+ puts " #{command} dynamodb:migrate"
26
+ else
27
+ puts " #{command} migrate"
28
+ end
29
+ puts
25
30
  end
26
31
 
27
32
  def migration_code
28
- path = File.expand_path("../templates/#{table_action}.rb", __FILE__)
29
- result = Dynomite::Erb.result(path,
33
+ path = File.expand_path("../templates/#{action}.rb", __FILE__)
34
+ Dynomite::Erb.result(path,
30
35
  migration_class_name: migration_class_name,
31
36
  table_name: table_name,
32
37
  partition_key: @options[:partition_key],
@@ -35,30 +40,39 @@ class Dynomite::Migration
35
40
  )
36
41
  end
37
42
 
38
- def table_action
39
- @options[:table_action] || conventional_table_action
43
+ def action
44
+ # optoins[:table_action] is old and deprecated
45
+ action = @options[:action] || @options[:table_action] || conventional_action
46
+ case action
47
+ when /create/
48
+ "create_table"
49
+ when /delete/
50
+ "delete_table"
51
+ else
52
+ "update_table" # default and fallback
53
+ end
40
54
  end
41
55
 
42
- def conventional_table_action
43
- @migration_name.include?("update") ? "update_table" : "create_table"
56
+ def conventional_action
57
+ @migration_name.split("_").first
44
58
  end
45
59
 
46
60
  def table_name
47
- @options[:table_name] || conventional_table_name
61
+ @options[:table_name] || conventional_table_name || "TABLE_NAME"
48
62
  end
49
63
 
50
64
  # create_posts => posts
51
65
  # update_posts => posts
52
66
  def conventional_table_name
53
- @migration_name.sub(/^(create|update)_/, '')
67
+ @migration_name.sub(/^(create|update|delete)_/, '')
54
68
  end
55
69
 
56
70
  def migration_class_name
57
- "#{@migration_name}_migration".classify # doesnt include timestamp
71
+ "#{@migration_name}".camelize # doesnt include timestamp
58
72
  end
59
73
 
60
74
  def migration_path
61
- "#{Dynomite.app_root}dynamodb/migrate/#{timestamp}-#{@migration_name}_migration.rb"
75
+ "#{Dynomite.root}/dynamodb/migrate/#{timestamp}-#{@migration_name}.rb"
62
76
  end
63
77
 
64
78
  def timestamp
@@ -0,0 +1,7 @@
1
+ class Dynomite::Migration
2
+ module Helpers
3
+ def table_name_with_namespace(table_name)
4
+ [Dynomite.config.namespace, table_name].reject {|s| s.nil? || s.empty?}.join(Dynomite.config.namespace_separator)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ class CreateSchemaMigrations < Dynomite::Migration
2
+ include Dynomite::Client
3
+
4
+ def up
5
+ create_table :schema_migrations do |t|
6
+ t.partition_key "version:number" # required
7
+ t.billing_mode "PAY_PER_REQUEST"
8
+ end
9
+ end
10
+
11
+ def table_exist?(full_table_name)
12
+ client.describe_table(table_name: full_table_name)
13
+ true
14
+ rescue Aws::DynamoDB::Errors::ResourceNotFoundException
15
+ false
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ class SchemaMigration < Dynomite::Item
2
+ disable_id!
3
+ field :version, type: :integer
4
+ field :time_took, type: :integer
5
+ fields :status, :path, :error_message
6
+ end