class ActiveFacts::Generators::Rails::Models

Generate Rails models for the vocabulary Invoke as

afgen --rails/models[=options] <file>.cql

Constants

Public Class Methods

new(vocabulary, *options) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 29
def initialize(vocabulary, *options)
  @vocabulary = vocabulary
  @vocabulary = @vocabulary.Vocabulary.values[0] if ActiveFacts::API::Constellation === @vocabulary
  help if options.include? "help"
  options.delete_if { |option| @output = $1 if option =~ /^output=(.*)/ }
  @concern = nil
  options.delete_if { |option| @concern = $1 if option =~ /^concern=(.*)/ }
  @validations = true
  options.delete_if { |option| @validations = eval($1) if option =~ /^validation=(.*)/ }
end

Public Instance Methods

column_constraints(table) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 178
def column_constraints table
  return [] unless @validations
  ccs =
    table.columns.map do |column|
      name = column.rails_name
      column.is_mandatory &&
        !column.is_auto_assigned && !column.is_auto_timestamp ? [
        "    validates :#{name}, :presence => true"
      ] : []
    end.flatten
  ccs.unshift("") unless ccs.empty?
  ccs
end
create_if_ok(filename) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 105
def create_if_ok filename
  # Create a file in the output directory, being careful not to overwrite carelessly
  if @output
    pathname = (@output+'/'+filename).gsub(%r{//+}, '/')
    @preexisting_files.reject!{|f| f == pathname }    # Don't clean up this file
    if generated_file_exists(pathname) == false
      $stderr.puts "not overwriting non-generated file #{pathname}"
      @individual_file = nil
      return
    end
    @individual_file = @out = File.open(pathname, 'w')
    puts "#{HEADER}"
  end
  true
end
delete_old_generated_files() click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 78
def delete_old_generated_files
  remaining = []
  cleaned = 0
  @preexisting_files.each do |pathname|
    if generated_file_exists(pathname) == true
      File.unlink(pathname) 
      cleaned += 1
    else
      remaining << pathname
    end
  end
  $stderr.puts "Cleaned up #{cleaned} old generated files" if @preexisting_files.size > 0
  $stderr.puts "Remaining non-generated files:\n\t#{remaining*"\n\t"}" if remaining.size > 0
end
from_associations(table) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 142
def from_associations table
  # has_one/has_many Associations
  table.foreign_keys_to.sort_by{|fk| fk.describe}.map do |fk|
    # Get the jump reference

    if fk.from_columns.size > 1
      raise "Can't emit Rails associations for multi-part foreign key with #{fk.references.inspect}. Did you mean to use --transform/surrogate"
    end

    association_type, association_name = *fk.rails_to_association

    ref = fk.jump_reference
    [
      "\n    \# #{fk.verbalised_path(true)}" +
      "\n" +
        %Q{    #{association_type} :#{association_name}} +
        %Q{, :class_name => '#{fk.from.rails_class_name}'} +
        %Q{, :foreign_key => :#{fk.from_columns[0].rails_name}} +
        %Q{, :dependent => :destroy}
    ] +
      # If ref.from is a join table, we can emit a has_many :through for each other key
      # REVISIT Could alternately do this for all belongs_to's in ref.from
      if ref.from.identifier_columns.length > 1
        ref.from.identifier_columns.map do |ic|
          next nil if ic.references[0] == ref or        # Skip the back-reference
            ic.references[0].is_unary           # or use rails_plural_name(ic.references[0].to_names) ?
          # This far association name needs to be augmented for its role name
          far_association_name = ic.references[0].to.rails_name
          %Q{    has_many :#{far_association_name}, :through => :#{association_name}} # \# via #{ic.name}}
        end
      else
        []
      end
  end.flatten.compact
end
generate_table(table) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 212
def generate_table table
  old_out = @out
  filename = table.rails_singular_name+'.rb'

  return unless create_if_ok filename

  puts "\n"
  puts "module #{@concern}" if @concern
  puts model_body(table).gsub(/^./, @concern ? '  \0' : '\0')
  puts 'end' if @concern

  true    # We succeeded
ensure
  @out = old_out
  @individual_file.close if @individual_file
end
generated_file_exists(pathname) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 93
def generated_file_exists pathname
  File.open(pathname, 'r') do |existing|
    first_lines = existing.read(1024)     # Make it possible to pass over a magic charset comment
    if first_lines.length == 0 or first_lines =~ %r{^#{HEADER}}
      return true
    end
  end
  return false    # File exists, but is not generated
rescue Errno::ENOENT
  return nil      # File does not exist
end
list_extant_files() click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 74
def list_extant_files
  @preexisting_files = Dir[@output+'/*.rb']
end
model_body(table) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 192
        def model_body table
          %Q{module #{table.rails_class_name}
  extend ActiveSupport::Concern
  included do} +
            (table.identifier_columns.length == 1 ? %Q{
    self.primary_key = '#{table.identifier_columns[0].rails_name}'
} : ''
            ) +

            (
              to_associations(table) +
              from_associations(table) +
              column_constraints(table)
            ) * "\n" +
            %Q{
  end
end
}
        end
to_associations(table) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 121
    def to_associations table
      # belongs_to Associations
      table.foreign_keys.map do |fk|
        association_name = fk.rails_from_association_name

        if association_name != fk.to.rails_singular_name
          # A different class_name is implied, emit an explicit one:
          class_name = ", :class_name => '#{fk.to.rails_class_name}'"
        end
        foreign_key = ", :foreign_key => :#{fk.from_columns[0].rails_name}"
        if foreign_key == fk.to.rails_singular_name+'_id'
          # See lib/active_record/reflection.rb, method #derive_foreign_key
          foreign_key = ''
        end

        %Q{
\# #{fk.verbalised_path}
belongs_to :#{association_name}#{class_name}#{foreign_key}}
      end
    end

Private Instance Methods

help() click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 40
        def help
          @helping = true
          warn %Q{Options for --rails/schema:
        output=dir              Overwrite model files into this output directory
        concern=name            Namespace for the concerns
        validation=false        Disable generation of validations
}
        end
puts(s) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 53
def puts s
  @out.puts s
end
warn(*a) click to toggle source
# File lib/activefacts/generators/rails/models.rb, line 49
def warn *a
  $stderr.puts *a
end