I have a User
model and a Role
model. When I started building the app, I start by creating the User
model and the migration file that was generated contain a reference to Roles:
class CreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.string :first_name
t.string :last_name
t.string :username
t.string :email
t.string :password
t.string :password_digest
t.boolean :banned
t.references :role, foreign_key: true
t.timestamps
end
end
end
Then I created the Role
model which generated this migration file:
class CreateRoles < ActiveRecord::Migration[5.0]
def change
create_table :roles do |t|
t.string :title
t.integer :access_level
t.timestamps
end
end
end
I am trying to deploy to Heroku and migrating my database per the documentation using the following command heroku run rails db:migrate
(using Rails 5).
I am getting an error from Heroku saying:
heroku run rake db:migrate
Running rake db:migrate on ⬢ gentle-headland-79177... up, run.9293 (Free)
D, [2016-12-31T08:15:33.131367 #4] DEBUG -- : (90.7ms) CREATE TABLE "schema_migrations" ("version" character varying PRIMARY KEY)
D, [2016-12-31T08:15:33.152682 #4] DEBUG -- : (11.5ms) CREATE TABLE "ar_internal_metadata" ("key" character varying PRIMARY KEY, "value" character varying, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL)
D, [2016-12-31T08:15:33.155373 #4] DEBUG -- : (1.1ms) SELECT pg_try_advisory_lock(6845940114126317925);
D, [2016-12-31T08:15:33.172106 #4] DEBUG -- : ActiveRecord::SchemaMigration Load (1.2ms) SELECT "schema_migrations".* FROM "schema_migrations"
I, [2016-12-31T08:15:33.178453 #4] INFO -- : Migrating to CreateUsers (20161117083901)
D, [2016-12-31T08:15:33.181903 #4] DEBUG -- : (0.9ms) BEGIN
== 20161117083901 CreateUsers: migrating ======================================
-- create_table(:users)
D, [2016-12-31T08:15:33.199351 #4] DEBUG -- : (13.4ms) CREATE TABLE "users" ("id" serial primary key, "first_name" character varying, "last_name" character varying, "username" character varying, "email" character varying, "password" character varying, "password_digest" character varying, "banned" boolean, "role_id" integer, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "fk_rails_642f17018b"
FOREIGN KEY ("role_id")
REFERENCES "roles" ("id")
)
D, [2016-12-31T08:15:33.200707 #4] DEBUG -- : (1.0ms) ROLLBACK
D, [2016-12-31T08:15:33.202190 #4] DEBUG -- : (1.2ms) SELECT pg_advisory_unlock(6845940114126317925)
rake aborted!
StandardError: An error has occurred, this and all later migrations canceled:
PG::UndefinedTable: ERROR: relation "roles" does not exist
: CREATE TABLE "users" ("id" serial primary key, "first_name" character varying, "last_name" character varying, "username" character varying, "email" character varying, "password" character varying, "password_digest" character varying, "banned" boolean, "role_id" integer, "created_at" timestamp NOT NULL, "updated_at" timestamp NOT NULL, CONSTRAINT "fk_rails_642f17018b"
FOREIGN KEY ("role_id")
REFERENCES "roles" ("id")
)
From my understanding, it appears Heroku is expecting Role
to be defined first then User
.
Why is it on my local machine, I can do db:migrate fine but on Heroku it fails?
Difference between Sqlite3 and Postgresql perhaps?
How do I go about solving this deployment problem?
Do I just rename my create_role migration file to have an earlier timestamp than the create_user migration file? Is that even recommended practice ? :D
Update
I did a git clone of my repository to a Desktop folder on my iMac.
Then I ran rails db:migrate
on that new local copy.
No error whatsoever. All db migrations ran, all the tables are in place along with all the relationships. Something's really messed up on the Heroku end.
Update 2
Did another checkout of my repository into a new Desktop folder, ran bundle install
and then tried this version of the db:migrate command:
rails db:migrate RAILS_ENV=production
and I see the same error output about roles does not exists
HOWEVER
I then created a brand spanking new rails project honey
:
rails new honey
Did bundle install
Then went:
rails generate model User name:string role:references
Finally, I went:
rails db:migrate RAILS_ENV=production
and no errors...
I've clearly have not generated any Role
model, so why does it not fail?
Here's the console log:
Warlocks-iMac:bad clementwu$ cd honey/
Warlocks-iMac:honey clementwu$ ls
Gemfile Rakefile config lib test
Gemfile.lock app config.ru log tmp
README.md bin db public vendor
Warlocks-iMac:honey clementwu$ rails generate model User name:string role:references
Running via Spring preloader in process 27200
Expected string default value for '--jbuilder'; got true (boolean)
invoke active_record
create db/migrate/20170101100613_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/user_test.rb
create test/fixtures/users.yml
Warlocks-iMac:honey clementwu$ rails db:migrate RAILS_ENV=production
== 20170101100613 CreateUsers: migrating ======================================
-- create_table(:users)
-> 0.0018s
== 20170101100613 CreateUsers: migrated (0.0018s) =============================
Warlocks-iMac:honey clementwu$
The database is created and even shows the role_id
foreign key despite no table called role
existing in my production database:
Baffling :D
Update 3
Perhaps it's the difference between a sqlite3 database and a postgresql database.
By default, a rails app config/database.yml
specifies that production database is called db/production.sqlite3
, i.e. it isn't using a PostgreSQL database, hence why it doesn't give the error about roles not existing.
And according to this Stackoverflow post: Does SQLite support referential integrity?
It appears SQLite3 doesn't guarantee referential integrity :(
Major pain in the butt.
It's a good thing this is only a personal learning project not a work project.
It's also not really feasible to start off with PostgreSQL, you can't just drop and recreate database as easily as you can with SQLite3 and Rails CLI.
I guess the lesson to learn here is to think hard and create the dependant tables first.
I fixed all my migrations and got the API running on Heroku server now.
The real answer is: deploy to Heroku early, don't wait till after finishing development on local machine then deploy.
Deploy early will identify problems early like my migration discrepancies between SQlite and PostgreSQL.
Additionally, when doing references in migration file, if not using matching model and table names e.g.
author
vsuser
, modify the migration file to useadd_foreign_key
before running the migration.For example:
Needs to become:
Found the new knowledge from this link:
http://sevenseacat.net/2015/02/24/add_foreign_key_gotchas.html