Class: ActiveFacts::Generate::Rails::SchemaRb

Inherits:
Object
  • Object
show all
Includes:
Persistence
Defined in:
lib/activefacts/generate/rails/schema.rb

Overview

Generate a Rails-friendly schema for the vocabulary Invoke as

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

Instance Method Summary collapse

Methods included from Persistence

rails_plural_name, rails_singular_name, rails_type

Instance Method Details

#generate(out = $>) ⇒ Object

:nodoc:



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/activefacts/generate/rails/schema.rb', line 48

def generate(out = $>)      #:nodoc:
  return if @helping
  @out = out

  foreign_keys = []

  # If we get index names that need to be truncated, add a counter to ensure uniqueness
  dup_id = 0

  puts "#\n# schema.rb auto-generated using ActiveFacts for #{@vocabulary.name} on #{Date.today}\n#\n\n"
  puts "ActiveRecord::Schema.define(:version => #{Time.now.strftime('%Y%m%d%H%M%S')}) do"

  @vocabulary.tables.each do |table|
    ar_table_name = table.rails_name

    pk = table.identifier_columns
    identity_column = pk[0] if pk[0].is_auto_assigned

    fk_refs = table.references_from.select{|ref| ref.is_simple_reference }
    fk_columns = table.columns.select do |column|
      column.references[0].is_simple_reference
    end

    # Detect if this table is a join table.
    # Join tables have multi-part primary keys that are made up only of foreign keys
    is_join_table = pk.length > 1 and
      !pk.detect do |pk_column|
	!fk_columns.include?(pk_column)
      end
    warn "Warning: #{table.name} has a multi-part primary key" if pk.length > 1 and !is_join_table

    needs_rails_id_field = (pk.length > 1) && !is_join_table
    move_pk_to_create_table_call = !needs_rails_id_field &&
	pk.length == 1 &&
	(to = pk[0].references[-1].to) &&
	to.supertypes_transitive.detect{|t| t.name == 'Auto Counter'}

    identity =
      if move_pk_to_create_table_call
	":primary_key => :#{pk[0].rails_name}"
      else
	":id => #{needs_rails_id_field}"
      end

    puts %Q{  create_table "#{ar_table_name}", #{identity}, :force => true do |t|}

    # We sort the columns here, not in the persistence layer, because it affects
    # the ordering of columns in an index :-(.

    columns = table.
	columns.
	sort_by do |column|
	  [ # Emit columns alphabetically, but PK first, then FKs, then others
	    case
	    when column == identity_column
	      0
	    when fk_columns.include?(column)
	      1
	    else
	      2
	    end,
	    column.rails_name
	  ]
	end.
	map do |column|
      next [] if move_pk_to_create_table_call and column == pk[0]
      name = column.rails_name
      type, params, constraints = column.type
      length = params[:length]
      length &&= length.to_i
      scale = params[:scale]
      scale &&= scale.to_i
      type, length = Persistence::rails_type(type, length)

      length_name = type == 'decimal' ? 'precision' : 'limit'

      primary = (!is_join_table && pk.include?(column)) ? ", :primary => true" : ''
      comment = column.comment
      (@include_comments ? ["    \# #{comment}"] : []) +
      [
	%Q{    t.#{type}\t"#{name}"#{
	    length ? ", :#{length_name} => #{length}" : ''
	  }#{
	    scale ? ", :scale => #{scale}" : ''
	  }#{
	    column.is_mandatory ? ', :null => false' : ''
	  }#{primary}}
      ]
    end.flatten

    unless @exclude_fks
      table.foreign_keys.each do |fk|
	from_columns = fk.from_columns.map{|column| column.rails_name}
	to_columns = fk.to_columns.map{|column| column.rails_name}
	foreign_keys <<
	  if (from_columns.length == 1)
	    "    add_foreign_key :#{fk.from.rails_name}, :#{fk.to.rails_name}, :column => :#{from_columns[0]}, :primary_key => :#{to_columns[0]}, :dependent => :cascade"
	  else
	    # This probably isn't going to work without Dr Nic's CPK gem:
	    "    add_foreign_key :#{fk.to.rails_name}, :#{fk.from.rails_name}, :column => [:#{from_columns.join(':, ')}], :primary_key => [:#{to_columns.join(':, ')}], :dependent => :cascade"
	  end
      end
    end

    indices = table.indices
    index_text = []
    indices.each do |index|
      next if move_pk_to_create_table_call and index.is_primary	  # We've handled this already

      index_name = index.rails_name

      unique = !index.columns.detect{|column| !column.is_mandatory} and !@closed_world
      index_text << %Q{  add_index "#{ar_table_name}", ["#{index.columns.map{|c| c.rails_name}*'", "'}"], :name => :#{index_name}#{
	unique ? ", :unique => true" : ''
      }}
    end

    puts columns.join("\n")
    puts "  end\n\n"

    puts index_text.join("\n")
    puts "\n" unless index_text.empty?
  end

  unless @exclude_fks
    puts '  unless ENV["EXCLUDE_FKS"]'
    puts foreign_keys.join("\n")
    puts '  end'
  end
  puts "end"
end