stringer-rss / stringer

A self-hosted, anti-social RSS reader.
MIT License
3.83k stars 388 forks source link

Rails7.1: Updating Timestamp columns with precision #1161

Closed RobBoothAppDev21 closed 4 months ago

RobBoothAppDev21 commented 4 months ago

@mockdeep In the process of creating a migration to add precision: nil to the datetime columns, I noticed that the datetime columns already have the attribute precision:nil. See below.

My understanding based on this comment is that 7.0 -> 7.1 changed precision: nil from implicit to explicit in schema.rb in instances - such as stringer - where a precision value was not set.

Snippet of updated_at column from feeds table:

[
  #<ActiveRecord::ConnectionAdapters::PostgreSQL::Column:0x0000000113feb230
  @collation=nil,
  @comment=nil,
  @default=nil,
  @default_function=nil,
  @generated="",
  @identity=nil,
  @name="updated_at",
  @null=false,
  @serial=nil,
  @sql_type_metadata=
   #<ActiveRecord::ConnectionAdapters::SqlTypeMetadata:0x0000000114204ad0
    @limit=nil,
    @precision=nil,
    @scale=nil,
    @sql_type="timestamp without time zone",
    @type=:datetime>>
]

Ignore if above understanding is correct If a migration file is needed would this be the preferred approach?

  1. Generate 1st migration - add temporary columns (e.g. updated_at_temp) for each of the datetime columns in the database. See below for first pass
  2. Rake task - create a rake task to migrate data from previous datetime columns to new ones (e.g. updated_at --> updated_at_temp)
  3. Generate 2nd migration - remove old date columns (e.g. remove_column :feed, :updated_at) and rename new columns to old name (e.g. rename_column :feeds, :updated_at_temp, :updated_at
class AddPrecisionToTimestamps < ActiveRecord::Migration[7.1]
  SKIP_TABLES = [:schema_migrations, :ar_internal_metadata].freeze

  def change
    table_names = ActiveRecord::Base.connection.tables.map(&:to_sym)
    table_names.each do |table|
      next if SKIP_TABLES.include?(table)

      ActiveRecord::Base.connection.columns(table).each do |column|
        next unless datetime_column?(column)

        add_temp_column(table, column)
      end
    end
  end

  private

  def create_temp_column_name(column)
    name = column.name
    :"#{name}_temp"
  end

  def datetime_column?(column)
    column.sql_type_metadata.type == :datetime
  end

  def add_temp_column(table, column)
    options = { precision: nil }
    options[:null] = false if ["created_at", "updated_at"].include?(column.name)
    add_column(table, create_temp_column_name(column), :datetime, options)
  end
end