Skip to content

Instantly share code, notes, and snippets.

@sfcgeorge
Created December 16, 2019 11:05
Show Gist options
  • Save sfcgeorge/6b28e4f038e50a2e642fc14eacbc07c9 to your computer and use it in GitHub Desktop.
Save sfcgeorge/6b28e4f038e50a2e642fc14eacbc07c9 to your computer and use it in GitHub Desktop.
Patch 1 method in Scenic's schema dumper so that views are sorted first alphabetically, and then have any dependencies resolved.
# frozen_string_literal: true
require 'rails'
require 'scenic'
module Scenic
# @api private
module SchemaDumper
private
MAX_SORT_ATTEMPTS = 500
SCENIC_CHANGED = "Scenic's code has changed, update this patch!"
# Some checks to make sure Scenic's relevant code hasn't changed
raise SCENIC_CHANGED unless private_method_defined?(
:dumpable_views_in_database
)
raise SCENIC_CHANGED unless instance_method(
:dumpable_views_in_database
).source_location.first.split('/').last == 'schema_dumper.rb'
raise SCENIC_CHANGED unless instance_method(
:dumpable_views_in_database
).source_location.last == 24
def dumpable_views_in_database
@dumpable_views_in_database ||= begin
views = Scenic.database.views.reject { |view| ignored?(view.name) }
# First alphabetize so the initial order is deterministic
alphabetical_views = views.sort_by(&:name)
# We're gonna do a really dumb sort
n = 0
same = false
previously_sorted_views = alphabetical_views
# Keep looping until the order stops changing, AKA they're all sorted
until same || n > MAX_SORT_ATTEMPTS
n += 1
sorted_views = previously_sorted_views.dup
previously_sorted_views.each_with_index do |view, i|
next if i.zero?
prev_views = previously_sorted_views[0...i]
# Match the view name on its own, not a column name
# boundary character, view name, space or close parenthesis
matcher = Regexp.new("\\b#{view.name}\(\s|\\)\)", true)
next unless prev_views.any? do |prev_view|
prev_view.definition.match?(matcher)
end
prior_view = previously_sorted_views[i - 1]
# Swap
sorted_views[i - 1] = view
sorted_views[i] = prior_view
# Let's not get smart with the sort algo perf, we'll just repeat
break
end
same = sorted_views == previously_sorted_views
previously_sorted_views = sorted_views
end
# If there's a cyclic dependency, AKA 2 views reference each other
# (or the above Regexp is faulty) then the sort will keep swapping views
# so we fall back to whatever order the database spits out.
if n > MAX_SORT_ATTEMPTS
puts 'View sort failed, order non-deterministic. Using default order'
views
else
previously_sorted_views
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment