class EZ::DomainModeler

The DomainModeler class implements the db/models.yml file syntax and provides a hash of the domain model specification.

Constants

DEFAULT_VALUE_REGEXES

Valid formats for default values

Attributes

spec[R]

Get the domain model as a hash

Public Class Methods

automigrate() click to toggle source
# File lib/ez/domain_modeler.rb, line 35
def self.automigrate
  return unless EZ::Config.models?

  begin
    models_yml = File.join(Rails.root, 'db', 'models.yml')
    EZ::DomainModeler.generate_models_yml unless File.exist?(models_yml)

    if should_migrate?(models_yml)
      old_level = ActiveRecord::Base.logger.level

      ActiveRecord::Base.logger.level = Logger::WARN
      EZ::DomainModeler.update_tables
      dump_schema if (Rails.env.development? || Rails.env.test?)

      ActiveRecord::Base.logger.level = old_level
      EZ::RailsUpdater.update! if Rails.env.development?
    end
  rescue => e
    puts "Exception: #{e}"
  end
end
dump_schema() click to toggle source
# File lib/ez/domain_modeler.rb, line 70
def self.dump_schema
  require "active_record/schema_dumper"
  File.open(File.join(Rails.root, 'db/schema.rb'), "w:utf-8") do |file|
    ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
  end
end
generate_models_yml() click to toggle source
# File lib/ez/domain_modeler.rb, line 77
    def self.generate_models_yml
      return unless Rails.env.development?

      filename = Rails.root + "db/models.yml"
      unless File.exist?(filename)
        File.open(filename, "w") do |f|
          f.puts <<-EOS
# Example:
#
# Book
#   title: text
#   author_id: integer
#   summary: text
#   price: integer
#   hardcover: boolean
#
#
# Indent consistently!  Follow the above syntax exactly.
#
#
#
# Column choices are: text, integer, boolean, datetime, and float.
#
#
# Default values can be specified like this:
#
#    price: integer(0)
#
# If not specified, Boolean columns always default to false.
#
# You can omit the column type if it's a text column or obviously an integer column:
#
# Book
#   title
#   author_id
#   summary
#   price: integer
#   hardcover: boolean
#
# Complete details are in the README file online.
#
# Have fun!
  EOS
        end
      end
    end
models() click to toggle source
# File lib/ez/domain_modeler.rb, line 20
def self.models
  tables.map { |t| t.classify }
end
new() click to toggle source
# File lib/ez/domain_modeler.rb, line 60
def initialize
  @ok = false
  begin
    load_model_specs
    @ok = true
  rescue => e
    puts e
  end
end
should_migrate?(models_yml = nil) click to toggle source
# File lib/ez/domain_modeler.rb, line 24
def self.should_migrate?(models_yml = nil)
  models_yml ||= File.join(Rails.root, 'db', 'models.yml')
  return false unless File.exist?(models_yml)

  schema_rb = File.join(Rails.root, 'db', 'schema.rb')
  sqlite_db = File.join(Rails.root, 'db', "#{Rails.env}.sqlite3")
  !(Rails.env.development? || Rails.env.test?) ||
    (!File.exist?(schema_rb) || (File.mtime(schema_rb) < File.mtime(models_yml))) ||
      (!File.exist?(sqlite_db) || (File.mtime(sqlite_db) < File.mtime(models_yml)))
end
tables() click to toggle source
# File lib/ez/domain_modeler.rb, line 16
def self.tables
  tables = ActiveRecord::Base.connection.data_sources - ['schema_migrations', 'ar_internal_metadata']
end
update_tables(silent = false, dry_run = false) click to toggle source
# File lib/ez/domain_modeler.rb, line 124
def self.update_tables(silent = false, dry_run = false)
  self.new.update_tables(silent, dry_run)
end

Public Instance Methods

interpret_column_spec(column_name, column_type, model) click to toggle source
# File lib/ez/domain_modeler.rb, line 196
def interpret_column_spec(column_name, column_type, model)
  column_type ||= begin
    if column_name =~ /_id|_count$/
      'integer'
    elsif column_name =~ /_at$/
      'datetime'
    elsif column_name =~ /_on$/
      'date'
    elsif column_name =~ /\?$/
      'boolean'
    else
      'text'
    end
  end

  default_column_value = nil
  DEFAULT_VALUE_REGEXES.each { |r| default_column_value = $1 if column_type.sub!(r, '') }
  default_column_value = default_column_value.to_i if default_column_value.present? && column_type == 'integer'
  default_column_value = default_column_value.to_f if default_column_value.present? && column_type == 'float'

  if column_type == 'boolean'
    default_column_value = default_column_value.present? && default_column_value.in?(EZ::COLUMN_TRUE_VALUES)
  end

  @spec[model][column_name] = { type: column_type, default: default_column_value}
  @spec[model][column_name][:limit] = 1024 if column_type == 'binary'
  @spec[model][column_name][:index] = true if column_name =~ /_id$/
end
load_model_specs(filename = "db/models.yml") click to toggle source
# File lib/ez/domain_modeler.rb, line 163
def load_model_specs(filename = "db/models.yml")
  load_model_specs_from_string(IO.read(filename))
end
load_model_specs_from_string(s) click to toggle source
# File lib/ez/domain_modeler.rb, line 141
def load_model_specs_from_string(s)

  # Ignore comments
  s.gsub!(/#.*$/,'')

  # Append missing colons
  s.gsub!(/^((\s|\-)*\w[^\:]+?)$/, '\1:')

  # Replace ", default:" syntax so YAML doesn't try to parse it
  s.gsub!(/,?\s*(default)?:?\s(\S)\s*$/, '(\2)')

  # For backward compatibility with old array syntax
  s.gsub!(/^(\s*)\-\s*/, '\1')

  @spec = YAML.load(s)
  parse_model_spec

  # puts "@spec:"
  # puts @spec.inspect
  # puts "-" * 10
end
parse_model_spec() click to toggle source
# File lib/ez/domain_modeler.rb, line 167
def parse_model_spec
  @spec ||= {}
  @spec.each do |model, columns|

    msg = nil

    if !columns.is_a?(Hash)
      msg = "Could not understand models.yml while parsing model '#{model}'."
    end

    if model !~ /^[A-Z]/ || model =~ /\s/
      msg = "Could not understand models.yml while parsing model '#{model}'."
      msg <<  " Models must begin with an uppercase letter and cannot contain spaces."
    end

    raise msg if msg

    if EZ::Config.timestamps?
      columns['created_at'] = 'datetime'
      columns['updated_at'] = 'datetime'
    end

    columns.each do |column_name, column_type|
      interpret_column_spec column_name, column_type, model
    end
  end

end
update_tables(silent = false, dry_run = false) click to toggle source
# File lib/ez/domain_modeler.rb, line 128
def update_tables(silent = false, dry_run = false)
  return false unless @ok

  SchemaModifier.migrate(@spec, silent, dry_run)
  return true

  rescue => e
    puts e.message unless silent
    puts e.backtrace.first unless silent
    @ok = false
    return false
end