declare_schema 1.4.0.colin.7 → 1.4.0.colin.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -1
- data/lib/declare_schema/model/field_spec.rb +2 -0
- data/lib/declare_schema/model/foreign_key_definition.rb +39 -43
- data/lib/declare_schema/model/habtm_model_shim.rb +18 -25
- data/lib/declare_schema/model/index_definition.rb +43 -30
- data/lib/declare_schema/model/table_options_definition.rb +11 -1
- data/lib/declare_schema/model.rb +48 -36
- data/lib/declare_schema/version.rb +1 -1
- data/lib/declare_schema.rb +34 -2
- data/lib/generators/declare_schema/migration/migrator.rb +16 -10
- data/spec/lib/declare_schema/field_spec_spec.rb +22 -2
- data/spec/lib/declare_schema/migration_generator_spec.rb +17 -35
- data/spec/lib/declare_schema/model/foreign_key_definition_spec.rb +28 -27
- data/spec/lib/declare_schema/model/habtm_model_shim_spec.rb +54 -57
- data/spec/lib/declare_schema/model/index_definition_spec.rb +60 -53
- data/spec/lib/declare_schema/model/table_options_definition_spec.rb +26 -6
- data/spec/lib/declare_schema_spec.rb +62 -8
- data/spec/lib/generators/declare_schema/migration/migrator_spec.rb +3 -1
- data/spec/spec_helper.rb +4 -3
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: ca0ed75221c664e2a5c59bff495a971495922f7f31ea0fb548fb67f0ad0eb639
         | 
| 4 | 
            +
              data.tar.gz: fc76669be291fff9da61353f7f040ba14327e1359f712a963e5865ed948034c8
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 47b53d7e5306a3b2ada9296494af897738056c1c5e14e35e0b3967daf3176e07cf48c74560e2ccd659d0c1b8bc466e6d29f632f86b25bff568bcc4b6dcf672af
         | 
| 7 | 
            +
              data.tar.gz: 9af422633cae00b32e94bf64d0e6e9445aee83849e4898633384af679b61c877a22ce14f1b20c225d17f4a1546ee3c3a1867e0661516f186717895a658d4b2eb
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -10,6 +10,11 @@ Note: this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0 | |
| 10 10 | 
             
            ### Changed
         | 
| 11 11 | 
             
            - Deprecate index: 'name' and unique: true|false in favor of index: { name: 'name', unique: true|false }.
         | 
| 12 12 |  | 
| 13 | 
            +
            ## [1.3.3] - 2023-01-17
         | 
| 14 | 
            +
            ### Fixed
         | 
| 15 | 
            +
            - Fix a MySQL 8 bug where MySQL 8+ renames charset 'utf8' to 'utf8mb3' and collation 'utf8_general_ci' to
         | 
| 16 | 
            +
              'utf8mb3_unicode_ci'.
         | 
| 17 | 
            +
             | 
| 13 18 | 
             
            ## [1.3.2] - 2024-01-12
         | 
| 14 19 | 
             
            ### Fixed
         | 
| 15 20 | 
             
            - Fix bug in migrator when table option definitions differ
         | 
    
        data/Gemfile.lock
    CHANGED
    
    
    
        data/README.md
    CHANGED
    
    | @@ -176,7 +176,7 @@ DeclareSchema.clear_default_schema | |
| 176 176 | 
             
            ```
         | 
| 177 177 |  | 
| 178 178 | 
             
            ### Global Configuration
         | 
| 179 | 
            -
            Configurations can be set  | 
| 179 | 
            +
            Configurations can be set globally to customize default declaration for the following values:
         | 
| 180 180 |  | 
| 181 181 | 
             
            #### Text Limit
         | 
| 182 182 | 
             
            The default text limit can be set using the `DeclareSchema.default_text_limit=` method.
         | 
| @@ -256,6 +256,9 @@ turn all tables into `utf8mb4` supporting tables: | |
| 256 256 | 
             
            DeclareSchema.default_charset   = "utf8mb4"
         | 
| 257 257 | 
             
            DeclareSchema.default_collation = "utf8mb4_bin"
         | 
| 258 258 | 
             
            ```
         | 
| 259 | 
            +
            Note: MySQL 8+ aliases charset 'utf8' to 'utf8mb3', and 'utf8_general_ci' to 'utf8mb3_unicode_ci',
         | 
| 260 | 
            +
            so when running on MySQL 8+, those aliases will be applied by `DeclareSchema`.
         | 
| 261 | 
            +
             | 
| 259 262 | 
             
            #### db:migrate Command
         | 
| 260 263 | 
             
            `declare_schema` can run the migration once it is generated, if the `--migrate` option is passed.
         | 
| 261 264 | 
             
            If not, it will display the command to run later. By default this command is
         | 
| @@ -107,7 +107,9 @@ module DeclareSchema | |
| 107 107 | 
             
                    if @type.in?([:text, :string])
         | 
| 108 108 | 
             
                      if ActiveRecord::Base.connection.class.name.match?(/mysql/i)
         | 
| 109 109 | 
             
                        @options[:charset]   ||= model._table_options&.[](:charset)   || ::DeclareSchema.default_charset
         | 
| 110 | 
            +
                        @options[:charset] = DeclareSchema.normalize_charset(@options[:charset])
         | 
| 110 111 | 
             
                        @options[:collation] ||= model._table_options&.[](:collation) || ::DeclareSchema.default_collation
         | 
| 112 | 
            +
                        @options[:collation] = DeclareSchema.normalize_collation(@options[:collation])
         | 
| 111 113 | 
             
                      else
         | 
| 112 114 | 
             
                        @options.delete(:charset)
         | 
| 113 115 | 
             
                        @options.delete(:collation)
         | 
| @@ -7,53 +7,61 @@ module DeclareSchema | |
| 7 7 | 
             
                class ForeignKeyDefinition
         | 
| 8 8 | 
             
                  include Comparable
         | 
| 9 9 |  | 
| 10 | 
            -
                  attr_reader : | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
                   | 
| 14 | 
            -
             | 
| 15 | 
            -
                    @ | 
| 16 | 
            -
                    @ | 
| 17 | 
            -
             | 
| 18 | 
            -
                    @child_table_name = model.table_name # unless a table rename, which would happen when a class is renamed??
         | 
| 19 | 
            -
                    @parent_table_name = options[:parent_table]&.to_s
         | 
| 20 | 
            -
                    @foreign_key_name = options[:foreign_key]&.to_s || @foreign_key
         | 
| 21 | 
            -
             | 
| 10 | 
            +
                  attr_reader :foreign_key_column, :constraint_name, :child_table_name, :parent_class_name, :dependent
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # Caller needs to pass either constraint_name or child_table_name, and
         | 
| 13 | 
            +
                  # either parent_class_name or parent_table_name.
         | 
| 14 | 
            +
                  def initialize(foreign_key_column, constraint_name: nil, child_table_name: nil, parent_class_name: nil, parent_table_name: nil, dependent: nil)
         | 
| 15 | 
            +
                    @foreign_key_column = foreign_key_column&.to_s or raise ArgumentError "foreign key must not be empty: #{foreign_key_column.inspect}"
         | 
| 16 | 
            +
                    @constraint_name = constraint_name&.to_s.presence || ::DeclareSchema::Model::IndexDefinition.default_index_name(child_table_name, [@foreign_key_column])
         | 
| 17 | 
            +
                    @child_table_name = child_table_name&.to_s or raise ArgumentError, "child_table_name must not be nil"
         | 
| 22 18 | 
             
                    @parent_class_name =
         | 
| 23 | 
            -
                      case  | 
| 19 | 
            +
                      case parent_class_name
         | 
| 24 20 | 
             
                      when String, Symbol
         | 
| 25 | 
            -
                         | 
| 21 | 
            +
                        parent_class_name.to_s
         | 
| 26 22 | 
             
                      when Class
         | 
| 27 | 
            -
                        @parent_class =  | 
| 23 | 
            +
                        @parent_class = parent_class_name
         | 
| 28 24 | 
             
                        @parent_class.name
         | 
| 29 25 | 
             
                      when nil
         | 
| 30 | 
            -
                        @ | 
| 26 | 
            +
                        @foreign_key_column.sub(/_id\z/, '').camelize
         | 
| 31 27 | 
             
                      end
         | 
| 32 | 
            -
             | 
| 33 | 
            -
                     | 
| 34 | 
            -
             | 
| 35 | 
            -
                    @on_delete_cascade = options[:dependent] == :delete
         | 
| 28 | 
            +
                    @parent_table_name = parent_table_name
         | 
| 29 | 
            +
                    dependent.in?([nil, :delete]) or raise ArgumentError, "dependent: must be nil or :delete"
         | 
| 30 | 
            +
                    @dependent = dependent
         | 
| 36 31 | 
             
                  end
         | 
| 37 32 |  | 
| 38 33 | 
             
                  class << self
         | 
| 39 | 
            -
                    def  | 
| 40 | 
            -
                      show_create_table =  | 
| 34 | 
            +
                    def for_table(child_table_name, connection, dependent: nil)
         | 
| 35 | 
            +
                      show_create_table = connection.select_rows("show create table #{connection.quote_table_name(child_table_name)}").first.last
         | 
| 41 36 | 
             
                      constraints = show_create_table.split("\n").map { |line| line.strip if line['CONSTRAINT'] }.compact
         | 
| 42 37 |  | 
| 43 38 | 
             
                      constraints.map do |fkc|
         | 
| 44 | 
            -
                         | 
| 45 | 
            -
                         | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
                        new(model, foreign_key, **options)
         | 
| 39 | 
            +
                        constraint_name, foreign_key_column, parent_table_name = fkc.match(/CONSTRAINT `([^`]*)` FOREIGN KEY \(`([^`]*)`\) REFERENCES `([^`]*)`/).captures
         | 
| 40 | 
            +
                        dependent_value = :delete if dependent || fkc['ON DELETE CASCADE']
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                        new(foreign_key_column,
         | 
| 43 | 
            +
                            constraint_name: constraint_name,
         | 
| 44 | 
            +
                            child_table_name: child_table_name,
         | 
| 45 | 
            +
                            parent_table_name: parent_table_name,
         | 
| 46 | 
            +
                            dependent: dependent_value)
         | 
| 53 47 | 
             
                      end
         | 
| 54 48 | 
             
                    end
         | 
| 55 49 | 
             
                  end
         | 
| 56 50 |  | 
| 51 | 
            +
                  def key
         | 
| 52 | 
            +
                    @key ||= [@child_table_name, @foreign_key_column, @dependent].freeze
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  def <=>(rhs)
         | 
| 56 | 
            +
                    key <=> rhs.key
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                  alias eql? ==
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def equivalent?(rhs)
         | 
| 62 | 
            +
                    self == rhs
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 57 65 | 
             
                  # returns the parent class as a Class object
         | 
| 58 66 | 
             
                  # lazy loaded so that we don't require the parent class until we need it
         | 
| 59 67 | 
             
                  def parent_class
         | 
| @@ -64,21 +72,9 @@ module DeclareSchema | |
| 64 72 | 
             
                    @parent_table_name ||= parent_class.table_name
         | 
| 65 73 | 
             
                  end
         | 
| 66 74 |  | 
| 67 | 
            -
                  def <=>(rhs)
         | 
| 68 | 
            -
                    key <=> rhs.send(:key)
         | 
| 69 | 
            -
                  end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                  alias eql? ==
         | 
| 72 | 
            -
             | 
| 73 75 | 
             
                  def hash
         | 
| 74 76 | 
             
                    key.hash
         | 
| 75 77 | 
             
                  end
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                  private
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                  def key
         | 
| 80 | 
            -
                    @key ||= [@child_table_name, @parent_class_name, @foreign_key_name, @on_delete_cascade].map(&:to_s)
         | 
| 81 | 
            -
                  end
         | 
| 82 78 | 
             
                end
         | 
| 83 79 | 
             
              end
         | 
| 84 80 | 
             
            end
         | 
| @@ -5,28 +5,21 @@ module DeclareSchema | |
| 5 5 | 
             
                class HabtmModelShim
         | 
| 6 6 | 
             
                  class << self
         | 
| 7 7 | 
             
                    def from_reflection(refl)
         | 
| 8 | 
            -
                      join_table  | 
| 9 | 
            -
             | 
| 10 | 
            -
                        [refl.foreign_key.to_s, refl.active_record],
         | 
| 11 | 
            -
                        [refl.association_foreign_key.to_s, refl.class_name.constantize]
         | 
| 12 | 
            -
                      ].sort { |a, b| a.first <=> b.first }
         | 
| 13 | 
            -
                      foreign_keys = foreign_keys_and_classes.map(&:first)
         | 
| 14 | 
            -
                      foreign_key_classes = foreign_keys_and_classes.map(&:last)
         | 
| 15 | 
            -
                      # this may fail in weird ways if HABTM is running across two DB connections (assuming that's even supported)
         | 
| 16 | 
            -
                      # figure that anybody who sets THAT up can deal with their own migrations...
         | 
| 17 | 
            -
                      connection = refl.active_record.connection
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                      new(join_table, foreign_keys, foreign_key_classes, connection)
         | 
| 8 | 
            +
                      new(refl.join_table, [refl.foreign_key, refl.association_foreign_key],
         | 
| 9 | 
            +
                                           [refl.active_record.table_name, refl.class_name.constantize.table_name])
         | 
| 20 10 | 
             
                    end
         | 
| 21 11 | 
             
                  end
         | 
| 22 12 |  | 
| 23 | 
            -
                  attr_reader :join_table, :foreign_keys, : | 
| 13 | 
            +
                  attr_reader :join_table, :foreign_keys, :parent_table_names
         | 
| 24 14 |  | 
| 25 | 
            -
                  def initialize(join_table, foreign_keys,  | 
| 15 | 
            +
                  def initialize(join_table, foreign_keys, parent_table_names)
         | 
| 16 | 
            +
                    foreign_keys.is_a?(Array) && foreign_keys.size == 2 or
         | 
| 17 | 
            +
                      raise ArgumentError, "foreign_keys must be <Array[2]>; got #{foreign_keys.inspect}"
         | 
| 18 | 
            +
                    parent_table_names.is_a?(Array) && parent_table_names.size == 2 or
         | 
| 19 | 
            +
                      raise ArgumentError, "parent_table_names must be <Array[2]>; got #{parent_table_names.inspect}"
         | 
| 26 20 | 
             
                    @join_table = join_table
         | 
| 27 | 
            -
                    @foreign_keys = foreign_keys
         | 
| 28 | 
            -
                    @ | 
| 29 | 
            -
                    @connection = connection
         | 
| 21 | 
            +
                    @foreign_keys = foreign_keys.sort # Rails requires these be in alphabetical order
         | 
| 22 | 
            +
                    @parent_table_names = @foreign_keys == foreign_keys ? parent_table_names : parent_table_names.reverse # match the above sort
         | 
| 30 23 | 
             
                  end
         | 
| 31 24 |  | 
| 32 25 | 
             
                  def _table_options
         | 
| @@ -38,8 +31,8 @@ module DeclareSchema | |
| 38 31 | 
             
                  end
         | 
| 39 32 |  | 
| 40 33 | 
             
                  def field_specs
         | 
| 41 | 
            -
                    foreign_keys.each_with_index.each_with_object({}) do |( | 
| 42 | 
            -
                      result[ | 
| 34 | 
            +
                    foreign_keys.each_with_index.each_with_object({}) do |(foreign_key, i), result|
         | 
| 35 | 
            +
                      result[foreign_key] = ::DeclareSchema::Model::FieldSpec.new(self, foreign_key, :bigint, position: i, null: false)
         | 
| 43 36 | 
             
                    end
         | 
| 44 37 | 
             
                  end
         | 
| 45 38 |  | 
| @@ -53,8 +46,8 @@ module DeclareSchema | |
| 53 46 |  | 
| 54 47 | 
             
                  def index_definitions_with_primary_key
         | 
| 55 48 | 
             
                    @index_definitions_with_primary_key ||= Set.new([
         | 
| 56 | 
            -
                      IndexDefinition.new( | 
| 57 | 
            -
                      IndexDefinition.new( | 
| 49 | 
            +
                      IndexDefinition.new(foreign_keys, name: Model::IndexDefinition::PRIMARY_KEY_NAME, table_name: table_name, unique: true), # creates a primary composite key on both foreign keys
         | 
| 50 | 
            +
                      IndexDefinition.new(foreign_keys.last,                                            table_name: table_name, unique: false) # index for queries where we only have the last foreign key
         | 
| 58 51 | 
             
                    ])
         | 
| 59 52 | 
             
                  end
         | 
| 60 53 |  | 
| @@ -64,10 +57,10 @@ module DeclareSchema | |
| 64 57 | 
             
                    @ignore_indexes ||= Set.new
         | 
| 65 58 | 
             
                  end
         | 
| 66 59 |  | 
| 67 | 
            -
                  def  | 
| 68 | 
            -
                    @ | 
| 69 | 
            -
                      ForeignKeyDefinition.new( | 
| 70 | 
            -
                      ForeignKeyDefinition.new( | 
| 60 | 
            +
                  def constraint_definitions
         | 
| 61 | 
            +
                    @constraint_definitions ||= Set.new([
         | 
| 62 | 
            +
                      ForeignKeyDefinition.new(foreign_keys.first, constraint_name: "#{join_table}_FK1", child_table_name: @join_table, parent_table_name: parent_table_names.first, dependent: :delete),
         | 
| 63 | 
            +
                      ForeignKeyDefinition.new(foreign_keys.last, constraint_name: "#{join_table}_FK2", child_table_name: @join_table, parent_table_name: parent_table_names.last, dependent: :delete)
         | 
| 71 64 | 
             
                    ])
         | 
| 72 65 | 
             
                  end
         | 
| 73 66 | 
             
                end
         | 
| @@ -7,65 +7,78 @@ module DeclareSchema | |
| 7 7 | 
             
                class IndexDefinition
         | 
| 8 8 | 
             
                  include Comparable
         | 
| 9 9 |  | 
| 10 | 
            -
                  # TODO: replace `fields` with `columns` and remove alias. -Colin
         | 
| 11 10 | 
             
                  OPTIONS = [:name, :unique, :where, :length].freeze
         | 
| 12 | 
            -
                  attr_reader : | 
| 13 | 
            -
             | 
| 11 | 
            +
                  attr_reader :columns, :explicit_name, :table_name, *OPTIONS
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  alias fields columns # TODO: change callers to use columns. -Colin
         | 
| 14 14 |  | 
| 15 15 | 
             
                  class IndexNameTooLongError < RuntimeError; end
         | 
| 16 16 |  | 
| 17 17 | 
             
                  PRIMARY_KEY_NAME = "PRIMARY"
         | 
| 18 18 |  | 
| 19 | 
            -
                   | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
                    @ | 
| 23 | 
            -
                    @ | 
| 24 | 
            -
                    @name  | 
| 25 | 
            -
                    @ | 
| 26 | 
            -
                    @ | 
| 19 | 
            +
                  # Caller needs to pass either name or table_name. The table_name is not remembered; it is just used to compute the
         | 
| 20 | 
            +
                  # default name if no name is given.
         | 
| 21 | 
            +
                  def initialize(columns, table_name:, name: nil, allow_equivalent: false, unique: false, where: nil, length: nil)
         | 
| 22 | 
            +
                    @table_name = table_name
         | 
| 23 | 
            +
                    @name = name || self.class.default_index_name(table_name, columns)
         | 
| 24 | 
            +
                    @name.to_s == 'index_adverts_on_Advert' and binding.pry
         | 
| 25 | 
            +
                    @columns = Array.wrap(columns).map(&:to_s)
         | 
| 26 | 
            +
                    @explicit_name = @name if !allow_equivalent
         | 
| 27 | 
            +
                    unique.in?([false, true]) or raise ArgumentError, "unique must be true or false: got #{unique.inspect}"
         | 
| 28 | 
            +
                    if @name == PRIMARY_KEY_NAME
         | 
| 29 | 
            +
                      unique or raise ArgumentError, "primary key index must be unique"
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                    @unique = unique
         | 
| 27 32 |  | 
| 28 33 | 
             
                    if DeclareSchema.max_index_and_constraint_name_length && @name.length > DeclareSchema.max_index_and_constraint_name_length
         | 
| 29 34 | 
             
                      raise IndexNameTooLongError, "Index '#{@name}' exceeds configured limit of #{DeclareSchema.max_index_and_constraint_name_length} characters. Give it a shorter name, or adjust DeclareSchema.max_index_and_constraint_name_length if you know your database can accept longer names."
         | 
| 30 35 | 
             
                    end
         | 
| 31 36 |  | 
| 32 | 
            -
                    if  | 
| 37 | 
            +
                    if where
         | 
| 33 38 | 
             
                      @where = where.start_with?('(') ? where : "(#{where})"
         | 
| 34 39 | 
             
                    end
         | 
| 35 40 |  | 
| 36 | 
            -
                     | 
| 41 | 
            +
                    @length = length
         | 
| 37 42 | 
             
                  end
         | 
| 38 43 |  | 
| 39 44 | 
             
                  class << self
         | 
| 40 45 | 
             
                    # extract IndexSpecs from an existing table
         | 
| 41 46 | 
             
                    # includes the PRIMARY KEY index
         | 
| 42 | 
            -
                    def  | 
| 43 | 
            -
                       | 
| 44 | 
            -
             | 
| 45 | 
            -
                      primary_key_columns = Array(model.connection.primary_key(t)).presence
         | 
| 46 | 
            -
                      primary_key_columns or raise "could not find primary key for table #{t} in #{model.connection.columns(t).inspect}"
         | 
| 47 | 
            +
                    def for_table(table_name, ignore_indexes, connection)
         | 
| 48 | 
            +
                      primary_key_columns = Array(connection.primary_key(table_name))
         | 
| 49 | 
            +
                      primary_key_columns.present? or raise "could not find primary key for table #{table_name} in #{connection.columns(table_name).inspect}"
         | 
| 47 50 |  | 
| 48 51 | 
             
                      primary_key_found = false
         | 
| 49 | 
            -
                      index_definitions =  | 
| 50 | 
            -
                         | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 52 | 
            +
                      index_definitions = connection.indexes(table_name).map do |index|
         | 
| 53 | 
            +
                        next if ignore_indexes.include?(index.name)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                        if index.name == PRIMARY_KEY_NAME
         | 
| 56 | 
            +
                          index.columns == primary_key_columns && index.unique or
         | 
| 57 | 
            +
                            raise "primary key on #{table_name} was not unique on #{primary_key_columns} (was unique=#{index.unique} on #{index.columns})"
         | 
| 54 58 | 
             
                          primary_key_found = true
         | 
| 55 59 | 
             
                        end
         | 
| 56 | 
            -
                         | 
| 60 | 
            +
                        length =
         | 
| 61 | 
            +
                          case lengths = index.lengths
         | 
| 62 | 
            +
                          when {}
         | 
| 63 | 
            +
                            nil
         | 
| 64 | 
            +
                          when Hash
         | 
| 65 | 
            +
                            lengths.size == 1 ? lengths.values.first : lengths
         | 
| 66 | 
            +
                          else
         | 
| 67 | 
            +
                            lengths
         | 
| 68 | 
            +
                          end
         | 
| 69 | 
            +
                        new(index.columns, name: index.name, table_name: table_name, unique: index.unique, where: index.where, length: length)
         | 
| 57 70 | 
             
                      end.compact
         | 
| 58 71 |  | 
| 59 72 | 
             
                      if !primary_key_found
         | 
| 60 | 
            -
                        index_definitions << new( | 
| 73 | 
            +
                        index_definitions << new(primary_key_columns, name: PRIMARY_KEY_NAME, table_name: table_name, unique: true)
         | 
| 61 74 | 
             
                      end
         | 
| 62 75 | 
             
                      index_definitions
         | 
| 63 76 | 
             
                    end
         | 
| 64 77 |  | 
| 65 | 
            -
                    def default_index_name( | 
| 78 | 
            +
                    def default_index_name(table_name, columns)
         | 
| 66 79 | 
             
                      index_name = nil
         | 
| 67 80 | 
             
                      [:long_index_name, :short_index_name].find do |method_name|
         | 
| 68 | 
            -
                        index_name = send(method_name,  | 
| 81 | 
            +
                        index_name = send(method_name, table_name, columns)
         | 
| 69 82 | 
             
                        if DeclareSchema.max_index_and_constraint_name_length.nil? || index_name.length <= DeclareSchema.max_index_and_constraint_name_length
         | 
| 70 83 | 
             
                          break index_name
         | 
| 71 84 | 
             
                        end
         | 
| @@ -115,12 +128,12 @@ module DeclareSchema | |
| 115 128 |  | 
| 116 129 | 
             
                  # Unique key for this object. Used for equality checking.
         | 
| 117 130 | 
             
                  def to_key
         | 
| 118 | 
            -
                    @ | 
| 131 | 
            +
                    @to_key ||= [name, *settings].freeze
         | 
| 119 132 | 
             
                  end
         | 
| 120 133 |  | 
| 121 134 | 
             
                  # The index settings for this object. Used for equivalence checking. Does not include the name.
         | 
| 122 135 | 
             
                  def settings
         | 
| 123 | 
            -
                    @settings ||= [ | 
| 136 | 
            +
                    @settings ||= [columns, options.except(:name)].freeze
         | 
| 124 137 | 
             
                  end
         | 
| 125 138 |  | 
| 126 139 | 
             
                  def hash
         | 
| @@ -136,7 +149,7 @@ module DeclareSchema | |
| 136 149 | 
             
                  end
         | 
| 137 150 |  | 
| 138 151 | 
             
                  def with_name(new_name)
         | 
| 139 | 
            -
                    self.class.new(@ | 
| 152 | 
            +
                    self.class.new(@columns, name: new_name, table_name: @table_name, unique: @unique, allow_equivalent: @explicit_name.nil?, where: @where, length: @length)
         | 
| 140 153 | 
             
                  end
         | 
| 141 154 |  | 
| 142 155 | 
             
                  alias eql? ==
         | 
| @@ -50,7 +50,17 @@ module DeclareSchema | |
| 50 50 |  | 
| 51 51 | 
             
                  def initialize(table_name, **table_options)
         | 
| 52 52 | 
             
                    @table_name    = table_name
         | 
| 53 | 
            -
                    @table_options = table_options
         | 
| 53 | 
            +
                    @table_options = table_options.each_with_object({}) do |(k, v),result|
         | 
| 54 | 
            +
                      result[k] =
         | 
| 55 | 
            +
                        case k
         | 
| 56 | 
            +
                        when :charset
         | 
| 57 | 
            +
                          DeclareSchema.normalize_charset(v)
         | 
| 58 | 
            +
                        when :collation
         | 
| 59 | 
            +
                          DeclareSchema.normalize_collation(v)
         | 
| 60 | 
            +
                        else
         | 
| 61 | 
            +
                          v
         | 
| 62 | 
            +
                        end
         | 
| 63 | 
            +
                    end
         | 
| 54 64 | 
             
                  end
         | 
| 55 65 |  | 
| 56 66 | 
             
                  def to_key
         | 
    
        data/lib/declare_schema/model.rb
    CHANGED
    
    | @@ -28,7 +28,7 @@ module DeclareSchema | |
| 28 28 | 
             
                      # index_definitions holds IndexDefinition objects for all the declared indexes.
         | 
| 29 29 | 
             
                      inheriting_cattr_reader index_definitions: Set.new
         | 
| 30 30 | 
             
                      inheriting_cattr_reader ignore_indexes: Set.new
         | 
| 31 | 
            -
                      inheriting_cattr_reader  | 
| 31 | 
            +
                      inheriting_cattr_reader constraint_definitions: Set.new
         | 
| 32 32 |  | 
| 33 33 | 
             
                      # table_options holds optional configuration for the create_table statement
         | 
| 34 34 | 
             
                      # supported options include :charset and :collation
         | 
| @@ -49,16 +49,23 @@ module DeclareSchema | |
| 49 49 | 
             
                end
         | 
| 50 50 |  | 
| 51 51 | 
             
                module ClassMethods
         | 
| 52 | 
            -
                  def index( | 
| 53 | 
            -
                    index_definitions << ::DeclareSchema::Model::IndexDefinition.new( | 
| 52 | 
            +
                  def index(columns, name: nil, allow_equivalent: false, unique: false, where: nil, length: nil)
         | 
| 53 | 
            +
                    index_definitions << ::DeclareSchema::Model::IndexDefinition.new(
         | 
| 54 | 
            +
                      columns,
         | 
| 55 | 
            +
                      name: name, table_name: table_name, allow_equivalent: allow_equivalent, unique: unique, where: where, length: length
         | 
| 56 | 
            +
                    )
         | 
| 54 57 | 
             
                  end
         | 
| 55 58 |  | 
| 56 | 
            -
                  def primary_key_index(* | 
| 57 | 
            -
                    index( | 
| 59 | 
            +
                  def primary_key_index(*columns)
         | 
| 60 | 
            +
                    index(columns.flatten, unique: true, name: ::DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME)
         | 
| 58 61 | 
             
                  end
         | 
| 59 62 |  | 
| 60 | 
            -
                  def constraint( | 
| 61 | 
            -
                     | 
| 63 | 
            +
                  def constraint(foreign_key_column, parent_table_name: nil, constraint_name: nil, parent_class_name: nil, dependent: nil)
         | 
| 64 | 
            +
                    constraint_definitions << ::DeclareSchema::Model::ForeignKeyDefinition.new(
         | 
| 65 | 
            +
                      foreign_key_column.to_s,
         | 
| 66 | 
            +
                      constraint_name: constraint_name,
         | 
| 67 | 
            +
                      child_table_name: table_name, parent_table_name: parent_table_name, parent_class_name: parent_class_name, dependent: dependent
         | 
| 68 | 
            +
                    )
         | 
| 62 69 | 
             
                  end
         | 
| 63 70 |  | 
| 64 71 | 
             
                  # tell the migration generator to ignore the named index. Useful for existing indexes, or for indexes
         | 
| @@ -76,7 +83,7 @@ module DeclareSchema | |
| 76 83 | 
             
                    _add_serialize_for_field(name, type, options)
         | 
| 77 84 | 
             
                    _add_formatting_for_field(name, type)
         | 
| 78 85 | 
             
                    _add_validations_for_field(name, type, args, options)
         | 
| 79 | 
            -
                    _add_index_for_field(name, args, options)
         | 
| 86 | 
            +
                    _add_index_for_field(name, args, **options)
         | 
| 80 87 | 
             
                    field_specs[name] = ::DeclareSchema::Model::FieldSpec.new(self, name, type, position: field_specs.size, **options)
         | 
| 81 88 | 
             
                    attr_order << name unless attr_order.include?(name)
         | 
| 82 89 | 
             
                  end
         | 
| @@ -91,8 +98,8 @@ module DeclareSchema | |
| 91 98 |  | 
| 92 99 | 
             
                  # Extend belongs_to so that it
         | 
| 93 100 | 
             
                  # 1. creates a FieldSpec for the foreign key
         | 
| 94 | 
            -
                  # 2. declares an index on the foreign key
         | 
| 95 | 
            -
                  # 3. declares a foreign_key constraint
         | 
| 101 | 
            +
                  # 2. declares an index on the foreign key (optional)
         | 
| 102 | 
            +
                  # 3. declares a foreign_key constraint (optional)
         | 
| 96 103 | 
             
                  def belongs_to(name, scope = nil, **options)
         | 
| 97 104 | 
             
                    column_options = {}
         | 
| 98 105 |  | 
| @@ -112,14 +119,14 @@ module DeclareSchema | |
| 112 119 | 
             
                    # index: { ... } means create an index on the foreign key with the given options
         | 
| 113 120 | 
             
                    index_value = options.delete(:index)
         | 
| 114 121 | 
             
                    if index_value != false || options.has_key?(:unique) || options.has_key?(:allow_equivalent)
         | 
| 115 | 
            -
                      index_options = {}
         | 
| 122 | 
            +
                      index_options = {} # truthy iff we want an index
         | 
| 116 123 | 
             
                      case index_value
         | 
| 117 | 
            -
                      when String
         | 
| 124 | 
            +
                      when String, Symbol
         | 
| 118 125 | 
             
                        Kernel.warn("belongs_to index: 'name' is deprecated; use index: { name: 'name' } instead (in #{name})")
         | 
| 119 | 
            -
                        index_options[:name] = index_value
         | 
| 120 | 
            -
                      when true
         | 
| 126 | 
            +
                        index_options[:name] = index_value.to_s
         | 
| 121 127 | 
             
                      when false
         | 
| 122 128 | 
             
                        raise ArgumentError, "belongs_to index: false contradicts others options #{options.inspect} (in #{name})"
         | 
| 129 | 
            +
                      when true
         | 
| 123 130 | 
             
                      when nil
         | 
| 124 131 | 
             
                      when Hash
         | 
| 125 132 | 
             
                        index_options = index_value
         | 
| @@ -135,23 +142,20 @@ module DeclareSchema | |
| 135 142 | 
             
                      index_options[:allow_equivalent] = options.delete(:allow_equivalent) if options.has_key?(:allow_equivalent)
         | 
| 136 143 | 
             
                    end
         | 
| 137 144 |  | 
| 138 | 
            -
                     | 
| 139 | 
            -
                    fk_options[:constraint_name] = options.delete(:constraint) if options.has_key?(:constraint)
         | 
| 140 | 
            -
                    fk_options[:index_name] = index_options&.[](:name)
         | 
| 145 | 
            +
                    constraint_name = options.delete(:constraint)
         | 
| 141 146 |  | 
| 142 | 
            -
                     | 
| 147 | 
            +
                    dependent_delete = :delete if options.delete(:far_end_dependent) == :delete
         | 
| 143 148 |  | 
| 149 | 
            +
                    # infer :optional from :null
         | 
| 144 150 | 
             
                    if !options.has_key?(:optional)
         | 
| 145 | 
            -
                      options[:optional] = column_options[:null] | 
| 151 | 
            +
                      options[:optional] = column_options[:null]
         | 
| 146 152 | 
             
                    end
         | 
| 147 153 |  | 
| 148 | 
            -
                    fk_options[:dependent] = options.delete(:far_end_dependent) if options.has_key?(:far_end_dependent)
         | 
| 149 | 
            -
             | 
| 150 154 | 
             
                    super
         | 
| 151 155 |  | 
| 152 156 | 
             
                    reflection = reflections[name.to_s] or raise "Couldn't find reflection #{name} in #{reflections.keys}"
         | 
| 153 | 
            -
                     | 
| 154 | 
            -
                     | 
| 157 | 
            +
                    foreign_key_column = reflection.foreign_key or raise "Couldn't find foreign_key for #{name} in #{reflection.inspect}"
         | 
| 158 | 
            +
                    foreign_key_column_options = column_options.dup
         | 
| 155 159 |  | 
| 156 160 | 
             
                    # Note: the foreign key limit: should match the primary key limit:. (If there is a foreign key constraint,
         | 
| 157 161 | 
             
                    # those limits _must_ match.) We'd like to call _infer_fk_limit and get the limit right from the PK.
         | 
| @@ -162,27 +166,34 @@ module DeclareSchema | |
| 162 166 | 
             
                    # The one downside of this approach is that application code that asks the field_spec for the declared
         | 
| 163 167 | 
             
                    # foreign key limit: will always get 8 back even if this is a grandfathered foreign key that points to
         | 
| 164 168 | 
             
                    # a limit: 4 primary key. It seems unlikely that any application code would do this.
         | 
| 165 | 
            -
                     | 
| 166 | 
            -
                      if (inferred_limit = _infer_fk_limit( | 
| 169 | 
            +
                    foreign_key_column_options[:pre_migration] = ->(field_spec) do
         | 
| 170 | 
            +
                      if (inferred_limit = _infer_fk_limit(foreign_key_column, reflection))
         | 
| 167 171 | 
             
                        field_spec.sql_options[:limit] = inferred_limit
         | 
| 168 172 | 
             
                      end
         | 
| 169 173 | 
             
                    end
         | 
| 170 174 |  | 
| 171 | 
            -
                    declare_field( | 
| 175 | 
            +
                    declare_field(foreign_key_column.to_sym, :bigint, **foreign_key_column_options)
         | 
| 172 176 |  | 
| 173 177 | 
             
                    if reflection.options[:polymorphic]
         | 
| 174 178 | 
             
                      foreign_type = options[:foreign_type] || "#{name}_type"
         | 
| 175 179 | 
             
                      _declare_polymorphic_type_field(foreign_type, column_options)
         | 
| 176 | 
            -
                       | 
| 180 | 
            +
                      if ::DeclareSchema.default_generate_indexing && index_options
         | 
| 181 | 
            +
                        index([foreign_type, foreign_key_column], **index_options)
         | 
| 182 | 
            +
                      end
         | 
| 177 183 | 
             
                    else
         | 
| 178 | 
            -
                       | 
| 179 | 
            -
             | 
| 184 | 
            +
                      if ::DeclareSchema.default_generate_indexing && index_options
         | 
| 185 | 
            +
                        index([foreign_key_column], **index_options)
         | 
| 186 | 
            +
                      end
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                      if ::DeclareSchema.default_generate_foreign_keys && constraint_name != false
         | 
| 189 | 
            +
                        constraint(foreign_key_column, constraint_name: constraint_name || index_options&.[](:name), parent_class_name: reflection.klass, dependent: dependent_delete)
         | 
| 190 | 
            +
                      end
         | 
| 180 191 | 
             
                    end
         | 
| 181 192 | 
             
                  end
         | 
| 182 193 |  | 
| 183 | 
            -
                  def _infer_fk_limit( | 
| 194 | 
            +
                  def _infer_fk_limit(foreign_key_column, reflection)
         | 
| 184 195 | 
             
                    if reflection.options[:polymorphic]
         | 
| 185 | 
            -
                      if (foreign_key_column = columns_hash[ | 
| 196 | 
            +
                      if (foreign_key_column = columns_hash[foreign_key_column.to_s]) && foreign_key_column.type == :integer
         | 
| 186 197 | 
             
                        foreign_key_column.limit
         | 
| 187 198 | 
             
                      end
         | 
| 188 199 | 
             
                    else
         | 
| @@ -224,7 +235,7 @@ module DeclareSchema | |
| 224 235 | 
             
                  end
         | 
| 225 236 |  | 
| 226 237 | 
             
                  def _rails_default_primary_key
         | 
| 227 | 
            -
                    ::DeclareSchema::Model::IndexDefinition.new( | 
| 238 | 
            +
                    ::DeclareSchema::Model::IndexDefinition.new([_declared_primary_key], name: DeclareSchema::Model::IndexDefinition::PRIMARY_KEY_NAME, table_name: table_name, unique: true)
         | 
| 228 239 | 
             
                  end
         | 
| 229 240 |  | 
| 230 241 | 
             
                  # Declares the "foo_type" field that accompanies the "foo_id"
         | 
| @@ -295,15 +306,16 @@ module DeclareSchema | |
| 295 306 | 
             
                    end
         | 
| 296 307 | 
             
                  end
         | 
| 297 308 |  | 
| 298 | 
            -
                  def _add_index_for_field( | 
| 299 | 
            -
                    if ( | 
| 309 | 
            +
                  def _add_index_for_field(column_name, args, **options)
         | 
| 310 | 
            +
                    if (index_name = options.delete(:index))
         | 
| 300 311 | 
             
                      index_opts =
         | 
| 301 312 | 
             
                        {
         | 
| 302 313 | 
             
                          unique: args.include?(:unique) || !!options.delete(:unique)
         | 
| 303 314 | 
             
                        }
         | 
| 315 | 
            +
             | 
| 304 316 | 
             
                      # support index: true declaration
         | 
| 305 | 
            -
                      index_opts[:name] =  | 
| 306 | 
            -
                      index( | 
| 317 | 
            +
                      index_opts[:name] = index_name unless index_name == true
         | 
| 318 | 
            +
                      index([column_name], **index_opts)
         | 
| 307 319 | 
             
                    end
         | 
| 308 320 | 
             
                  end
         | 
| 309 321 |  |