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
class Child > ActiveRecord::Base
has_many :toys
end
# app/models/toy.rb
class Toy > ActiveRecord::Base
belongs_to :child
end
Let's create the application and scaffold these resources:
$ rails new DemoApp && cd DemoApp
$ rails generate scaffold child name birth_date:date
$ rails generate scaffold toy description price:decimal child:references
Run the migrations:
$ 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.
$ rails s
=> Booting WEBrick
=> Rails 4.1.0.beta1 application starting in development on http://0.0.0.0:3000
...
It looks fine, except for one thing: there's no data displayed! Let's add some entries manually.
Good. Now let's give some toys to our boys:
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.
$ 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.
$ 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.
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!
$ 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:
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:
$ 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.
$ 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.