diff --git a/lib/generators/scenic/view/templates/db/migrate/update_view.erb b/lib/generators/scenic/view/templates/db/migrate/update_view.erb index 7282edd5..f286af46 100644 --- a/lib/generators/scenic/view/templates/db/migrate/update_view.erb +++ b/lib/generators/scenic/view/templates/db/migrate/update_view.erb @@ -2,11 +2,13 @@ class <%= migration_class_name %> < <%= activerecord_migration_class %> def change <%- if materialized? -%> update_view <%= formatted_plural_name %>, - version: <%= version %>, - revert_to_version: <%= previous_version %>, + version: <%= definition.version %>, + revert_to_version: <%= previous_definition.version %>, materialized: <%= no_data? ? "{ no_data: true }" : true %> <%- else -%> - update_view <%= formatted_plural_name %>, version: <%= version %>, revert_to_version: <%= previous_version %> + update_view <%= formatted_plural_name %>, + version: <%= definition.version %>, + revert_to_version: <%= previous_definition.version %> <%- end -%> end end diff --git a/lib/generators/scenic/view/view_generator.rb b/lib/generators/scenic/view/view_generator.rb index 496035f8..28fce780 100644 --- a/lib/generators/scenic/view/view_generator.rb +++ b/lib/generators/scenic/view/view_generator.rb @@ -1,6 +1,8 @@ require "rails/generators" require "rails/generators/active_record" require "generators/scenic/materializable" +require "scenic/definition" +require "scenic/definitions" module Scenic module Generators @@ -11,8 +13,8 @@ class ViewGenerator < Rails::Generators::NamedBase source_root File.expand_path("templates", __dir__) def create_views_directory - unless views_directory_path.exist? - empty_directory(views_directory_path) + unless Scenic.configuration.definitions_path.exist? + empty_directory(Scenic.configuration.definitions_path) end end @@ -20,7 +22,7 @@ def create_view_definition if creating_new_view? create_file definition.path else - copy_file previous_definition.full_path, definition.full_path + copy_file previous_definition.path, definition.path end end @@ -31,6 +33,7 @@ def create_migration_file "db/migrate/create_#{plural_file_name}.rb", ) else + version = definition.version migration_template( "db/migrate/update_view.erb", "db/migrate/update_#{plural_file_name}_to_version_#{version}.rb", @@ -43,22 +46,11 @@ def self.next_migration_number(dir) end no_tasks do - def previous_version - @previous_version ||= - Dir.entries(views_directory_path) - .map { |name| version_regex.match(name).try(:[], "version").to_i } - .max - end - - def version - @version ||= destroying? ? previous_version : previous_version.next - end - def migration_class_name if creating_new_view? "Create#{class_name.tr('.', '').pluralize}" else - "Update#{class_name.pluralize}ToVersion#{version}" + "Update#{class_name.pluralize}ToVersion#{definition.version}" end end @@ -79,24 +71,22 @@ def file_name super.tr(".", "_") end - def views_directory_path - @views_directory_path ||= Rails.root.join("db", "views") - end - - def version_regex - /\A#{plural_file_name}_v(?\d+)\.sql\z/ + def definitions + @definitions ||= Scenic::Definitions.new(plural_file_name) end def creating_new_view? - previous_version.zero? + definitions.none? end def definition + previous_version = previous_definition.version + version = destroying? ? previous_version : previous_version.next Scenic::Definition.new(plural_file_name, version) end def previous_definition - Scenic::Definition.new(plural_file_name, previous_version) + definitions.max || Scenic::Definition.new(plural_file_name, 0) end def destroying? @@ -120,7 +110,7 @@ def create_view_options end def destroying_initial_view? - destroying? && version == 1 + destroying? && definition.version == 1 end end end diff --git a/lib/scenic.rb b/lib/scenic.rb index 8adcb2ee..c1637ac0 100644 --- a/lib/scenic.rb +++ b/lib/scenic.rb @@ -1,7 +1,6 @@ require "scenic/configuration" require "scenic/adapters/postgres" require "scenic/command_recorder" -require "scenic/definition" require "scenic/railtie" require "scenic/schema_dumper" require "scenic/statements" diff --git a/lib/scenic/configuration.rb b/lib/scenic/configuration.rb index aec067fa..f3408c4a 100644 --- a/lib/scenic/configuration.rb +++ b/lib/scenic/configuration.rb @@ -1,3 +1,5 @@ +require_relative "adapters/postgres" + module Scenic class Configuration # The Scenic database adapter instance to use when executing SQL. @@ -6,8 +8,15 @@ class Configuration # @return Scenic adapter attr_accessor :database + # The full path where is stored views definition sql files. + # + # Defaults to `Rails.root.join("db", "views")` an instance of {Pathname} + # @return the path + attr_accessor :definitions_path + def initialize - @database = Scenic::Adapters::Postgres.new + self.database = Scenic::Adapters::Postgres.new + self.definitions_path = Rails.root.join("db", "views") end end diff --git a/lib/scenic/definition.rb b/lib/scenic/definition.rb index dac137cd..50bc4f55 100644 --- a/lib/scenic/definition.rb +++ b/lib/scenic/definition.rb @@ -1,35 +1,35 @@ module Scenic # @api private class Definition + include Comparable + + attr_reader :name, :version + def initialize(name, version) @name = name @version = version.to_i end def to_sql - File.read(full_path).tap do |content| + File.read(path).tap do |content| if content.empty? raise "Define view query in #{path} before migrating." end end end - def full_path - Rails.root.join(path) - end - def path - File.join("db", "views", filename) + Scenic.configuration.definitions_path.join(filename) end - def version - @version.to_s.rjust(2, "0") + def <=>(other) + version <=> other.version end private def filename - "#{@name.to_s.tr('.', '_')}_v#{version}.sql" + "#{name.to_s.tr('.', '_')}_v#{version.to_s.rjust(2, '0')}.sql" end end end diff --git a/lib/scenic/definitions.rb b/lib/scenic/definitions.rb new file mode 100644 index 00000000..79027b10 --- /dev/null +++ b/lib/scenic/definitions.rb @@ -0,0 +1,34 @@ +require_relative "definition" + +module Scenic + # @api private + class Definitions + include Enumerable + + attr_reader :name, :views_directory_path + + def initialize(name) + @name = name + end + + def each + versions.each do |version| + yield Scenic::Definition.new(name, version) + end + end + + def versions + @versions ||= Dir.entries(Scenic.configuration.definitions_path) + .map { |filename| /\A#{name}_v(?\d+)\.sql\z/.match(filename) } + .compact + .map { |match| match["version"].to_i } + .sort + end + + private + + def version_regex + /\A#{name}_v(?\d+)\.sql\z/ + end + end +end diff --git a/lib/scenic/statements.rb b/lib/scenic/statements.rb index 84bf24b0..9989ead2 100644 --- a/lib/scenic/statements.rb +++ b/lib/scenic/statements.rb @@ -1,3 +1,4 @@ +require_relative "definition" module Scenic # Methods that are made available in migrations for managing Scenic views. module Statements diff --git a/spec/generators/scenic/view/view_generator_spec.rb b/spec/generators/scenic/view/view_generator_spec.rb index cf4acd22..202ba9aa 100644 --- a/spec/generators/scenic/view/view_generator_spec.rb +++ b/spec/generators/scenic/view/view_generator_spec.rb @@ -30,6 +30,7 @@ allow(Dir).to receive(:entries).and_return(["aired_episodes_v01.sql"]) run_generator ["aired_episode", "--materialized"] + migration = migration_file( "db/migrate/update_aired_episodes_to_version_2.rb", ) diff --git a/spec/scenic/definition_spec.rb b/spec/scenic/definition_spec.rb index fd1157b8..4b3d13ad 100644 --- a/spec/scenic/definition_spec.rb +++ b/spec/scenic/definition_spec.rb @@ -23,7 +23,7 @@ module Scenic describe "path" do it "returns a sql file in db/views with padded version and view name" do - expected = "db/views/searches_v01.sql" + expected = Rails.root.join("db/views/searches_v01.sql") definition = Definition.new("searches", 1) @@ -32,16 +32,9 @@ module Scenic it "handles schema qualified view names" do definition = Definition.new("non_public.searches", 1) + expected = Rails.root.join("db/views/non_public_searches_v01.sql") - expect(definition.path).to eq "db/views/non_public_searches_v01.sql" - end - end - - describe "full_path" do - it "joins the path with Rails.root" do - definition = Definition.new("searches", 15) - - expect(definition.full_path).to eq Rails.root.join(definition.path) + expect(definition.path).to eq expected end end @@ -49,13 +42,13 @@ module Scenic it "pads the version number with 0" do definition = Definition.new(:_, 1) - expect(definition.version).to eq "01" + expect(definition.version).to eq 1 end it "doesn't pad more than 2 characters" do definition = Definition.new(:_, 15) - expect(definition.version).to eq "15" + expect(definition.version).to eq 15 end end end diff --git a/spec/support/generator_spec_setup.rb b/spec/support/generator_spec_setup.rb index fdeaa9d1..4c62a2b1 100644 --- a/spec/support/generator_spec_setup.rb +++ b/spec/support/generator_spec_setup.rb @@ -4,11 +4,23 @@ require "ammeter/init" RSpec.configure do |config| + rails_root = Rails.root + fake_rails_root = Pathname.new(File.expand_path("../../tmp", __dir__)) + config.before(:example, :generator) do - fake_rails_root = File.expand_path("../../tmp", __dir__) - allow(Rails).to receive(:root).and_return(Pathname.new(fake_rails_root)) + allow(Rails).to receive(:root).and_return(fake_rails_root) - destination fake_rails_root + destination fake_rails_root.to_s prepare_destination + + Scenic.configure do |configuration| + configuration.definitions_path = fake_rails_root.join("db", "views") + end + end + + config.after(:example, :generator) do + Scenic.configure do |configuration| + configuration.definitions_path = rails_root.join("db", "views") + end end end diff --git a/spec/support/view_definition_helpers.rb b/spec/support/view_definition_helpers.rb index d1688bdf..47483305 100644 --- a/spec/support/view_definition_helpers.rb +++ b/spec/support/view_definition_helpers.rb @@ -1,10 +1,10 @@ module ViewDefinitionHelpers def with_view_definition(name, version, schema) definition = Scenic::Definition.new(name, version) - FileUtils.mkdir_p(File.dirname(definition.full_path)) - File.open(definition.full_path, "w") { |f| f.write(schema) } + FileUtils.mkdir_p(File.dirname(definition.path)) + File.open(definition.path, "w") { |f| f.write(schema) } yield ensure - FileUtils.rm_f(definition.full_path) + FileUtils.rm_f(definition.path) end end