5.31.0.txt

doc/release_notes/5.31.0.txt
Last Update: 2020-04-01 08:28:09 -0700

New Features

  • A forbid_lazy_load plugin has been added to forbid the lazy loading of model associations if the current object was retreived with other objects. This plugin helps detect N+1 query issues. This plugin will raise an error if a lazy load is detected in such cases:

    Album.plugin :forbid_lazy_load
    Album.one_to_many :tracks
    
    Album.each do |album|
      album.tracks
      # Could be N+1, raises Sequel::Plugins::ForbidLazyLoad::Error
    end
    
    Album.first.tracks
    # Could not be N+1, no error raised

    The forbid_lazy_load plugin is designed to be loaded into the base model class (generally Sequel::Model), and can be loaded only in test mode, or only in certain test mode configurations, so that it does not have any production performance impact.

    Note that an alternative approach that Sequel has supported for many years is the tactical_eager_loading plugin, which automatically eager loads when an N+1 query issue is detected.

  • An association_lazy_eager_option plugin has been added which supports the :eager option for the association method. If the association has not been loaded, this eagerly loads the associations specified by the :eager option when loading the association. If the association has already been loaded, this option is ignored, with the assumption that whatever loaded the association already used the correct eager loading. Example:

    Album.plugin :association_lazy_eager_option
    Album.one_to_many :tracks
    Track.many_to_one :artist
    
    album = Album.first
    album.tracks(:eager=>:artist)
    # Loads tracks for album, then artist for each track (2 queries)
    
    album.tracks(:eager=>:artist)
    # No query issued as association is cached

    You could previously have similar behavior for uncached associations by passing a block to the association method and calling eager on the yielded dataset. However, that would ignore any cached association, causing redundant loading of the association in such cases.

  • On PostgreSQL 10+, creating partitioned tables and partitions of other tables is now supported.

    To create a partitioned table, use the :partition_by option:

    DB.create_table(:table1, partition_by: :date_column,
                            partition_type: :range) do
      Integer :id
      Date :date_column
    end
    
    DB.create_table(:table2, partition_by: :string_column,
                             partition_type: :list) do
      Integer :id
      String :string_column
    end
    
    DB.create_table(:table3, partition_by: :int_column,
                             partition_type: :hash) do
      Integer :id
      Integer :int_column
    end

    To add partitions of other tables, use the :partition_of option. This option will use a custom DSL specific to partitions of other tables.

    For range partitioning, you can use the from and to methods to specify the inclusive beginning and exclusive ending of the range of the partition. You can call the minvalue and maxvalue methods to get the minimum and maximum values for the column(s) in the range, useful as arguments to from and to:

    DB.create_table(:table1a, partition_of: :table1) do
      from minvalue
      to 0
    end
    DB.create_table(:table1b, partition_of: :table1) do
      from 0
      to 100
    end
    DB.create_table(:table1c, partition_of: :table1) do
      from 100
      to maxvalue
    end

    For list partitioning, you use the values_in method. You can also use the default method to mark a partition as the default partition:

    DB.create_table(:table2a, partition_of: :table2) do
      values_in 1, 2, 3
    end
    DB.create_table(:table2b, partition_of: :table2) do
      values_in 4, 5, 6
    end
    DB.create_table(:table2c, partition_of: :table2) do
      default
    end

    For hash partitioning, you use the modulus and remainder methods:

    DB.create_table(:table3a, partition_of: :table3) do
      modulus 3
      remainder 0
    end
    DB.create_table(:table3b, partition_of: :table3) do
      modulus 3
      remainder 1
    end
    DB.create_table(:table3c, partition_of: :table3) do
      modulus 3
      remainder 2
    end
  • On PostgreSQL 12+ and SQLite 3.31+, column schema hashes now have a :generated entry for whether the column is a generated column.

  • The schema_dumper extension now dumps generated columns correctly when using the :same_db option on PostgreSQL 12+.

  • A skip_saving_columns plugin has been added. This allows skipping saving of specific columns for the model. By default, it skips saving of generated columns, but you can customize the columns that it skips:

    Album.plugin :skip_saving_columns
    Album.skip_saving_columns = [:some_column]

Other Improvements

  • The alter_table drop_constraint :primary_key option on SQLite now works correctly for non-integer primary keys.

  • When an error is raised due to an irreversible migration, the error message now includes the file containing the migration for easier debugging.