I’m writing migrations in Rails 3.2 using SQL (i.e. execute) to enforce application logic (foreign keys, defaults, checks, triggers) at the database level. The reason is that I want consistent database interactions in case I need to use the same database from different applications (i.e. not Rails) or mass import data directly. Everything seems to work splendidly except for default values for one model (see below) .
For example created_at would include NOT NULL DEFAULT current_timestamp. This is all nice if I create a record in the database, but for some reason schema.rb doesn’t detect the default values, but correctly identifies the NOT NULL constraint. The consequence is that I can’t save an instance in the database using Model.create or Model.new (e.g. BibliographicItem.create(title: "Foo")), because created_at and updated_at end up being nil which violates the null: false constraint in schema.rb.
The offending table in schema.rb:
create_table "bibliographic_items", :id => false, :force => true do |t|
t.string "uuid", :limit => nil, :null => false
t.string "title", :limit => nil, :null => false
t.integer "publication_year"
t.string "type", :limit => nil, :null => false
t.text "description"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
Its model:
class BibliographicItem < ActiveRecord::Base
include Extensions::UUID
attr_accessible :title, :publication_year, :description
has_many :authorships
has_many :authors, through: :authorships, order: "role DESC, author_order DESC NULLS LAST"
validates :title, presence: true, length: { maximum: 500 }
validates :publication_year, numericality: { only_integer: true,
less_than_or_equal_to: Date.today.year() }
end
The table creation in the execute statement:
CREATE TABLE bibliographic_items (
uuid uuid DEFAULT uuid_generate_v4() PRIMARY KEY,
title varchar NOT NULL,
publication_year int CHECK (publication_year <= left(now()::text, 4)::int),
type varchar NOT NULL REFERENCES bibliographic_item_types ON UPDATE CASCADE ON DELETE RESTRICT,
description text,
created_at timestamp without time zone NOT NULL DEFAULT current_timestamp,
updated_at timestamp without time zone NOT NULL DEFAULT current_timestamp,
CHECK (updated_at >= created_at)
);
Why do .create and .new not assign values for created_at and updated_at? For all my other models, with similar (but simpler definitions) there is no issue.
I found the cause of my problems, which as it happens, has nothing to do with SQL migrations, created_at or schema.rb, but is caused by failing the validation
validates publication_year, numericality: { only_integer: true, less_than_or_equal_to: Date.today.year() }when publication_year isnil.Bangs head against wall.