Làm cách nào để đặt giá trị mặc định cho cột ngày giờ để ghi lại thời gian tạo khi di chuyển?


114

Hãy xem xét kịch bản tạo bảng bên dưới:

create_table :foo do |t|
  t.datetime :starts_at, :null => false
end

Có thể đặt giá trị mặc định là thời gian hiện tại không?

Tôi đang cố gắng tìm một DB độc lập tương đương trong rails cho các định nghĩa cột SQL được đưa ra bên dưới:

Cú pháp Oracle

start_at DATE DEFAULT SYSDATE() 

Cú pháp MySQL

start_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP

HOẶC LÀ

start_at DATETIME DEFAULT NOW()

Câu trả lời:


174

Điều này hiện được hỗ trợ trong Rails 5.

Đây là một mẫu di chuyển:

class CreatePosts < ActiveRecord::Migration[5.0]
  def change
    create_table :posts do |t|
      t.datetime :modified_at, default: -> { 'CURRENT_TIMESTAMP' }
      t.timestamps
    end
  end 
end

Xem thảo luận tại https://github.com/rails/rails/issues/27077 và trả lời tại đó bởi prathamesh-sonpatki


14
Câu trả lời chính xác. Hoàn toàn là một bên, hãy lưu ý rằng trong Postgres CURRENT_TIMESTAMPsẽ là thời điểm bắt đầu giao dịch hiện tại, vì vậy nhiều bản ghi được tạo trong cùng một giao dịch sẽ nhận được cùng giá trị đó. Nếu bạn muốn thời gian hiện tại thực sự mà câu lệnh thực thi (bỏ qua ngữ cảnh giao dịch), hãy kiểm tra CLOCK_TIMESTAMP.
Abe Voelker

Tôi đã thêm một cái gì đó như thế này: add_column :table_name, :start_date, :datetime, default: -> { 'CURRENT_TIMESTAMP' }và đây là những gì nó trông giống như trong schema.rb t.datetime "start_date", default: -> { "now()" }Nhưng khi tôi tạo bản ghi mới, nó không được điền. Bất kỳ ý tưởng tại sao?
Sandip Subedi

@SandipSubedi tương tự cho tôi. Trên đường ray 5.2.3.
courtimas

1
@courtsimas bạn cần làm gì post.reloadđể nhận được những giá trị đó. Nó được giải thích ở đây: stackoverflow.com/questions/53804787/…
Sandip Subedi

1
@SandipSubedi Tôi nhận ra điều đó 5 phút sau nhận xét của mình. Cũng giống như với thế hệ uuid. Cảm ơn!
courtimas

119

Bạn có thể thêm một hàm trong một mô hình như thế này:

  before_create :set_foo_to_now
  def set_foo_to_now
    self.foo = Time.now
  end

Vì vậy, mô hình sẽ thiết lập thời gian hiện tại trong mô hình.

Bạn cũng có thể đặt một số mã sql trong quá trình di chuyển để đặt giá trị mặc định ở cấp cơ sở dữ liệu, chẳng hạn như:

execute 'alter table foo alter column starts_at set default now()'

Đặt một cái gì đó như thế này:

create_table :foo do |t|
  t.datetime :starts_at, :null => false, :default => Time.now
end

nguyên nhân thực thi hàm Time.now trong khi di chuyển, do đó bảng trong cơ sở dữ liệu được tạo như sau:

create table foo ( starts_at timestamp not null default '2009-01-01 00:00:00');

nhưng tôi nghĩ rằng nó không phải là những gì bạn muốn.


1
Hiện tại, tôi đang đặt giá trị trong lệnh gọi lại: before_create. <br> Tôi đang tìm kiếm một số loại phép thuật AR ở đây. Tôi đã dành một chút thời gian để xem mã Rails, nhưng tôi không tìm thấy giải pháp nào. Tôi nghĩ tôi sẽ hỏi xung quanh để xem có bất kỳ lựa chọn thay thế nào không.
Harish Shetty 17/10/09

Tôi khuyên bạn nên làm điều đó với một cuộc gọi lại trên before_create.
jonnii

1
Tôi không muốn thay đổi bảng DB vì tôi muốn giữ mã DB trung lập. Tôi đã hy vọng rằng AR có một số cơ chế để đặt giá trị mặc định cho trường Datetime tương tự như trường create_at.
Harish Shetty

9
Hãy lưu ý rằng lệnh gọi lại before_create chạy trước khi chèn vào cơ sở dữ liệu nhưng sau khi bạn khởi tạo đối tượng của mình (như với new()). Vì vậy, self.foo = Time.nowsẽ ghi đè giá trị mà bạn có thể cung cấp new(). Tôi đề nghị self.foo = Time.current unless self.foo.present?thay thế.
Fatih

2
Không phải vậy, khi sử dụng thực thi, điều này sẽ đặt một dòng :starts_at, :default => 'now()'trong schema.rb. Tuy nhiên, điều này sẽ không hoạt động khi sử dụng rake db:schema:dumpđiều này sẽ ghi đè schema.rb với :starts_at, :default => '2015-05-29 09:46:33'(hoặc bất kỳ ngày nào khi bạn khởi chạy tập lệnh) ... buồn ....
astreal

13

Active Record tự động dấu thời gian tạo và cập nhật các hoạt động nếu bảng có các trường có tên created_at/ created_onhoặc updated_at/ updated_on. Nguồn - api.rubyonrails.org

Bạn không cần phải làm gì khác ngoại trừ việc có cột đó.


Tôi đã có những trường đó trong bảng của mình. Tôi cần một trường bổ sung để trình lập lịch biểu của tôi giữ ngày bắt đầu. Hiện tại, tôi đang sử dụng: before_create callback để đặt ngày hiện tại. Nếu tôi gặp trường hợp này thường xuyên, tôi phải dùng đến cách viết một plugin để thay đổi việc xử lý giá trị mặc định trong phương thức 'to_sql' của lớp ColumnDefinition.
Harish Shetty 17/10/09

9

Tôi đang tìm kiếm một giải pháp tương tự nhưng tôi đã kết thúc bằng https://github.com/FooBarWidget/default_value_for .

Các default_value_forplugin cho phép một để xác định giá trị mặc định cho các mô hình ActiveRecord một cách tường thuật. Ví dụ:

class User < ActiveRecord::Base
  default_value_for :name, "(no name)"
  default_value_for :last_seen do
    Time.now
  end
end

u = User.new
u.name       # => "(no name)"
u.last_seen  # => Mon Sep 22 17:28:38 +0200 2008

9

Tôi thường làm:

def change
  execute("
    ALTER TABLE your_table
    ALTER COLUMN your_column
    SET DEFAULT CURRENT_TIMESTAMP
  ")
end

Vì vậy, của bạn schema.rbsẽ có một cái gì đó như:

create_table "your_table", force: :cascade do |t|
  t.datetime "your_column", default: "now()"
end

Nhược điểm: giải pháp này chỉ hoạt động khi bạn chạy rake db:migratechứ không phải khi bạn tải tệp lược đồ của mình với một cái gì đó như thế rake db:schema:load.
Alter Lagos,

8

Nếu bạn cần phải thay đổi một cột DateTime hiện trong Rails 5 (thay vì tạo ra một bảng mới theo quy định tại câu trả lời khác) để nó có thể tận dụng khả năng ngày mặc định, bạn có thể tạo một sự chuyển đổi như thế này:

class MakeStartsAtDefaultDateForFoo < ActiveRecord::Migration[5.0]
  def change
    change_column :foos, :starts_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' }
  end
end

Hình thức tồi để bỏ phiếu điều gì đó và không giải thích vấn đề đã được thực hiện với câu trả lời. Bất kỳ ai có ý tưởng tại sao điều này bị bỏ phiếu? Nó hiển thị cú pháp nếu bạn muốn thay đổi một cột thay vì tạo một cột.
Matt Long

Tôi không phải là người phản đối, nhưng tôi gặp khó khăn khi thấy câu trả lời này khác với câu trả lời của ý chí đi trước bạn một năm. Câu hỏi là về việc đặt mặc định và câu trả lời của bạn có cùng một mệnh đề lambda.
nurettin

2
@nurettin Tôi hiểu ý bạn, nhưng tôi bảo vệ, cú pháp để tạo một cột mới khác một cách tinh tế với việc thay đổi một cột hiện có. Cung cấp cú pháp đó cho những người tìm thấy câu hỏi này qua tìm kiếm trên web trong khi cố gắng thêm mặc định vào mô hình dữ liệu hiện tại của họ thay vì tạo một mô hình / bảng mới hoàn toàn có lẽ khá hữu ích. Không? Bạn đang giả định rằng mọi người đều biết họ có thể sử dụng cùng một lambda cho một cột_thay đổi. Có lẽ họ nên nhận ra điều đó, nhưng đó là lý do tại sao tôi trả lời ở đây - vì vậy họ không cần phải đi đâu khác để tìm ra điều đó. Chúc mừng!
Matt Long

4
FWIW Tôi vừa mới sử dụng cú pháp này và đánh giá cao rằng đối với tìm kiếm trên google của tôi và vấn đề cụ thể, câu trả lời này đã giúp tôi nhiều nhất.
Jay Killeen

1
Tôi không thấy có gì sai khi đây là một câu trả lời hơn là một bình luận. Đã ủng hộ. Tôi nghĩ những người phản đối chỉ đọc một cách cẩu thả (giống như hầu hết những người đóng câu hỏi!; P).
iconoclast

-1

Trong câu trả lời được đưa ra bởi @ szymon-lipiński (Szymon Lipiński), phương thức thực thi không hoạt động với tôi. Đó là lỗi cú pháp MySQL.

Cú pháp MySQL phù hợp với tôi là thế này.

execute "ALTER TABLE mytable CHANGE `column_name` `column_name` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"

Vì vậy, để đặt giá trị mặc định cho cột ngày giờ trong tập lệnh di chuyển có thể được thực hiện như sau:

def up
  create_table :foo do |t|
    t.datetime :starts_at, :null => false
  end

  execute "ALTER TABLE `foo` CHANGE `starts_at` `starts_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP"
end
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.