Rainer Blessing
Rainer Blessing

Reputation: 1151

Renaming the created_at, updated_at columns of ActiveRecord/Rails

I want to rename the timestamp columns defined in timestamp.rb . Can the methods of timestamp.rb be overwritten? And what has to be done in the application that the module with the overwritten methods is used.

Upvotes: 16

Views: 14031

Answers (10)

Robin
Robin

Reputation: 915

In Rails 6.0 the instance method timestamp_attributes_for_create does not exist anymore. This method got pushed up onto the class method level and is now private

https://apidock.com/rails/v6.0.0/ActiveRecord/Timestamp/ClassMethods/timestamp_attributes_for_create

This change was introduced with this commit https://github.com/rails/rails/commit/77ff9a0adbd1318b45203a76c64561b115245603

You could still overwrite self.timestamp_attributes_for_create but I strongly suggest not to since the private API can be changed without further notice or bigger version bumps.

For Rails 6 I don't see a given "Rails way" to define custom created_at / updated_at columns.

I suggest following this answer https://stackoverflow.com/a/18197702/924050

Upvotes: 0

Tadas T
Tadas T

Reputation: 2637

I think NPatel's method is a right approach, but it is doing too much if you only need to change #created_at on a single model. Since the Timestamp module is included in every AR object, you can just override it per model, not globally.

<= Rails 5.0

class User < ActiveRecord::Base
  ...
  private
  def timestamp_attributes_for_create
    super << :registered_at
  end
end

Rails ~> 5.1

  private_class_method
  def self.timestamp_attributes_for_create
    # only strings allowed here, symbols won't work, see below commit for more details
    # https://github.com/rails/rails/commit/2b5dacb43dd92e98e1fd240a80c2a540ed380257 
    super << 'registered_at' 
  end
end

Upvotes: 30

coorasse
coorasse

Reputation: 5528

An updated anwser for Rails >= 5.1. I would suggest to use the ApplicationRecord that is available by default in your application and define the following:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  class << self
    private

    def timestamp_attributes_for_create
      super << 'my_created_at_column'
    end

    def timestamp_attributes_for_update
      super << 'my_updated_at_column'
    end
  end
end

Note that you can also use this block on a specific model if you don't want to configure it for all your models.

Note also that you should use strings and not symbols.

Upvotes: 10

Rahul
Rahul

Reputation: 452

The correct syntax for doing this in rails 4.2

class User < ActiveRecord::Base
...

private

    def timestamp_attributes_for_create
        super << 'date_add'
    end

    def timestamp_attributes_for_update
        super << 'date_update'
    end
end

Upvotes: 1

Patelify
Patelify

Reputation: 1800

This can be done via just over writing the ActiveRecord::Timestamp module methods. Not actually overwriting the entire module. I need to to accomplish this same goal as I am working with a legacy database. I am on rails 3 and I have started following a methodology of how to monkey patch my code with over writing rails functionality.

I first create the base project that I am working with and create a file called that in the initializers. In this case I created active_record.rb. And within the file I put code to override two methods that controlled the timestamp. Below is the sample of my code:

module ActiveRecord
    module Timestamp      
      private
      def timestamp_attributes_for_update #:nodoc:
        ["updated_at", "updated_on", "modified_at"]
      end
      def timestamp_attributes_for_create #:nodoc:
        ["created_at", "created_on"]
      end      
    end
  end

NOTE: I would also like to mention that this sort of monkey patching to get things to work is frowned upon and may break on upgrades so be careful and be fully aware of what it is that you want to do.

Updates:

  • Change timestamp column names from symbol to string per api change. Thanks Techbrunch for bringing this API change to my attention.

Upvotes: 25

Blaine Kasten
Blaine Kasten

Reputation: 1693

You could use the beforesave and beforecreate methods to post the DateTime.now to your specified columns.

class Sample < ActiveRecord::Base
  before_create :set_time_stamps
  before_save :set_time_stamps

  private
  def set_time_stamps
    self.created_column = DateTime.now if self.new_record?
    self.updated_column = DateTime.now
  end
end

Upvotes: 6

unboundev
unboundev

Reputation: 357

A sol'n which does not use a monkey patch; use the reversible block during your migration:

class CreateTest < ActiveRecord::Migration
  def change

    create_table :test do |t|
    end

    # set the timestamp columns default to now()
    reversible do |dir|
      dir.up do
        tables = [
          :test
        ]
        tables.each do |t|
          execute <<-SQL
            ALTER TABLE #{t.to_s}
              add column created timestamp without time zone default now();
          SQL
          execute <<-SQL
            ALTER TABLE #{t.to_s}
              add column updated timestamp without time zone default now();
          SQL
        end
      end
      dir.down do
        # no need to do anything, the rollback will drop the tables
      end
    end

  end
end

Upvotes: 0

ma11hew28
ma11hew28

Reputation: 126457

Or you could just make your own columns (DateTime) and just manually set the created_at column when you create and set the updated_at column when you update. That might be easier than the hack above. That's what I'm gonna do. Better yet, update rails to allow us to change these names.

Upvotes: 0

Maximiliano Guzman
Maximiliano Guzman

Reputation: 2035

quick ugly hack, elaborating from Milan Novota's answer: append the following to environment.rb, replacing the values of the CREATED_COL and UPDATED_COL constants with the desired column names:


module ActiveRecord
  module Timestamp
    CREATED_COL = 'created_at'
    UPDATED_COL = 'updated_at'
    private
      def create_with_timestamps #:nodoc:
        if record_timestamps
          t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
          write_attribute(CREATED_COL, t) if respond_to?(CREATED_COL) && send(CREATED_COL).nil?
          write_attribute(UPDATED_COL, t) if respond_to?(UPDATED_COL) && send(UPDATED_COL).nil?
        end
        create_without_timestamps
      end

      def update_with_timestamps(*args) #:nodoc:
        if record_timestamps && (!partial_updates? || changed?)
          t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
          write_attribute(UPDATED_COL, t) if respond_to?(UPDATED_COL)
        end
        update_without_timestamps(*args)
      end
  end
end

Upvotes: 3

Milan Novota
Milan Novota

Reputation: 15596

There is no simple way to do that. You can accomplish this either by overriding the ActiveRecord::Timestamp module, or by writing your own one to do the magic for you.

Here is how the magic works.

Upvotes: 2

Related Questions