Rails 4: update/synchronize views with database schema after a migration

So you just updated your model and the related database schema, but your views are now obsolete. How to refresh them?

Well, there are a lot of premises to say here. First, there’s no actual way to synchronize your views with your model. The best way is to do it manually, but it’s not the only way. The other way, which I’m going to explain in this post, actually only works if you are willing to scaffold your whole model/view/controller/tests from scratch. This is probably not desirable in most of cases, but I assure you can safely try this path if you are in a early stage of development, or if you are ok with the default (generated) Rails’ views. So, whatever, if you don’t want to rake generate scaffold your_resource again, you can stop reading now.


Oh well, you are still reading :-)

I’ll proceed explaining how to synchronize your views after the migration(s) you have run. Let’s just start from scratch.

A full example

Let’s say we have these two models: Child and Toy.

Child

  • name
  • birth_date

Toy

  • description
  • price
  • child_id

As you might have already guessed, I am going to tie our models with a one-to-many relationship: a child has_many toys, and a toy belongs_to a child.

app/models/child.rb
1
2
3
class Child > ActiveRecord::Base
  has_many :toys
end
app/models/toy.rb
1
2
3
class Toy > ActiveRecord::Base
  belongs_to :child
end

Let’s create the application and scaffold these resources:

1
$ rails new DemoApp && cd DemoApp
1
2
3
$ rails generate scaffold child name birth_date:date

$ rails generate scaffold toy description price:decimal child:references

Run the migrations:

1
2
3
4
5
6
7
8
9
10
$ rake db:migrate
==  CreateChildren: migrating =================================================
-- create_table(:children)
   -> 0.0014s
==  CreateChildren: migrated (0.0015s) ========================================

==  CreateToys: migrating =====================================================
-- create_table(:toys)
   -> 0.0028s
==  CreateToys: migrated (0.0029s) ============================================

We can now start the server and check that everything went good.

1
2
3
4
$ rails s
=> Booting WEBrick
=> Rails 4.1.0.beta1 application starting in development on http://0.0.0.0:3000
...

listing children empty

It looks fine, except for one thing: there’s no data displayed! Let’s add some entries manually.

listing children

Good. Now let’s give some toys to our boys:

toy successfully created

Done. But wait a minute: this view looks a bit… crappy, doesn’t it? We don’t want to see a reference to a child… just his/her name. Even the prices don’t look right.

Let’s generate the scaffold again, with some corrections.

1
2
3
$ rails g scaffold toy description:string{50} price:decimal{4,2}
      invoke  active_record
Another migration is already named create_toys: /Users/simo/Projects/DemoApp/db/migrate/20140209145850_create_toys.rb. Use --force to remove the old migration file and replace it.

Fair enough. We must destroy the entire scaffold before recreating it.

1
2
3
4
5
6
7
8
9
10
11
$ rails destroy scaffold toy
      invoke  active_record
      remove    db/migrate/20140208224426_create_toys.rb
      remove    app/models/toy.rb
      remove    ...

$ rails generate scaffold toy description:string{50} price:decimal{4,2} child:references
      invoke  active_record
      create    db/migrate/20140209145850_create_toys.rb
      create    app/models/toy.rb
      create    ...

Ok. Let’s give a quick look at the generated migration.

1
2
3
4
5
6
7
8
9
10
11
class CreateToys > ActiveRecord::Migration
  def change
    create_table :toys do |t|
      t.string :description, limit: 50
      t.decimal :price, precision: 4, scale: 2
      t.references :child, index: true

      t.timestamps
    end
  end
end

It looks right. Let’s run it!

1
2
3
4
5
6
7
$ rake db:migrate
==  CreateToys: migrating =====================================================
-- create_table(:toys)
rake aborted!
An error has occurred, this and all later migrations canceled:

SQLite3::SQLException: table "toys" already exists: CREATE TABLE "toys" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "description" varchar(50), "price" decimal(4,2), "child_id" integer, "created_at" datetime, "updated_at" datetime) /Users/simo/.rvm/gems/ruby-2.0.0-p353/gems/sqlite3-1.3.8/lib/sqlite3/database.rb:91:in `initialize'

Whoops! The table already exists. We should remove it first. Edit the migration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CreateToys > ActiveRecord::Migration
  def change
    # This will do the work
    drop_table :toys

    create_table :toys do |t|
      t.string :description, limit: 50
      t.decimal :price, precision: 4, scale: 2
      t.references :child, index: true

      t.timestamps
    end
  end
end

And migrate again:

1
2
3
4
5
6
7
$ rake db:migrate
==  CreateToys: migrating =====================================================
-- drop_table(:toys)
   -> 0.0107s
-- create_table(:toys)
   -> 0.0109s
==  CreateToys: migrated (0.0220s) ============================================

Ok, we are ready to start the server again and see what changed.

1
$ rails s

That’s it. This is basically the process. It’s way too labourious, I know, but the truth is that we just can’t efficiently automate a process like this, because: what if we generate more migrations during the journey? We would have to delete them and recreate again the whole schema by running rails generate scaffold, rake db:reset and rerun the generate again and again… well, that sucks. At this stage we’ve got the point: it’s better to do it manually! Rails gives us tons of helper methods to format prices and get things done, and that’s definitely the path to follow.

If you are still not convinced, you can check this question on StackOverflow. Basically, it’s almost the same question I had before writing this post. Both the question and the answer are quite old, and in the meantime the Rails Team didn’t develop anything that would help us regenerating our views. This probably means we are not supposed to do it, don’t you think? However, if your goal is to get focused on the backend without having to deal with the frontend, you can always use a gem like ActiveScaffold. But you have been warned!

I hope you’ve found this article useful; thoughts are welcome, as always. If you want you can leave a comment below.

Comments