lib/activefacts/generator/rails/schema.rb in activefacts-compositions-1.9.8 vs lib/activefacts/generator/rails/schema.rb in activefacts-compositions-1.9.9
- old
+ new
@@ -4,11 +4,10 @@
# Copyright (c) 2009-2016 Clifford Heath. Read the LICENSE file.
#
require 'digest/sha1'
require 'activefacts/metamodel'
require 'activefacts/metamodel/datatypes'
-require 'activefacts/registry'
require 'activefacts/compositions'
require 'activefacts/generator'
require 'activefacts/compositions/traits/rails'
module ActiveFacts
@@ -23,12 +22,13 @@
include_comments: ['Boolean', "Generate a comment for each column showing the absorption path"],
closed_world: ['Boolean', "Set this if your DBMS only allows one null in a unique index (MS SQL)"],
})
end
- def initialize composition, options = {}
- @composition = composition
+ def initialize compositions, options = {}
+ raise "--rails/schema only processes a single composition" if compositions.size > 1
+ @composition = compositions[0]
@options = options
@option_exclude_fks = options.delete("exclude_fks")
@option_include_comments = options.delete("include_comments")
@option_closed_world = options.delete("closed_world")
end
@@ -40,10 +40,11 @@
def data_type_context
@data_type_context ||= RailsDataTypeContext.new
end
def generate
+ @indexes_generated = {}
@foreign_keys = []
# If we get index names that need to be truncated, add a counter to ensure uniqueness
@dup_id = 0
tables =
@@ -103,47 +104,66 @@
warn "Warning: #{table.name} has a multi-part primary key" if pk.length > 1 and !is_join_table
create_table = %Q{ create_table "#{ar_table_name}", id: false, force: true do |t|}
columns = generate_columns composite
+ index_texts = []
+ composite.all_index.each do |index|
+ next if index.composite_as_primary_index && index.all_index_field.size == 1 # We've handled this already
+
+ index_column_names = index.all_index_field.map{|ixf| ixf.component.column_name.snakecase}
+ index_name = ACTR::name_trunc("index_#{ar_table_name}_on_#{index_column_names*'_'}")
+
+ index_texts << '' if index_texts.empty?
+
+ all_mandatory = index.all_index_field.to_a.all?{|ixf| ixf.component.path_mandatory}
+ @indexes_generated[index] = true
+ index_texts << %Q{ add_index "#{ar_table_name}", #{index_column_names.inspect}, name: :#{index_name}#{
+ # Avoid problems with closed-world uniqueness: only all_mandatory indices can be unique on closed-world index semantics (MS SQL)
+ index.is_unique && (!@option_closed_world || all_mandatory) ? ", unique: true" : ''
+ }}
+ end
+
unless @option_exclude_fks
composite.all_foreign_key_as_source_composite.each do |fk|
from_column_names = fk.all_foreign_key_field.map{|fxf| fxf.component.column_name.snakecase}
to_column_names = fk.all_index_field.map{|ixf| ixf.component.column_name.snakecase}
@foreign_keys.concat(
if (from_column_names.length == 1)
+
+ # See if the from_column already has an index (not necessarily unique, but the column must be first):
+ from_index_needed =
+ fk.
+ all_foreign_key_field.
+ single.
+ component. # See whether the foreign key component
+ all_index_field. # occurs in a unique index already
+ select do |ixf|
+ ixf.access_path.is_a?(Metamodel::Index) && # It's an Index, not an FK
+ ixf.ordinal == 0 # It's first in this index
+ end
+ already_indexed = from_index_needed.any?{|ixf| @indexes_generated[ixf.access_path]}
+ is_one_to_one = fk.absorption && fk.absorption.child_role.is_unique
+
index_name = ACTR::name_trunc("index_#{ar_table_name}_on_#{from_column_names[0]}")
[
" add_foreign_key :#{ar_table_name}, :#{fk.composite.mapping.rails.plural_name}, column: :#{from_column_names[0]}, primary_key: :#{to_column_names[0]}, on_delete: :cascade",
# Index it non-uniquely only if it's not unique already:
- fk.absorption && fk.absorption.child_role.is_unique ? nil :
+ if already_indexed or is_one_to_one # Either it already is, or it will be indexed, no new index needed
+ nil
+ else
" add_index :#{ar_table_name}, [:#{from_column_names[0]}], unique: false, name: :#{index_name}"
+ end
].compact
else
[ ]
end
)
end
end
- index_texts = []
- composite.all_index.each do |index|
- next if index.composite_as_primary_index && index.all_index_field.size == 1 # We've handled this already
-
- index_column_names = index.all_index_field.map{|ixf| ixf.component.column_name.snakecase}
- index_name = ACTR::name_trunc("index_#{ar_table_name}_on_#{index_column_names*'_'}")
-
- index_texts << '' if index_texts.empty?
-
- all_mandatory = index.all_index_field.to_a.all?{|ixf| ixf.component.path_mandatory}
- index_texts << %Q{ add_index "#{ar_table_name}", #{index_column_names.inspect}, name: :#{index_name}#{
- # Avoid problems with closed-world uniqueness: only all_mandatory indices can be unique on closed-world index semantics (MS SQL)
- index.is_unique && (!@option_closed_world || all_mandatory) ? ", unique: true" : ''
- }}
- end
-
[
create_table,
*columns,
" end",
*index_texts.sort
@@ -163,16 +183,22 @@
options ||= {}
length = options[:length]
value_constraint = options[:value_constraint]
type, type_name = *normalise_type(type_name)
- if a = options[:auto_assign]
+ if pkxf = component.all_index_field.detect{|ixf| (a = ixf.access_path).is_a?(MM::Index) && a.composite_as_primary_index }
+ auto_assign = options[:auto_assign]
case type_name
when 'integer'
- type_name = 'primary_key' if a != 'assert'
+ type_name = 'primary_key' if auto_assign
+ @indexes_generated[pkxf.access_path] = true
when 'uuid'
- type_name = "uuid, default: 'gen_random_uuid()', primary_key: true"
+ type_name = "uuid"
+ if auto_assign
+ type_name += ", default: 'gen_random_uuid()', primary_key: true"
+ @indexes_generated[pkxf.access_path] = true
+ end
end
end
valid_parameters = MM::DataType::TypeParameters[type]
length_ok = valid_parameters &&
@@ -248,18 +274,23 @@
case type
when MM::DataType::TYPE_Boolean; 'boolean'
when MM::DataType::TYPE_Integer; 'integer'
when MM::DataType::TYPE_Real; 'float'
when MM::DataType::TYPE_Decimal; 'decimal'
- when MM::DataType::TYPE_Money; 'datatime'
+ when MM::DataType::TYPE_Money; 'decimal'
when MM::DataType::TYPE_Char; 'string'
when MM::DataType::TYPE_String; 'string'
when MM::DataType::TYPE_Text; 'text'
when MM::DataType::TYPE_Date; 'datetime'
when MM::DataType::TYPE_Time; 'time'
when MM::DataType::TYPE_DateTime; 'datetime'
when MM::DataType::TYPE_Timestamp;'datetime'
- when MM::DataType::TYPE_Binary; 'binary'
+ when MM::DataType::TYPE_Binary;
+ if type_name =~ /^([Gu]uid|uniqueidentifier)$/i
+ 'uuid'
+ else
+ 'binary'
+ end
else
type_name
end
]
end