I decided to spend some of my Christmas break taking a look at Ruby on Rails again and trying to get an application together under this application stack. Since I last looked at Rails, it had crossed the 1.x to 2.x version marker and several changes manifested. For someone trying to follow the 1.x tutorials or the canonical reference book Agile Web Development With Rails, one doesn’t make very much progress before the changes with respect to scaffolding bite one. One may need a bit of a tutorial on Rails 2.x scaffolding before being “pretty close” to training documentation. An excellent tutorial for this content was found at Fairleads.
Yet as I followed this through, I found myself stymied by the same error reported by “James”
Thanks now I easily understand how scaffolding changed in 2.0.
But I still have a problem when I followed the tutorial everything went fine but i am not presented with any sort of fields to enter information.
And when i add stuff to the database by hand and I list from within the scaffold I am not getting the information that is entered into the database I am just getting a blank entry.
At first I thought my problem was that I was trying to use a PostgreSQL back end, or perhaps it was because I was using MySQL, etc. But nevertheless I still ran into the same problem no matter which db back-end I used.
I then found another tutorial at SapphireSteel. Curiously, this worked ( the db back end was sqlite3 ). This tutorial is meant to show off the Sapphire Steel product, but Huw, the author, has added, graciously, a “here’s what Rails is doing on the back end” translation for each step that his product automates.
With these data points my working hypothesis was this:
- Use SQLite3
- Follow Huw’s instructions, which use a more specialized invocation of “script/generate scaffold” ( control )
- Duplicate these instructions using a more primitive invocation of “script-generate scaffold” that’s more familiar ( experiment )
Step 1: Set up the working directory
- Make a working directory ~/experiment.
- Make an experimental rails install ~/experiment/exp with rails -d sqlite3 exp
- Make an control rails install ~/experiment/control with rails -d sqlite3 control
Step 2: Check the config/database.yml
# SQLite version 3.x # gem install sqlite3-ruby (not necessary on OS X Leopard) development: adapter: sqlite3 database: db/development.sqlite3 timeout: 5000 # Warning: The database defined as 'test' will be erased and # re-generated from your development database when you run 'rake'. # Do not set this db to the same as development or production. test: adapter: sqlite3 database: db/test.sqlite3 timeout: 5000 production: adapter: sqlite3 database: db/production.sqlite3 timeout: 5000
Step 3: Verify that they are the same between the two installs:
bash-3.2$ diff control/config/database.yml exp/config/database.yml
Step 4: In the control section issue:
script/generate scaffold post title:string body:text created_at:datetime
This follows the SapphireSteel documentation. In the experiment do not implement the fully fleshed out line:
script/generate scaffold post
Each of these steps generates a lot of output as various files are conjured.
I also tried running script/server for each of these and then visiting http://localhost:3000 site to verify that the Rails install, at its most basic was running. This was valid.
Step 5: Examine the diff between the control’s db/migrate/001_create_posts.rb
bash-3.2$ diff -y control/db/migrate/001_create_posts.rb exp/db/migrate/001_create_posts.rb
Note: Changed format to be vertical so that you don’t have horizontal bleed in web format
class CreatePosts < ActiveRecord::Migration
def self.up
create_table :posts do |t|
t.string :title
t.text :body
t.datetime :created_at
t.timestamps
end
end
def self.down
drop_table :posts
end
end
class CreatePosts < ActiveRecord::Migration
def self.up
create_table :posts do |t|
>
>
>
t.timestamps
end
end
def self.down
drop_table :posts
end
end
Step 6: Difference between the two migrations are these three lines.
t.string :title t.text :body t.datetime :created_at
Step 7: Test migrations
rake db:migrate
in both the experiment/exp and experiment/control directories. Roll back the changes with:
rake db:migrate VERSION=0
Step 7: Make the experimental 001… migration look identical to the control-generated experimental 001… migration
I actually typed the lines in, but you could use a tool like vimdiff, or, more swiftly, copy the control file into the experimental file like so:
cp control/db/migrate/001_create_posts.rb exp/db/migrate/001_create_posts.rb
You could verify their parity by using diff.
Step 8: Migrate both with rake db:migrate
Hypothesize
At this point they are using the same migration, the same db back end, etc. They should be the same in function.
Start up script/server on the control
It is rich functioning, when adding “new” entries we have all the basic scaffolded elements and things work as expected.
Stop control, start script/server on experiment.
Breaks! When you hit “new”, you are not given the fields to fill in! Instead you are taken directly back to the index! This is the broken behavior described at fairleads.
Step 9: Try to fix it
- Re-create the exp and control rails installs
- Get a list of all the files: find exp -f > rawfiles
- Use sed to remove the leading exp/ from rawfiles
for i in `cat rawfiles` ; do diff exp/$i control/$i; done
I removed the hexidecimal noise from the diff.
bash-3.2$ for i in `cat rawfiles `; do diff exp/$i control/$i; done 9c9 < protect_from_forgery # :secret => 'hex stuff' --- > protect_from_forgery # :secret => 'hex stuff' 40,41c40,41 < :session_key => '_exp_session', < :secret => 'hex stuff' --- > :session_key => '_control_session', > :secret => 'hex stuff'
OK, so they’re the same install. Now. Let’s do the scaffold command the two different ways and record the output sent to STDOUT to text files.
diff the output files:
bash-3.2$ diff -y control.log ../exp/experiment.log
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/posts
exists app/views/layouts/
exists test/functional/
exists test/unit/
create app/views/posts/index.html.erb
create app/views/posts/show.html.erb
create app/views/posts/new.html.erb
create app/views/posts/edit.html.erb
create app/views/layouts/posts.html.erb
create public/stylesheets/scaffold.css
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/post.rb
create test/unit/post_test.rb
create test/fixtures/posts.yml
create db/migrate
create db/migrate/001_create_posts.rb
create app/controllers/posts_controller.rb
create test/functional/posts_controller_test.rb
create app/helpers/posts_helper.rb
route map.resources :posts
bash-3.2$ diff -y control.log ../exp/experiment.log
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/posts
exists app/views/layouts/
exists test/functional/
exists test/unit/
create app/views/posts/index.html.erb
create app/views/posts/show.html.erb
create app/views/posts/new.html.erb
create app/views/posts/edit.html.erb
create app/views/layouts/posts.html.erb
create public/stylesheets/scaffold.css
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
create app/models/post.rb
create test/unit/post_test.rb
create test/fixtures/posts.yml
create db/migrate
create db/migrate/001_create_posts.rb
create app/controllers/posts_controller.rb
create test/functional/posts_controller_test.rb
create app/helpers/posts_helper.rb
route map.resources :posts
OK, so they both generated the same files. In theory, if I use the find technique above to find all the files in either the exp directory or the control directory I can get a listing of all the files that both directories share. Using the foreach diff technique described above, I should be able to find out exactly what’s different between the two.
Rehash: Build file list, find diffs, remove the session key difference data we saw above, that’s OK to be different. Keep in mind, below, when you see a “>” character, it means “this is something the control has that the experimental does not”.
Output below:
Diffing: [exp/app/views/posts/edit.html.erb] to [control/app/views/posts/edit.html.e
6a7,21
> <b>Title</b><br />
> <%= f.text_field :title %>
> </p>
>
> <p>
> <b>Body</b><br />
> <%= f.text_area :body %>
> </p>
>
> <p>
> <b>Created at</b><br />
> <%= f.datetime_select :created_at %>
> </p>
>
> <p>
Diffing: [exp/app/views/posts/index.html.erb] to [control/app/views/posts/index.html
4a5,7
> <th>Title</th>
> <th>Body</th>
> <th>Created at</th>
8a12,14
> <td><%=h post.title %></td>
> <td><%=h post.body %></td>
> <td><%=h post.created_at %></td>
Diffing: [exp/app/views/posts/new.html.erb] to [control/app/views/posts/new.html.erb
6a7,21
> <b>Title</b><br />
> <%= f.text_field :title %>
> </p>
>
> <p>
> <b>Body</b><br />
> <%= f.text_area :body %>
> </p>
>
> <p>
> <b>Created at</b><br />
> <%= f.datetime_select :created_at %>
> </p>
>
> <p>
Diffing: [exp/app/views/posts/show.html.erb] to [control/app/views/posts/show.html.e
0a1,15
> <p>
> <b>Title:</b>
> <%=h @post.title %>
> </p>
>
> <p>
> <b>Body:</b>
> <%=h @post.body %>
> </p>
>
> <p>
> <b>Created at:</b>
> <%=h @post.created_at %>
> </p>
>
Diffing: [exp/db/migrate/001_create_posts.rb] to [control/db/migrate/001_create_post
3a4,6
> t.string :title
> t.text :body
> t.datetime :created_at
Diffing: [exp/test/fixtures/posts.yml] to [control/test/fixtures/posts.yml]
3,7c3,11
< # one:
< # column: value
< #
< # two:
< # column: value
---
> one:
> title: MyString
> body: MyText
> created_at: 2007-12-30 19:22:51
>
> two:
> title: MyString
> body: MyText
> created_at: 2007-12-30 19:22:51
Analyze differences
The files with a notable difference are these:
- /app/views/posts/edit.html.erb
- /app/views/posts/index.html.erb
- /app/views/posts/new.html.erb
- /app/views/posts/show.html.erb
- /db/migrate/001_create_posts.rb
- /test/fixtures/posts.yml
I’m going to take a stab that my problem is in the new.html.erb because, uh, my problems, thus far, have centered around what happens when I hit the new button.
bash-3.2$ cat control/app/views/posts/new.html.erb
<h1>New post</h1>
<%= error_messages_for :post %>
<% form_for(@post) do |f| %>
<p>
<b>Title</b><br />
<%= f.text_field :title %>
</p>
<p>
<b>Body</b><br />
<%= f.text_area :body %>
</p>
<p>
<b>Created at</b><br />
<%= f.datetime_select :created_at %>
</p>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
<%= link_to 'Back', posts_path %>
bash-3.2$ cat exp/app/views/posts/new.html.erb
<h1>New post</h1>
<%= error_messages_for :post %>
<% form_for(@post) do |f| %>
<p>
<%= f.submit "Create" %>
</p>
<% end %>
<%= link_to 'Back', posts_path %>
bash-3.2$
Well, this makes it pretty apparent what the problem would be. They both have the same error_messages_for line, so the difference is the way the form is defined. The control has all the widgets (text_field, text_area) all handily defined. From here we can see what’s causing the problem.
Running rake in the control will create the database entries ( yay! ) and the scaffolding has all the right widgets to take advantage of this database structure. When db:migrate command is passed to the experimental, the database structure changes, but when you try to interface with the scaffold, well, they don’t take advantage of the model because the template is pretty brain-dead.
To get things working rightly, we can test things manually.
- Modify the experiment’s migration file ( add t.string :title, t.datestamp… etc. )
- rake db:migrate both
- Transplant the control’s template files into the experimental’s template directory
- script/server the experimental
- It should work like expected.
And it does.
Step 10: Conclusions
Now this really messes with my normal use case with rails. I would come up with a model ( Movies, Posts, Cards, etc. ) and then I would create the migration. Back and forth I would go, creating new columns as needed. Due to the dynamic scaffolding, my views would adjust as necessary, now my views are stuck with my first guess on how to scaffold the data, and now they need to be updated.
Option: Once the “proper” columns are figured out, re-run the scaffold generation command “knowing what I wish I had known then”. That is, while your first execution was “script/generate scaffold post” you ultimately get to writing “script/generate scaffold post title:string foo:string bar:text”, etc.
Find a way to make rails re-sync the view components with the model ( Rails-y-er, I should think ).
So, to the documentation for script/generate I go.
script/generate —help
Points me to script/generate scaffold —help, the textual part of which is printed below:
Description: Scaffolds an entire resource, from model and migration to controller and views, along with a full test suite. The resource is ready to use as a starting point for your restful, resource-oriented application. Pass the name of the model, either CamelCased or under_scored, as the first argument, and an optional list of attribute pairs. Attribute pairs are column_name:sql_type arguments specifying the model's attributes. Timestamps are added by default, so you don't have to specify them by hand as 'created_at:datetime updated_at:datetime'. You don't have to think up every attribute up front, but it helps to sketch out a few so you can start working with the resource immediately. For example, `scaffold post title:string body:text published:boolean` gives you a model with those three attributes, a controller that handles the create/show/update/destroy, forms to create and edit your posts, and an index that lists them all, as well as a map.resources :posts declaration in config/routes.rb. Examples: `./script/generate scaffold post` # no attributes, view will be anemic `./script/generate scaffold post title:string body:text published:boolean` `./script/generate scaffold purchase order_id:integer amount:decimal`
Based on this, I think that the recommended way of handling this problem is to use a “more informed” script/generate command, but this seems to be clunky and rather un-agile.
I’ll confirm that with rails-list. I just wanted to save my research here for the benefit of others.
[…] stevengharms.net « Getting back on the Rails: Headaches around Rails 2.x […]
Glad my tutorial helped
I may come back and pinch some ideas from you now (in moving from Rails 1.x to 2.x we should all stick together!)
As I’ve said elsewhere, I think ‘breaking’ the scaffold from 1.x to 2.x was a mistake. While the 2.x version may (arguably) be an ‘improvement’, it also breaks all the existing tutorials and puts barriers in the way of newcomers. Speaking as the developer of a Ruby On Rails IDE, I may also add that even tiny ‘breaks’ (anything from the syntax of a script to the naming conventions of files - ‘.rhtml’ to ‘html.erb’) also breaks the IDEs (and forces us to divert our efforts into fixing things that weren’t hirtherto broken rather than getting on with developing new features). While I can understand the attractions of re-designing a system such as Rails to be more aesthetically pleasing to the developer, I must admit to serious reservations about some of the changes made in Rails 2.0. I only hope this is a one-off…
best wishes
Huw Collingbourne
What an excellent post. I’m going through the same thing with Rails 2.0
I was using 1.x a few months ago, but 2.0 is starting to get as bad as Java
I hit the same hurdle from 1.x to 2.x - it looks like the dynamic scaffolding is gone? I had the same “use case” where dynamic scaffolding would very helpfully update views during prototyping. Can a new generator be created for “dynamic scaffodling”?
Brian,
Sorry, it looks like we’re in the concrete from the get go same as if we’d done the generate static scaffold from 1.x.
Steven
I found the Scaffolding Extensions plugin - which looks like an even better solution and is available as a plugin:
http://wiki.rubyonrails.com/rails/pages/Scaffolding+Extensions+Plugin
http://wiki.rubyonrails.org/rails/pages/Scaffolding+Extensions+Tutorial
“I think ‘breaking’ the scaffold from 1.x to 2.x was a mistake. While the 2.x version may (arguably) be an ‘improvement’, it also breaks all the existing tutorials and puts barriers in the way of newcomers.”
Completely agree with the above. I was very close to give up on rails after spending a good 4 days trying to make my first application work. I was unfortunate enough to try rails after 2.02 was released. All of the documentation I found did not work. The beauty of ruby and rails keep me going. Finally I go it from blogs like yours and this one http://davidlynch.org/blog/2008/01/rails-20-scaffolding . Thanks!