pliny 0.31.0 → 0.32.0

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: 03b6f9bac86953d6bef8858e6c4c1f6f94f06073fac8175b9572728ffed5a276
4
- data.tar.gz: c20333f978e5f8e908b13f6eaf9d37a3dbdb0f10204c1db9a8b5f83b02fbac11
3
+ metadata.gz: 8de7550e51b73562d5fbf52d648945f5f07925cc5df89b5526faba9fc5b27075
4
+ data.tar.gz: 46f1ed81d71d0b9b70332085bc6dc61038a98d8f66ba2645db6651f355f85fd4
5
5
  SHA512:
6
- metadata.gz: e851ccdd1fb0a3d548fa672d209829e9cd8ecf69023d1c7a2fb6ad0fb84ee1616d2d3b2a761d11ba9c13a989d2fb5176c31d3eb6acf9a02e20c6e7bc4b01386f
7
- data.tar.gz: aefb3c02dc44ba282595bb9b2e85dc52e2891cb16ac6395693ab63bd79c603b7c68e9b93dea4c494c1b574e8be308b26a34e40040d4e71e0a74cb9a9f828a207
6
+ metadata.gz: 90130887007a82d06b841e6e4ca27f0f242f24eb782f273037a50a849d8a8a19ce73b7d4fb42ee75bc983b8cdfac57f02057e6322d11ab00678ef5c51b2d8d13
7
+ data.tar.gz: a77f028909da4a10a07db20f96e3fa872c351114395adbbbe500a1106bc0b3882676bdd94fbb52ec91a23676ba92cce2b84a76277a385321f29c16c64ede2fc1
@@ -62,6 +62,134 @@ module Pliny
62
62
  Integer(version)
63
63
  end
64
64
 
65
+ class MigrationStatus
66
+ attr_reader :filename
67
+ attr_accessor :present_on_disk, :present_in_database
68
+
69
+ def initialize(filename:)
70
+ @filename = filename
71
+ @present_on_disk = false
72
+ @present_in_database = false
73
+ end
74
+
75
+ def status
76
+ if present_on_disk
77
+ if present_in_database
78
+ :up
79
+ else
80
+ :down
81
+ end
82
+ else
83
+ if present_in_database
84
+ :file_missing
85
+ else
86
+ raise "error" # FIXME: better message
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ class MigrationStatusPresenter
93
+ PADDING = 2
94
+ UP = "UP".freeze
95
+ DOWN = "DOWN".freeze
96
+ FILE_MISSING = "FILE MISSING".freeze
97
+
98
+ STATUS_MAP = {
99
+ up: UP,
100
+ down: DOWN,
101
+ file_missing: FILE_MISSING
102
+ }.freeze
103
+
104
+ STATUS_OPTIONS = [
105
+ UP,
106
+ DOWN,
107
+ FILE_MISSING
108
+ ].freeze
109
+
110
+ attr_reader :migration_statuses
111
+
112
+ def initialize(migration_statuses:)
113
+ @migration_statuses = migration_statuses
114
+ end
115
+
116
+ def to_s
117
+ rows.join("\n")
118
+ end
119
+
120
+ def rows
121
+ header + statuses + footer
122
+ end
123
+
124
+ def header
125
+ [
126
+ barrier_row,
127
+ header_row,
128
+ barrier_row
129
+ ]
130
+ end
131
+
132
+ def statuses
133
+ migration_statuses.map { |status|
134
+ status_row(status)
135
+ }
136
+ end
137
+
138
+ def footer
139
+ [
140
+ barrier_row
141
+ ]
142
+ end
143
+
144
+ def barrier_row
145
+ "+#{'-' * (longest_status + PADDING)}+#{'-' * (longest_migration_name + PADDING)}+"
146
+ end
147
+
148
+ def header_row
149
+ "|#{'STATUS'.center(longest_status + PADDING)}|#{'MIGRATION'.center(longest_migration_name + PADDING)}|"
150
+ end
151
+
152
+ def status_row(migration_status)
153
+ "|#{STATUS_MAP[migration_status.status].center(longest_status + PADDING)}|#{' ' * (PADDING / 2)}#{migration_status.filename.ljust(longest_migration_name)}#{' ' * (PADDING / 2)}|"
154
+ end
155
+
156
+ private
157
+
158
+ def longest_migration_name
159
+ @longest_migration_name ||= migration_statuses.map(&:filename).max_by(&:length).length
160
+ end
161
+
162
+ def longest_status
163
+ STATUS_OPTIONS.max_by(&:length).length
164
+ end
165
+ end
166
+
167
+ def status
168
+ migrations_in_database = get_migrations_from_database
169
+ migrations_on_disk = Dir["#{MIGRATION_DIR}/*.rb"].map { |f| File.basename(f) }
170
+ total_set_of_migrations = (migrations_in_database | migrations_on_disk).sort_by(&:to_i)
171
+
172
+ migration_statuses = total_set_of_migrations.map { |filename|
173
+ status = MigrationStatus.new(filename: filename)
174
+ if migrations_on_disk.include?(filename)
175
+ status.present_on_disk = true
176
+ end
177
+
178
+ if migrations_in_database.include?(filename)
179
+ status.present_in_database = true
180
+ end
181
+ status
182
+ }
183
+
184
+ MigrationStatusPresenter.new(migration_statuses: migration_statuses).to_s
185
+ end
186
+
187
+ def get_migrations_from_database
188
+ return [] unless db.table_exists?(:schema_migrations)
189
+
190
+ db[:schema_migrations].order(Sequel.asc(:filename)).select_map(:filename)
191
+ end
192
+
65
193
  def rollback
66
194
  current_version = version
67
195
  return if current_version.zero?
@@ -15,6 +15,16 @@ begin
15
15
  end
16
16
  end
17
17
 
18
+ namespace :migrate do
19
+ desc "Get the status of migrations"
20
+ task :status do
21
+ # TODO: figure out how to handle multiple databases. Also why do we support multiple databases this way?
22
+ Pliny::DbSupport.run(database_urls.first, $stdout) do |helper|
23
+ puts helper.status
24
+ end
25
+ end
26
+ end
27
+
18
28
  desc "Run database migrations"
19
29
  task :migrate do
20
30
  next if Dir["./db/migrate/*.rb"].empty?
data/lib/pliny/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pliny
2
- VERSION = "0.31.0"
2
+ VERSION = "0.32.0"
3
3
  end
data/lib/template/Gemfile CHANGED
@@ -4,7 +4,7 @@ ruby "2.4.0"
4
4
  gem "multi_json"
5
5
  gem "oj"
6
6
  gem "pg"
7
- gem "pliny", "~> 0.31"
7
+ gem "pliny", "~> 0.32"
8
8
  gem "pry"
9
9
  gem "puma", "~> 3"
10
10
  gem "rack-ssl"
@@ -114,4 +114,243 @@ describe Pliny::DbSupport do
114
114
  end
115
115
  end
116
116
  end
117
+
118
+ describe "MigrationStatus" do
119
+ let(:filename) { "1630551344_latest_change.rb" }
120
+ let(:migration_status) { described_class::MigrationStatus.new(filename: filename) }
121
+
122
+ describe "#status" do
123
+ context "given a migration present on disk" do
124
+ before do
125
+ migration_status.present_on_disk = true
126
+ end
127
+
128
+ context "and present in the database" do
129
+ before do
130
+ migration_status.present_in_database = true
131
+ end
132
+
133
+ it "is :up" do
134
+ assert_equal :up, migration_status.status
135
+ end
136
+ end
137
+
138
+ context "and is absent from the database" do
139
+ before do
140
+ migration_status.present_in_database = false
141
+ end
142
+
143
+ it "is :down" do
144
+ assert_equal :down, migration_status.status
145
+ end
146
+ end
147
+ end
148
+
149
+ context "given a migration absent from disk" do
150
+ before do
151
+ migration_status.present_on_disk = false
152
+ end
153
+
154
+ context "and present in the database" do
155
+ before do
156
+ migration_status.present_in_database = true
157
+ end
158
+
159
+ it "is :file_missing" do
160
+ assert_equal :file_missing, migration_status.status
161
+ end
162
+ end
163
+
164
+ context "and is absent from the database" do
165
+ before do
166
+ migration_status.present_in_database = false
167
+ end
168
+
169
+ it "is an error case" do
170
+ assert_raises RuntimeError do
171
+ migration_status.status
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+
179
+ describe 'MigrationStatusPresenter' do
180
+ let(:filename) { '1630551344_latest_change.rb' }
181
+ let(:up_migration) { described_class::MigrationStatus.new(filename: "00#{filename}") }
182
+ let(:down_migration) { described_class::MigrationStatus.new(filename: "0#{filename}") }
183
+ let(:file_missing_migration) { described_class::MigrationStatus.new(filename: "000#{filename}") }
184
+ let(:migration_statuses) { [up_migration, down_migration, file_missing_migration] }
185
+ let(:presenter) { described_class::MigrationStatusPresenter.new(migration_statuses: migration_statuses) }
186
+
187
+ before do
188
+ up_migration.present_in_database = true
189
+ up_migration.present_on_disk = true
190
+ down_migration.present_in_database = false
191
+ down_migration.present_on_disk = true
192
+ file_missing_migration.present_in_database = true
193
+ file_missing_migration.present_on_disk = false
194
+ end
195
+
196
+ describe '#barrier_row' do
197
+ it 'pads to the longest_migration name' do
198
+ expectation = '+--------------+--------------------------------+'
199
+ assert_equal expectation, presenter.barrier_row
200
+ end
201
+ end
202
+
203
+ describe '#header_row' do
204
+ it 'pads to the longest migration name' do
205
+ expectation = '| STATUS | MIGRATION |'
206
+ assert_equal expectation, presenter.header_row
207
+ end
208
+ end
209
+
210
+ describe '#header' do
211
+ let(:barrier) { '+--------------+--------------------------------+' }
212
+ let(:header) { '| STATUS | MIGRATION |' }
213
+
214
+ it 'wraps the title in barriers' do
215
+ assert_equal [barrier, header, barrier], presenter.header
216
+ end
217
+ end
218
+
219
+ describe '#footer' do
220
+ let(:barrier) { '+--------------+--------------------------------+' }
221
+
222
+ it 'just a barrier' do
223
+ assert_equal [barrier], presenter.footer
224
+ end
225
+ end
226
+
227
+ describe '#status_row' do
228
+ context 'an up migration' do
229
+ it 'shows the correct details' do
230
+ expectation = '| UP | 001630551344_latest_change.rb |'
231
+ assert_equal expectation, presenter.status_row(up_migration)
232
+ end
233
+ end
234
+
235
+ context 'a down migration' do
236
+ it 'shows the correct details' do
237
+ expectation = '| DOWN | 01630551344_latest_change.rb |'
238
+ assert_equal expectation, presenter.status_row(down_migration)
239
+ end
240
+ end
241
+
242
+ context 'a file missing migration' do
243
+ it 'shows the correct details' do
244
+ expectation = '| FILE MISSING | 0001630551344_latest_change.rb |'
245
+ assert_equal expectation, presenter.status_row(file_missing_migration)
246
+ end
247
+ end
248
+ end
249
+
250
+ describe '#statuses' do
251
+ let(:up_expectation) { '| UP | 001630551344_latest_change.rb |' }
252
+ let(:down_expectation) { '| DOWN | 01630551344_latest_change.rb |' }
253
+ let(:file_missing_expectation) { '| FILE MISSING | 0001630551344_latest_change.rb |' }
254
+
255
+ it 'returns strings' do
256
+ assert_equal [up_expectation, down_expectation, file_missing_expectation], presenter.statuses
257
+ end
258
+ end
259
+
260
+ describe '#rows' do
261
+ let(:barrier) { '+--------------+--------------------------------+' }
262
+ let(:header) { '| STATUS | MIGRATION |' }
263
+ let(:up_expectation) { '| UP | 001630551344_latest_change.rb |' }
264
+ let(:down_expectation) { '| DOWN | 01630551344_latest_change.rb |' }
265
+ let(:file_missing_expectation) { '| FILE MISSING | 0001630551344_latest_change.rb |' }
266
+ let(:footer) { '+--------------+--------------------------------+' }
267
+
268
+ it 'is the table as an array' do
269
+ expectation = [
270
+ barrier,
271
+ header,
272
+ barrier,
273
+ up_expectation,
274
+ down_expectation,
275
+ file_missing_expectation,
276
+ footer
277
+ ]
278
+
279
+ assert_equal expectation, presenter.rows
280
+ end
281
+ end
282
+
283
+ describe '#to_s' do
284
+ it 'is the table as a string' do
285
+ expectation = <<~OUTPUT.chomp
286
+ +--------------+--------------------------------+
287
+ | STATUS | MIGRATION |
288
+ +--------------+--------------------------------+
289
+ | UP | 001630551344_latest_change.rb |
290
+ | DOWN | 01630551344_latest_change.rb |
291
+ | FILE MISSING | 0001630551344_latest_change.rb |
292
+ +--------------+--------------------------------+
293
+ OUTPUT
294
+
295
+ assert_equal expectation, presenter.to_s
296
+ end
297
+ end
298
+ end
299
+
300
+ describe '#status' do
301
+ let(:filename) { '1630551344_latest_change.rb' }
302
+ let(:up_migration) { "00#{filename}" }
303
+ let(:down_migration) { "0#{filename}" }
304
+ let(:file_missing_migration) { "000#{filename}" }
305
+
306
+ before do
307
+ DB.create_table(:schema_migrations) do
308
+ text :filename
309
+ end
310
+
311
+ migrations = DB[:schema_migrations]
312
+ migrations.insert(filename: up_migration)
313
+ migrations.insert(filename: file_missing_migration)
314
+
315
+ File.open("./db/migrate/#{up_migration}", "w") do |f|
316
+ f.puts "
317
+ Sequel.migration do
318
+ change do
319
+ create_table(:foo) do
320
+ primary_key :id
321
+ text :bar
322
+ end
323
+ end
324
+ end
325
+ "
326
+ end
327
+
328
+ File.open("./db/migrate/#{down_migration}", "w") do |f|
329
+ f.puts "
330
+ Sequel.migration do
331
+ change do
332
+ create_table(:foo) do
333
+ primary_key :id
334
+ text :bar
335
+ end
336
+ end
337
+ end
338
+ "
339
+ end
340
+ end
341
+
342
+ it 'returns a table string' do
343
+ expectation = <<~OUTPUT.chomp
344
+ +--------------+--------------------------------+
345
+ | STATUS | MIGRATION |
346
+ +--------------+--------------------------------+
347
+ | FILE MISSING | 0001630551344_latest_change.rb |
348
+ | UP | 001630551344_latest_change.rb |
349
+ | DOWN | 01630551344_latest_change.rb |
350
+ +--------------+--------------------------------+
351
+ OUTPUT
352
+
353
+ assert_equal expectation, support.status
354
+ end
355
+ end
117
356
  end
@@ -1,30 +1,46 @@
1
1
  require "spec_helper"
2
+ require "open3"
3
+ require 'active_support/core_ext/object/blank'
2
4
 
3
5
  describe "Pliny integration test" do
4
- before(:all) do
5
- @original_dir = Dir.pwd
6
-
7
- test_dir = Dir.mktmpdir("plinytest-")
8
- Dir.chdir(test_dir)
9
-
10
- bash "pliny-new myapp"
6
+ describe "bin/setup" do
7
+ around do |example|
8
+ original_dir = Dir.pwd
9
+ test_dir = Dir.mktmpdir("plinytest-")
10
+ Dir.chdir(test_dir)
11
11
 
12
- Dir.chdir("myapp")
13
- bash "bin/setup"
14
- end
12
+ bash "pliny-new myapp"
15
13
 
16
- after(:all) do
17
- Dir.chdir(@original_dir)
18
- end
14
+ Dir.chdir("myapp")
15
+ example.run
16
+ Dir.chdir(original_dir)
17
+ end
19
18
 
20
- describe "bin/setup" do
21
19
  it "generates .env" do
20
+ bash "bin/setup"
21
+
22
22
  assert File.exist?("./.env")
23
23
  end
24
24
  end
25
25
 
26
26
  describe "pliny-generate scaffold" do
27
- before(:all) do
27
+ around do |example|
28
+ DB.tables.each { |t| DB.drop_table(t) }
29
+
30
+ original_dir = Dir.pwd
31
+ test_dir = Dir.mktmpdir("plinytest-")
32
+ Dir.chdir(test_dir)
33
+
34
+ bash "pliny-new myapp"
35
+
36
+ Dir.chdir("myapp")
37
+ bash "bin/setup"
38
+
39
+ example.run
40
+ Dir.chdir(original_dir)
41
+ end
42
+
43
+ before do
28
44
  bash "pliny-generate scaffold artist"
29
45
  end
30
46
 
@@ -45,6 +61,71 @@ describe "Pliny integration test" do
45
61
  end
46
62
  end
47
63
 
64
+ describe "rake db:migrate:status" do
65
+ around do |example|
66
+ DB.tables.each { |t| DB.drop_table(t) }
67
+
68
+ original_dir = Dir.pwd
69
+ test_dir = Dir.mktmpdir("plinytest-")
70
+ Dir.chdir(test_dir)
71
+
72
+ bash "pliny-new myapp"
73
+
74
+ Dir.chdir("myapp")
75
+ bash "bin/setup"
76
+
77
+ example.run
78
+ Dir.chdir(original_dir)
79
+ end
80
+
81
+ it "returns a migration in the DOWN state when not migrated" do
82
+ bash "pliny-generate model artist"
83
+ migration_file = Dir.glob('db/migrate/*').first
84
+
85
+ stdout, stderr = bash_with_output("rake db:migrate:status")
86
+
87
+ statuses = Hash[stdout.to_s.split(/\+[-]+\+[-]+\+/)[2..-1].map { |s| s.tr("\n", "") }.select(&:present?).map { |s| s.split("|").map { |s| s.tr(" ", "") }.select(&:present?).reverse }]
88
+ assert statuses[migration_file.split("/").last] == "DOWN"
89
+ end
90
+
91
+ it "returns a migration in the UP state when not migrated" do
92
+ bash "pliny-generate model artist"
93
+ migration_file = Dir.glob('db/migrate/*').first
94
+ bash "rake db:migrate"
95
+
96
+ stdout, stderr = bash_with_output("rake db:migrate:status")
97
+
98
+ statuses = Hash[stdout.to_s.split(/\+[-]+\+[-]+\+/)[2..-1].map { |s| s.tr("\n", "") }.select(&:present?).map { |s| s.split("|").map { |s| s.tr(" ", "") }.select(&:present?).reverse }]
99
+ assert statuses[migration_file.split("/").last] == "UP"
100
+ end
101
+
102
+ it "returns a migration in the FILE MISSING state when the file is missing" do
103
+ bash "pliny-generate model artist"
104
+ migration_file = Dir.glob('db/migrate/*').first
105
+ bash "rake db:migrate"
106
+
107
+ FileUtils.rm_f(migration_file)
108
+
109
+ stdout, stderr = bash_with_output("rake db:migrate:status")
110
+
111
+ statuses = Hash[stdout.to_s.split(/\+[-]+\+[-]+\+/)[2..-1].map { |s| s.tr("\n", "") }.select(&:present?).map { |s| s.split("|").map { |s| s.gsub(/(^[ ]+|[ ]+$)/, "") }.select(&:present?).reverse }]
112
+ assert statuses[migration_file.split("/").last] == "FILE MISSING"
113
+ end
114
+ end
115
+
116
+ def bash_with_output(cmd)
117
+ bin = File.expand_path('../bin', File.dirname(__FILE__))
118
+ path = "#{bin}:#{ENV["PATH"]}"
119
+ env = { "PATH" => path }
120
+ stdout, stderr, status = Open3.capture3(env, cmd)
121
+
122
+ unless status.success?
123
+ raise "Failed to run #{cmd}, error was #{stderr}"
124
+ end
125
+
126
+ return stdout, stderr
127
+ end
128
+
48
129
  def bash(cmd)
49
130
  bin = File.expand_path('../bin', File.dirname(__FILE__))
50
131
  path = "#{bin}:#{ENV["PATH"]}"
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pliny
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.31.0
4
+ version: 0.32.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brandur Leach
8
8
  - Pedro Belo
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-09-02 00:00:00.000000000 Z
12
+ date: 2022-02-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -497,7 +497,7 @@ homepage: https://github.com/interagent/pliny
497
497
  licenses:
498
498
  - MIT
499
499
  metadata: {}
500
- post_install_message:
500
+ post_install_message:
501
501
  rdoc_options: []
502
502
  require_paths:
503
503
  - lib
@@ -512,8 +512,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
512
512
  - !ruby/object:Gem::Version
513
513
  version: '0'
514
514
  requirements: []
515
- rubygems_version: 3.1.6
516
- signing_key:
515
+ rubygems_version: 3.1.2
516
+ signing_key:
517
517
  specification_version: 4
518
518
  summary: Basic tooling to support API apps in Sinatra
519
519
  test_files: []