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