- 更新日: 2015年7月7日
- Rails
Rails/ActiveRecordのtimestamps(created_at/updated_at)の実装を読んでみた
Rails(正しくは ActiveRecord ですが)の created_at, updated_at は、DBレコードを作成・更新した日時を自動で記録してくれるのでとても便利です。
Swift による iOS 開発で CoreData を使う際に、Rails の ActiveRecord のようにレコード保存で自動更新される created_at, updated_at カラムの機能を実装したかったので、t.timestamps メソッドとそれで作成される created_at, updated_at カラムの実装周りのソースコードを読んでみました。コードリーディングの練習も兼ねてます^^
CoreDataでcreated_at/updated_atの日付データを自動作成 | EasyRamble
— 環境 —
Rails 4.1
activerecord-4.1.1
マイグレーション中の t.timestamps の実装
Rails のマイグレーションファイルの中で、以下のように t.timestamps と書いておけば、DBレコードの追加・更新で自動更新される created_at, updated_at カラムを作成してくれます。
1 2 3 4 5 6 7 8 |
class CreateProfile < ActiveRecord::Migration def change create_table :profiles do |t| t.string :name, null: false, default: "" t.timestamps end end end |
まずは、この t.timestamps の実装からコードリーディングを開始しました。Rails プロジェクトのディレクトリに移動後…
1 2 3 |
$ bundle open activerecord |
これで activerecord のディレクトリをエディタで開けます。
lib/active_record/connection_adapters/abstract/schema_definitions.rb
394行目辺り。
1 2 3 4 5 6 |
# Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps # # t.timestamps def timestamps @base.add_timestamps(@table_name) end |
add_timestamps については、SchemaStatements#add_timestamps を見ろとコメントにあるのでそれを読む。
lib/active_record/connection_adapters/abstract/schema_statement.rb
あった。721行目辺り、ここで add_column を呼び出して created_at, updated_at のカラムが追加されてますね。
1 2 3 4 5 6 7 8 |
# Adds timestamps (+created_at+ and +updated_at+) columns to the named table. # # add_timestamps(:suppliers) # def add_timestamps(table_name) add_column table_name, :created_at, :datetime add_column table_name, :updated_at, :datetime end |
t.timestamps についてはとりあえずここまで。
created_at, updated_at の実装
続いて、実際にレコードが追加・更新される際の、created_at, updated_at の自動更新機能の実装を見てみます。
lib/active_record/timestamp.rb
まずは、レコードが作成(create)される場合の、created_at, updated_at の実装。46行目〜。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def create_record if self.record_timestamps current_time = current_time_from_proper_timezone all_timestamp_attributes.each do |column| if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil? write_attribute(column.to_s, current_time) end end end super end |
なるほど〜。all_timestamp_attributes メソッドで、timestamp に関するカラムを each で回して、そのモデルが timestamp のカラムを持っていて、かつ値が空(nil)の場合に write_attribute で属性に値(現在の日時)を書き込んでいます。Rails のソースコードはメタプログラミングが使われている箇所以外は読みやすいです。
all_timestamp_attributes メソッドについては以下。89行目〜。
1 2 3 4 5 6 7 8 9 10 11 |
def timestamp_attributes_for_update [:updated_at, :updated_on] end def timestamp_attributes_for_create [:created_at, :created_on] end def all_timestamp_attributes timestamp_attributes_for_create + timestamp_attributes_for_update end |
created_at, updated_at の他 created_on, updated_on も timestamp 用のカラムとして扱われるようです。
続いて、レコードが更新(update)される場合の、created_at, updated_at の実装。60行目〜。ソースを追うと分かりますが、created_at については update では何もしない。
1 2 3 4 5 6 7 8 9 10 11 12 |
def update_record(*args) if should_record_timestamps? current_time = current_time_from_proper_timezone timestamp_attributes_for_update_in_model.each do |column| column = column.to_s next if attribute_changed?(column) write_attribute(column, current_time) end end super end |
レコードのタイムスタンプが有効(if should_record_timestamps?)の場合に、タイムスタンプ更新用のカラムを timestamp_attributes_for_update_in_model で取得して each で回し、write_attribute でタイムスタンプ値を現在の日時に更新する処理が書かれています。
timestamp_attributes_for_update_in_model の実装は以下。81行目。
1 2 3 |
def timestamp_attributes_for_update_in_model timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c.to_s) } end |
以上で、ActiveRecord の created_at, updated_at 周りの実装がおおかた理解できました。今回のコードリーディングはここまでです。
- Rails の関連記事
- RailsでMySQLパーティショニングのマイグレーション
- Rails ActiveRecordでdatetime型カラムのGROUP BY集計にタイムゾーンを考慮する
- RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方
- RailsでAMPに対応するgemをリリースしました
- Railsでrequest.urlとrequest.original_urlの違い
- Railsでwheneverによるcronバッチ処理
- Google AnalyticsのRails Turbolinks対応
- Railsアプリにソーシャル・シェアボタンを簡単設置
- Rails監視ツール用にErrbitをHerokuで運用
- Facebook APIバージョンのアップグレード手順(Rails OmniAuth)
Leave Your Message!