class Monocle::View

Attributes

dependants[RW]
name[R]

Public Class Methods

new(name) click to toggle source
# File lib/monocle/view.rb, line 10
def initialize(name)
  @name = name
  @dependants = []
end

Public Instance Methods

create() click to toggle source
# File lib/monocle/view.rb, line 30
def create
  debug "Creating #{name}..."
  execute create_command
  Migration.find_or_create_by version: slug
  dependants.each &:create
  true
rescue ActiveRecord::StatementInvalid => e
  # We may have another new view coming that this view depend on
  # if the relation name is included on our list of views, we create
  # that first and then retry
  if e.message =~ /PG::UndefinedTable/ &&
     e.message.scan(/relation \"(\w+)\" does not exist/) &&
     list.keys.include?($1.to_sym)
     warn "Can't create #{name} because it depends on #{$1}, creating that first..."
     list.fetch($1.to_sym).create
     retry
  else
    fail e
  end
end
create_command() click to toggle source
# File lib/monocle/view.rb, line 103
def create_command
  @create_command ||= File.read(path_for_sql)
end
drop() click to toggle source
# File lib/monocle/view.rb, line 22
def drop
  debug "Dropping #{name}..."
  self.dependants = get_dependants_from_pg
  dependants.each &:drop
  execute drop_command
  true
end
drop_command() click to toggle source
# File lib/monocle/view.rb, line 98
def drop_command
  _materialized = 'MATERIALIZED' if materialized?
  "DROP #{_materialized} VIEW IF EXISTS #{name};"
end
drop_requested?() click to toggle source
# File lib/monocle/view.rb, line 19
def drop_requested?
end
exists?() click to toggle source
# File lib/monocle/view.rb, line 111
def exists?
  execute(check_if_view_exists_sql).entries.map(&:values).flatten.first
end
materialized?() click to toggle source
# File lib/monocle/view.rb, line 15
def materialized?
  !!(@materialized ||= create_command =~ /MATERIALIZED VIEW/i)
end
migrate() click to toggle source
# File lib/monocle/view.rb, line 51
def migrate
  if :drop == slug
    drop
    info "#{name} dropped."
    return true
  end
  if versions.include?(slug)
    debug "Skipping #{name} as it's already up to date."
    true
  else
    status = drop && create
    info "#{name} migrated to #{slug}!"
    status
  end
end
path_for_sql() click to toggle source
# File lib/monocle/view.rb, line 107
def path_for_sql
  @path_for_sql ||= File.join views_path, "#{name}.sql"
end
refresh(concurrently: false) click to toggle source
# File lib/monocle/view.rb, line 67
def refresh(concurrently: false)
  if :drop == slug
    drop
    info "#{name} dropped."
    return true
  end      
  # We don't refresh normal views
  return false unless materialized?
  _concurrently = " CONCURRENTLY" if concurrently
  refresh_cmd = "REFRESH MATERIALIZED VIEW#{_concurrently} #{name}"
  puts "MONOCLE:REFRESH - REFRESH MATERIALIZED VIEW#{_concurrently} #{name}"
  execute refresh_cmd
  true
rescue ActiveRecord::StatementInvalid => e
  # This view is trying to select from a different view that hasn't been
  # populated.
  if e.message =~ /PG::ObjectNotInPrerequisiteState/ &&
     e.message.scan(/materialized view \"(\w+)\" has not been populated/) &&
     list.keys.include?($1.to_sym)
     warn "Can't refresh #{name} because it depends on #{$1} which hasn't been populated, refreshing that first..."
     list.fetch($1.to_sym).refresh
     retry
  else
    fail e
  end
end
slug() click to toggle source
# File lib/monocle/view.rb, line 94
def slug
  @slug ||= VersionGenerator.new(path_for_sql).generate
end

Protected Instance Methods

check_if_view_exists_sql() click to toggle source
# File lib/monocle/view.rb, line 117
    def check_if_view_exists_sql
      <<-SQL
        SELECT count(*) > 0
        FROM pg_catalog.pg_class c
        JOIN pg_namespace n ON n.oid = c.relnamespace
        WHERE c.relkind in ('m','v')
        AND n.nspname = 'public'
        AND c.relname = '#{name}';
      SQL
    end
execute(sql) click to toggle source
# File lib/monocle/view.rb, line 157
def execute(sql)
  ActiveRecord::Base.connection.execute(sql)
end
find_dependants_sql() click to toggle source
# File lib/monocle/view.rb, line 136
    def find_dependants_sql
      <<-SQL
      WITH RECURSIVE vlist AS (
          SELECT c.oid::REGCLASS AS view_name
            FROM pg_class c
           WHERE c.relname = '#{name}'
           UNION ALL
          SELECT DISTINCT r.ev_class::REGCLASS AS view_name
            FROM pg_depend d
            JOIN pg_rewrite r ON (r.oid = d.objid)
            JOIN vlist ON (vlist.view_name = d.refobjid)
           WHERE d.refobjsubid != 0
      )
      SELECT * FROM vlist;
      SQL
    end
get_dependants_from_error(e) click to toggle source
# File lib/monocle/view.rb, line 153
def get_dependants_from_error(e)
  map_dependants e.message.scan(/(\w+) depends on.+view #{name}/).flatten
end
get_dependants_from_pg() click to toggle source
# File lib/monocle/view.rb, line 128
def get_dependants_from_pg
  map_dependants(execute(find_dependants_sql).entries.map(&:values).flatten - [name])
end
map_dependants(deps) click to toggle source
# File lib/monocle/view.rb, line 132
def map_dependants(deps)
  deps.map { |d| list[d.to_sym] }.compact
end