dynomite 1.2.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (124) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +17 -2
  3. data/CHANGELOG.md +24 -0
  4. data/Gemfile +1 -5
  5. data/LICENSE.txt +22 -0
  6. data/README.md +6 -188
  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 +45 -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 -299
  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 +178 -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/query.rb +48 -0
  103. data/lib/dynomite/reserved_words.rb +13 -3
  104. data/lib/dynomite/seed.rb +12 -0
  105. data/lib/dynomite/types.rb +22 -0
  106. data/lib/dynomite/version.rb +1 -1
  107. data/lib/dynomite/waiter.rb +40 -0
  108. data/lib/dynomite.rb +11 -17
  109. data/lib/generators/application_item/application_item_generator.rb +30 -0
  110. data/lib/generators/application_item/templates/application_item.rb.tt +4 -0
  111. data/lib/jets/commands/dynamodb_command.rb +29 -0
  112. data/lib/jets/commands/help/generate.md +33 -0
  113. data/lib/jets/commands/help/migrate.md +3 -0
  114. metadata +202 -17
  115. data/docs/migrations/long-example.rb +0 -127
  116. data/docs/migrations/short-example.rb +0 -40
  117. data/lib/dynomite/db_config.rb +0 -107
  118. data/lib/dynomite/errors.rb +0 -15
  119. data/lib/dynomite/log.rb +0 -15
  120. data/lib/dynomite/migration/common.rb +0 -86
  121. data/lib/dynomite/migration/dsl/base_secondary_index.rb +0 -73
  122. data/lib/dynomite/migration/dsl/global_secondary_index.rb +0 -4
  123. data/lib/dynomite/migration/dsl/local_secondary_index.rb +0 -8
  124. data/lib/dynomite/migration/executor.rb +0 -38
@@ -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
@@ -0,0 +1,178 @@
1
+ require "timeout"
2
+ require_relative "internal/migrate/create_schema_migrations"
3
+ require_relative "internal/models/schema_migration"
4
+
5
+ class Dynomite::Migration
6
+ class Runner
7
+ include Dynomite::Item::WaiterMethods
8
+
9
+ def initialize(options={})
10
+ @options = options
11
+ end
12
+
13
+ def run
14
+ puts "Running Dynomite migrations"
15
+ ensure_schema_migrations_exist!
16
+ clear_error_schema_migrations! if ENV['CLEAR_ERRORS']
17
+ check_for_migration_errors!
18
+ Dynomite::Migration::FileInfo.all_files.each do |path|
19
+ migrate(path)
20
+ end
21
+ end
22
+
23
+ def migrate(path)
24
+ # load migration class definition
25
+ # CreatePosts#up (up is called below)
26
+ file_info = FileInfo.new(path)
27
+
28
+ schema_migration = SchemaMigration.find_by(version: file_info.version)
29
+ if schema_migration
30
+ teriminal_statuses = %w[complete error]
31
+ if teriminal_statuses.include?(schema_migration.status)
32
+ return
33
+ else
34
+ action = with_timeout(message: "Timed out. You must respond within 60s.") do
35
+ uncompleted_migration_prompt(file_info, schema_migration)
36
+ end
37
+ end
38
+ end
39
+
40
+ case action
41
+ when :skip
42
+ return
43
+ when :delete
44
+ schema_migration.delete
45
+ when :completed
46
+ schema_migration.status = "completed"
47
+ schema_migration.save
48
+ return
49
+ when :exit
50
+ puts "Exiting"
51
+ exit
52
+ end
53
+
54
+ puts "Running migration: #{file_info.pretty_path}"
55
+ load path
56
+
57
+ # INSERT schema_migration table - in_progress
58
+ unless schema_migration
59
+ schema_migration = SchemaMigration.new(version: file_info.version, status: "in_progress", path: file_info.pretty_path)
60
+ schema_migration.save
61
+ end
62
+ start_time = Time.now
63
+
64
+ # Run actual migration
65
+ migration_class = file_info.migration_class
66
+ error_message = nil
67
+ begin
68
+ # Runs migration up command. Example:
69
+ # CreatePosts#up
70
+ # Migration#create_table
71
+ # Migration#execute (wait happens here)
72
+ migration_class.new.up # wait happens within create_table or update_table
73
+ rescue Aws::DynamoDB::Errors::ServiceError => error
74
+ puts "Unable to #{@method_name.to_s.gsub('_',' ')}: #{error.message}".color(:red)
75
+ error_message = error.message
76
+ end
77
+
78
+ # UPDATE schema_migrations table - complete status
79
+ if error_message
80
+ schema_migration.status = "error"
81
+ schema_migration.error_message = error_message
82
+ else
83
+ schema_migration.status = "complete"
84
+ end
85
+
86
+ schema_migration.time_took = (Time.now - start_time).to_i
87
+ schema_migration.save
88
+ # schema_migration.delete # HACK
89
+ puts "Time took: #{pretty_time_took(schema_migration.time_took)}"
90
+ exit 1 if error_message # otherwise continue to next migration file
91
+ end
92
+
93
+ def with_timeout(options={}, &block)
94
+ seconds = options[:seconds] || 60
95
+ message = options[:message] || "Timed out after #{seconds}s."
96
+ Timeout::timeout(seconds, &block)
97
+ rescue Timeout::Error => e
98
+ puts "#{e.class}: #{e.message}"
99
+ puts message
100
+ exit 1
101
+ end
102
+
103
+ def uncompleted_migration_prompt(file_info, schema_migration)
104
+ choice = nil
105
+ until %w[s d c e].include?(choice)
106
+ puts(<<~EOL)
107
+ The #{file_info.pretty_path} migration status is incomplete. Status: #{schema_migration.status}
108
+ This can happen and if it was interrupted by a CTRL-C.
109
+ Please check the migration to help determine what to do next.
110
+
111
+ Options:
112
+
113
+ s - skip and continue. leaves schema_migrations item as-is
114
+ d - delete and continue. deletes the schema_migrations item
115
+ c - mark as successful completed and continue. updates the schema_migrations item as completed.
116
+ e - exit
117
+
118
+ EOL
119
+ print "Choose an option (s/d/c/e): "
120
+ choice = $stdin.gets.strip
121
+ end
122
+
123
+ map = {
124
+ "s" => :skip,
125
+ "d" => :delete,
126
+ "c" => :completed,
127
+ "e" => :exit,
128
+ }
129
+ map[choice]
130
+ end
131
+
132
+ def ensure_schema_migrations_exist!
133
+ migration = CreateSchemaMigrations.new
134
+ return if migration.table_exist?(SchemaMigration.table_name)
135
+ puts "Creating #{SchemaMigration.table_name} table for the first time"
136
+ migration.up
137
+ end
138
+
139
+ def clear_error_schema_migrations!
140
+ SchemaMigration.warn_on_scan(false).where(status: "error").each do |schema_migration|
141
+ schema_migration.delete
142
+ puts "Deleted error schema_migration: #{schema_migration.path}"
143
+ end
144
+ end
145
+
146
+ def check_for_migration_errors!
147
+ errors = SchemaMigration.warn_on_scan(false).where(status: "error")
148
+ errors_info = SchemaMigration.warn_on_scan(false).where(status: "error").map do |schema_migration|
149
+ " #{schema_migration.path} - #{schema_migration.error_message}"
150
+ end.join("\n")
151
+ if errors.count > 0
152
+ puts <<~EOL
153
+ Found error migrations. Please review the migration erors fix before continuing.
154
+ You can clear them out manually by deleting them from the #{SchemaMigration.table_name} table.
155
+
156
+ #{errors_info}
157
+
158
+ You can also clear them with the following command:
159
+
160
+ CLEAR_ERRORS=1 jets dynamodb:migrate
161
+
162
+ EOL
163
+ exit 1
164
+ end
165
+ end
166
+
167
+ # http://stackoverflow.com/questions/4175733/convert-duration-to-hoursminutesseconds-or-similar-in-rails-3-or-ruby
168
+ def pretty_time_took(total_seconds)
169
+ minutes = (total_seconds / 60) % 60
170
+ seconds = total_seconds % 60
171
+ if total_seconds < 60
172
+ "#{seconds.to_i}s"
173
+ else
174
+ "#{minutes.to_i}m #{seconds.to_i}s"
175
+ end
176
+ end
177
+ end
178
+ end
@@ -1,33 +1,17 @@
1
1
  class <%= @migration_class_name %> < Dynomite::Migration
2
2
  def up
3
3
  create_table :<%= @table_name %> do |t|
4
- t.partition_key "<%= @partition_key %>" # required
4
+ t.partition_key :<%= @partition_key %>
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)
9
- t.provisioned_throughput(<%= @provisioned_throughput %>) # sets both read and write, defaults to 5 when not set
10
-
11
- # Instead of using partition_key and sort_key you can set the
12
- # key schema directly also
13
- # t.key_schema([
14
- # {attribute_name: "id", :key_type=>"HASH"},
15
- # {attribute_name: "created_at", :key_type=>"RANGE"}
16
- # ])
17
- # t.attribute_definitions([
18
- # {attribute_name: "id", attribute_type: "N"},
19
- # {attribute_name: "created_at", attribute_type: "S"}
20
- # ])
21
-
22
- # other ways to set provisioned_throughput
23
- # t.provisioned_throughput(:read, 10)
24
- # t.provisioned_throughput(:write, 10)
25
- # t.provisioned_throughput(
26
- # read_capacity_units: 5,
27
- # write_capacity_units: 5
28
- # )
8
+ <% if %w[id id:string].include?(@partition_key) -%>
9
+ t.add_gsi :updated_at
10
+ <% else %>
11
+ t.add_gsi :id
12
+ <% end -%>
29
13
  end
30
14
  end
31
15
  end
32
16
 
33
- # More examples: https://github.com/tongueroo/dynomite/tree/master/docs
17
+ # More examples: https://rubyonjets.com/docs/database/dynamodb/migration/
@@ -0,0 +1,7 @@
1
+ class <%= @migration_class_name %> < Dynomite::Migration
2
+ def up
3
+ delete_table :<%= @table_name %>
4
+ end
5
+ end
6
+
7
+ # More examples: https://rubyonjets.com/docs/database/dynamodb/migration/
@@ -1,26 +1,11 @@
1
1
  class <%= @migration_class_name %> < Dynomite::Migration
2
2
  def up
3
3
  update_table :<%= @table_name %> do |t|
4
- t.gsi(:create) do |i|
5
- i.partition_key "<%= @partition_key %>" # required
6
- <% if @sort_key # so extra spaces are not added when generated -%>
7
- t.sort_key "<%= @sort_key %>" # optional
8
- <% end -%>
4
+ t.add_gsi(partition_key: "<%= @partition_key %>", sort_key: "<%= @sort_key || 'updated_at' %>")
9
5
 
10
- i.provisioned_throughput(5)
11
- end
12
-
13
- # Examples:
14
- # t.gsi(:update, "update-me-index") do |i|
15
- # i.provisioned_throughput(10)
16
- # end
17
-
18
- # t.gsi(:delete, "delete-me-index")
19
-
20
- # Must use :create, :update, :delete one at a time in separate migration files.
21
- # DynamoDB imposes this.
6
+ # t.remove_gsi(partition_key: "<%= @partition_key %>", sort_key: "<%= @sort_key || 'updated_at' %>")
22
7
  end
23
8
  end
24
9
  end
25
10
 
26
- # More examples: https://github.com/tongueroo/dynomite/tree/master/docs
11
+ # More examples: https://rubyonjets.com/docs/database/dynamodb/migration/
@@ -1,27 +1,70 @@
1
1
  module Dynomite
2
2
  class Migration
3
- autoload :Dsl, "dynomite/migration/dsl"
4
- autoload :Generator, "dynomite/migration/generator"
5
- autoload :Executor, "dynomite/migration/executor"
3
+ include Item::WaiterMethods
4
+ include Dynomite::Client
5
+ include Helpers
6
6
 
7
7
  def up
8
8
  puts "Should defined an up method for your migration: #{self.class.name}"
9
9
  end
10
10
 
11
11
  def create_table(table_name, &block)
12
- execute_with_dsl_params(table_name, :create_table, &block)
12
+ execute(table_name, :create_table, &block)
13
13
  end
14
14
 
15
15
  def update_table(table_name, &block)
16
- execute_with_dsl_params(table_name, :update_table, &block)
16
+ execute(table_name, :update_table, &block)
17
17
  end
18
18
 
19
+ def delete_table(table_name, &block)
20
+ execute(table_name, :delete_table, &block)
21
+ end
22
+
23
+ def add_gsi(table_name, *args)
24
+ update_table(table_name) do |t|
25
+ t.add_gsi(*args)
26
+ end
27
+ end
28
+
29
+ def remove_gsi(table_name, *args)
30
+ update_table(table_name) do |t|
31
+ t.remove_gsi(*args)
32
+ end
33
+ end
34
+
35
+ def update_gsi(table_name, *args)
36
+ update_table(table_name) do |t|
37
+ t.update_gsi(*args)
38
+ end
39
+ end
40
+
41
+ def update_time_to_live(table_name, time_to_live_specification={})
42
+ table_name = table_name_with_namespace(table_name)
43
+ client.update_time_to_live(
44
+ table_name: table_name,
45
+ time_to_live_specification: time_to_live_specification
46
+ )
47
+ # fast enough of an operation, no need to wait
48
+ end
49
+ alias update_ttl update_time_to_live
50
+
19
51
  private
20
- def execute_with_dsl_params(table_name, method_name, &block)
21
- dsl = Dsl.new(method_name, table_name, &block)
22
- params = dsl.params
23
- executor = Executor.new(table_name, method_name, params)
24
- executor.run
52
+ # execute with dsl params
53
+ # table name is short name and params[:table_name] is full namespaced name
54
+ def execute(table_name, method_name, &block)
55
+ params = Dsl.new(method_name, table_name, &block).params
56
+
57
+ puts "Running #{method_name} with:"
58
+ pp params
59
+ resp = client.send(method_name, params)
60
+ namespaced_table_name = params[:table_name] # full: demo-dev_posts short: posts
61
+ puts "DynamoDB Table: #{namespaced_table_name} Status: #{resp.table_description.table_status}"
62
+
63
+ if method_name.to_sym == :delete_table # already sym, to_sym just in case
64
+ waiter.wait_for_delete(params[:table_name])
65
+ else # create_table or update_table
66
+ waiter.wait(params[:table_name])
67
+ end
25
68
  end
26
69
  end
27
70
  end
@@ -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,18 +1,28 @@
1
1
  module Dynomite
2
2
  RESERVED_WORDS = %w[
3
3
  as_json
4
- attrs
5
4
  attributes
6
- delete
5
+ attrs
7
6
  columns
7
+ destroy
8
+ fields
8
9
  find
9
10
  getter
11
+ hash_key
12
+ hash_key_field
13
+ key_schema
10
14
  new_record
11
15
  param_name
12
16
  partition_key
17
+ partition_key_field
18
+ range_key
19
+ range_key_field
13
20
  replace
14
- setter
21
+ save
15
22
  scan
23
+ setter
24
+ sort_key_field
25
+ sort_key_field_field
16
26
  table_name
17
27
  ].freeze
18
28
  end
@@ -0,0 +1,12 @@
1
+ module Dynomite
2
+ class Seed
3
+ def initialize(options={})
4
+ @options = options
5
+ end
6
+
7
+ def run
8
+ file = "dynamodb/seeds.rb"
9
+ load(file) if File.exist?(file)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,22 @@
1
+ module Dynomite
2
+ module Types
3
+ TYPE_MAP = {
4
+ string: 'S',
5
+ number: 'N',
6
+ binary: 'B',
7
+ boolean: 'BOOL',
8
+ null: 'NULL',
9
+ map: 'M',
10
+ list: 'L',
11
+ string_set: 'SS',
12
+ number_set: 'NS',
13
+ binary_set: 'BS',
14
+ }
15
+
16
+ # https://rubyonjets.com/docs/database/dynamodb/types/
17
+ # https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypeDescriptors
18
+ def type_map(attribute_type)
19
+ TYPE_MAP[attribute_type.to_s.downcase.to_sym] || attribute_type
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,3 @@
1
1
  module Dynomite
2
- VERSION = "1.2.6"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -0,0 +1,40 @@
1
+ module Dynomite
2
+ class Waiter
3
+ include Client
4
+
5
+ def wait(table_name)
6
+ logger.info "Waiting for #{table_name} table to be ready."
7
+ statuses = [:initial]
8
+ until statuses.all? { |s| s == "ACTIVE" } do
9
+ resp = client.describe_table(table_name: table_name)
10
+
11
+ table = resp.table
12
+ # table_status: CREATING UPDATING DELETING ACTIVE INACCESSIBLE_ENCRYPTION_CREDENTIALS ARCHIVING ARCHIVED
13
+ table_status = table.table_status
14
+ statuses = [table_status]
15
+ # index_status: CREATING UPDATING DELETING ACTIVE
16
+ indexes = table.global_secondary_indexes || []
17
+ statuses += indexes.map { |i| i.index_status }
18
+ print '.'
19
+ sleep pause_time
20
+ end
21
+ puts
22
+ end
23
+
24
+ def wait_for_delete(table_name)
25
+ logger.info "Waiting for #{table_name} table to be deleted."
26
+ begin
27
+ client.describe_table(table_name: table_name)
28
+ print '.'
29
+ sleep pause_time
30
+ rescue Aws::DynamoDB::Errors::ResourceNotFoundException => e
31
+ end
32
+ puts
33
+ end
34
+
35
+ private
36
+ def pause_time
37
+ 1
38
+ end
39
+ end
40
+ end
data/lib/dynomite.rb CHANGED
@@ -1,25 +1,19 @@
1
1
  $:.unshift(File.expand_path("../", __FILE__))
2
+ require "active_support"
3
+ require "active_support/concern"
4
+ require "active_support/core_ext/hash"
5
+ require "active_support/core_ext/string"
2
6
  require "dynomite/version"
7
+ require "memoist"
3
8
  require "rainbow/ext/string"
4
9
 
5
- module Dynomite
6
- ATTRIBUTE_TYPES = {
7
- 'string' => 'S',
8
- 'number' => 'N',
9
- 'binary' => 'B',
10
- 's' => 'S',
11
- 'n' => 'N',
12
- 'b' => 'B',
13
- }
10
+ require "dynomite/autoloader"
11
+ Dynomite::Autoloader.setup
14
12
 
15
- autoload :Migration, "dynomite/migration"
16
- autoload :Dsl, "dynomite/dsl"
17
- autoload :DbConfig, "dynomite/db_config"
18
- autoload :Item, "dynomite/item"
19
- autoload :Core, "dynomite/core"
20
- autoload :Erb, "dynomite/erb"
21
- autoload :Log, "dynomite/log"
22
- autoload :Errors, "dynomite/errors"
13
+ require "dynomite/reserved_words"
23
14
 
15
+ module Dynomite
24
16
  extend Core
25
17
  end
18
+
19
+ require "dynomite/engine" if defined?(Jets)