From ff9badea3c379146aabb0eb872e5fed580ca4224 Mon Sep 17 00:00:00 2001 From: Marcin Ruszkiewicz Date: Wed, 2 Oct 2019 07:26:06 +0200 Subject: [PATCH] Add force_datetime_default_format options and tests --- .gitignore | 2 + .rspec | 3 + Gemfile.lock | 21 +++ README.md | 22 ++++ activerecord-clean-db-structure.gemspec | 2 + .../clean_dump.rb | 12 ++ spec/clean_dump_spec.rb | 123 ++++++++++++++++++ spec/fixtures/dates.sql | 16 +++ spec/fixtures/empty_comments.sql | 18 +++ spec/fixtures/ending_whitespace.sql | 21 +++ spec/fixtures/unordered_columns.sql | 17 +++ spec/spec_helper.rb | 30 +++++ 12 files changed, 287 insertions(+) create mode 100644 .rspec create mode 100644 spec/clean_dump_spec.rb create mode 100644 spec/fixtures/dates.sql create mode 100644 spec/fixtures/empty_comments.sql create mode 100644 spec/fixtures/ending_whitespace.sql create mode 100644 spec/fixtures/unordered_columns.sql create mode 100644 spec/spec_helper.rb diff --git a/.gitignore b/.gitignore index 5fff1d9..a4be6dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ pkg +.rspec_status +.DS_Store diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/Gemfile.lock b/Gemfile.lock index d47d03a..3837004 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -18,11 +18,30 @@ GEM minitest (~> 5.1) tzinfo (~> 1.1) zeitwerk (~> 2.1, >= 2.1.8) + coderay (1.1.2) concurrent-ruby (1.1.5) + diff-lcs (1.3) i18n (1.6.0) concurrent-ruby (~> 1.0) + method_source (0.9.2) minitest (5.11.3) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) rake (0.9.6) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.2) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.4) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.2) thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) @@ -33,7 +52,9 @@ PLATFORMS DEPENDENCIES activerecord-clean-db-structure! + pry rake (~> 0) + rspec BUNDLED WITH 1.17.3 diff --git a/README.md b/README.md index d4dd5d3..aae1a1e 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,28 @@ INSERT INTO "schema_migrations" (version) VALUES ; ``` +You can also deal with default dates in the structure file if they're the common source of merge conflicts - this usually happens when using `DateTime.current` as a default and having your team run the migrations at different times. + +You can set the `force_datetime_default_format` option to a `Time` object like so: + +```ruby +Rails.application.configure do + config.activerecord_clean_db_structure.force_datetime_default_format = Time.new(2019, 5, 6, 16, 44, 22) +end +``` + +And it will set every date default to the passed date. + +Alternatively, you can set the `force_datetime_default_format` option to `true`: + +```ruby +Rails.application.configure do + config.activerecord_clean_db_structure.force_datetime_default_format = true +end +``` + +Which will only cut out the miliseconds from every datetime default. + ## Authors * [Lukas Fittl](https://github.com/lfittl) diff --git a/activerecord-clean-db-structure.gemspec b/activerecord-clean-db-structure.gemspec index acf7764..1c6d3c7 100644 --- a/activerecord-clean-db-structure.gemspec +++ b/activerecord-clean-db-structure.gemspec @@ -18,4 +18,6 @@ Gem::Specification.new do |s| s.add_dependency('activerecord', '>= 4.2') s.add_development_dependency 'rake', '~> 0' + s.add_development_dependency 'rspec' + s.add_development_dependency 'pry' end diff --git a/lib/activerecord-clean-db-structure/clean_dump.rb b/lib/activerecord-clean-db-structure/clean_dump.rb index 8abb6c5..2f1a04b 100644 --- a/lib/activerecord-clean-db-structure/clean_dump.rb +++ b/lib/activerecord-clean-db-structure/clean_dump.rb @@ -131,6 +131,18 @@ def run if options[:order_column_definitions] == true dump.replace(order_column_definitions(dump)) end + + if options[:force_datetime_default_format].is_a?(Time) + # force ALL default dates in the schema to be the same date + datestring = options[:force_datetime_default_format].strftime('%F %T') + dump.gsub!(/DEFAULT '\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2}(\.\d{1,6})?'/, "DEFAULT '#{datestring}'") + elsif options[:force_datetime_default_format] == true + # alternatively only cut out miliseconds if they're present + date_regexp = /DEFAULT '(\d{4}-\d{1,2}-\d{1,2} \d{1,2}:\d{1,2}:\d{1,2})(\.\d{1,6})?'/m + dump.scan(date_regexp).each do |date| + dump.gsub!(/DEFAULT '#{date.join}'/, "DEFAULT '#{date[0]}'") + end + end end def order_column_definitions(source) diff --git a/spec/clean_dump_spec.rb b/spec/clean_dump_spec.rb new file mode 100644 index 0000000..609c523 --- /dev/null +++ b/spec/clean_dump_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +RSpec.describe ActiveRecordCleanDbStructure::CleanDump do + context 'with empty comment lines' do + let(:sql_dump) { file_fixture('spec/fixtures/empty_comments.sql').read } + let(:cleaner) { described_class.new sql_dump.clone } + let(:expected_columns) do + [ + ');', + '', + '-- useless comment' + ] + end + + it 'removes empty comments lines' do + cleaner.run + expect(cleaner.dump.split("\n").last(3)).to eq expected_columns + end + end + + context 'with unnecessary whitespace' do + let(:sql_dump) { file_fixture('spec/fixtures/ending_whitespace.sql').read } + let(:cleaner) { described_class.new sql_dump.clone } + + it 'removes empty whitespace lines' do + cleaner.run + expect(cleaner.dump).to end_with ");\n\n" + end + end + + context 'with order_column_definitions option' do + let(:sql_dump) { file_fixture('spec/fixtures/unordered_columns.sql').read } + let(:cleaner) { described_class.new sql_dump.clone, order_column_definitions: true } + let(:expected_columns) do + [ + 'CREATE TABLE public.model (', + ' alpha character varying(255),', + ' beta character varying(255),', + ' gamma character varying(255),', + ' id SERIAL PRIMARY KEY', + ');' + ] + end + + it 'sorts columns alphabetically' do + cleaner.run + expect(cleaner.dump.split("\n").last(6)).to eq expected_columns + end + end + + context 'without order_column_definitions option' do + let(:sql_dump) { file_fixture('spec/fixtures/unordered_columns.sql').read } + let(:cleaner) { described_class.new sql_dump.clone } + let(:expected_columns) do + [ + 'CREATE TABLE public.model (', + ' id SERIAL PRIMARY KEY,', + ' beta character varying(255),', + ' gamma character varying(255),', + ' alpha character varying(255)', + ');' + ] + end + + it 'does not sort columns' do + cleaner.run + expect(cleaner.dump.split("\n").last(6)).to eq expected_columns + end + end + + context 'with force_datetime_default_format' do + context 'with Time object passed' do + let(:sql_dump) { file_fixture('spec/fixtures/dates.sql').read } + let(:cleaner) { described_class.new sql_dump.clone, force_datetime_default_format: Time.new(2019, 5, 6, 16, 44, 22) } + let(:expected_columns) do + [ + " alpha timestamp without time zone DEFAULT '2019-05-06 16:44:22'::timestamp without time zone,", + " beta timestamp without time zone DEFAULT '2019-05-06 16:44:22'::timestamp without time zone", + ');' + ] + end + + it 'forces all dates to be same datetime' do + cleaner.run + expect(cleaner.dump.split("\n").last(3)).to eq expected_columns + end + end + + context 'with true passed' do + let(:sql_dump) { file_fixture('spec/fixtures/dates.sql').read } + let(:cleaner) { described_class.new sql_dump.clone, force_datetime_default_format: true } + let(:expected_columns) do + [ + " alpha timestamp without time zone DEFAULT '2015-12-18 23:38:27'::timestamp without time zone,", + " beta timestamp without time zone DEFAULT '2016-05-10 14:01:06'::timestamp without time zone", + ');' + ] + end + + it 'forces all dates to be same format' do + cleaner.run + expect(cleaner.dump.split("\n").last(3)).to eq expected_columns + end + end + end + + context 'without force_datetime_default_format' do + let(:sql_dump) { file_fixture('spec/fixtures/dates.sql').read } + let(:cleaner) { described_class.new sql_dump.clone } + let(:expected_columns) do + [ + " alpha timestamp without time zone DEFAULT '2015-12-18 23:38:27.804383'::timestamp without time zone,", + " beta timestamp without time zone DEFAULT '2016-05-10 14:01:06'::timestamp without time zone", + ');' + ] + end + + it 'leaves dates defaults as is' do + cleaner.run + expect(cleaner.dump.split("\n").last(3)).to eq expected_columns + end + end +end diff --git a/spec/fixtures/dates.sql b/spec/fixtures/dates.sql new file mode 100644 index 0000000..9b2444b --- /dev/null +++ b/spec/fixtures/dates.sql @@ -0,0 +1,16 @@ + +-- PostgreSQL database dump + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; + +CREATE TABLE public.model ( + id SERIAL PRIMARY KEY, + alpha timestamp without time zone DEFAULT '2015-12-18 23:38:27.804383'::timestamp without time zone, + beta timestamp without time zone DEFAULT '2016-05-10 14:01:06'::timestamp without time zone +); diff --git a/spec/fixtures/empty_comments.sql b/spec/fixtures/empty_comments.sql new file mode 100644 index 0000000..0564c39 --- /dev/null +++ b/spec/fixtures/empty_comments.sql @@ -0,0 +1,18 @@ + +-- PostgreSQL database dump + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; + +CREATE TABLE public.model ( + id SERIAL PRIMARY KEY, + name character varying(255) +); +-- +-- useless comment +-- diff --git a/spec/fixtures/ending_whitespace.sql b/spec/fixtures/ending_whitespace.sql new file mode 100644 index 0000000..285d273 --- /dev/null +++ b/spec/fixtures/ending_whitespace.sql @@ -0,0 +1,21 @@ + +-- PostgreSQL database dump + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; + +CREATE TABLE public.model ( + id SERIAL PRIMARY KEY, + name character varying(255) +); + + + + + + diff --git a/spec/fixtures/unordered_columns.sql b/spec/fixtures/unordered_columns.sql new file mode 100644 index 0000000..be9ad00 --- /dev/null +++ b/spec/fixtures/unordered_columns.sql @@ -0,0 +1,17 @@ + +-- PostgreSQL database dump + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET client_min_messages = warning; + +CREATE TABLE public.model ( + id SERIAL PRIMARY KEY, + beta character varying(255), + gamma character varying(255), + alpha character varying(255) +); diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..10498db --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +ENV['RACK_ENV'] = 'test' + +require 'bundler/setup' +require 'activerecord-clean-db-structure/clean_dump' +require 'pry' + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = '.rspec_status' + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end + +def file_fixture(fixture_name) + path = Pathname.new(File.join(fixture_name)) + + if path.exist? + path + else + msg = "file does not exist: '%s'" + raise ArgumentError, format(msg, fixture_name) + end +end