Generating view scaffolding code for existing models
(Updated October 2009)
I just rewrote and cleaned up the code I describe below here and published it as a new gem called View Mapper on gemcutter.org. See my usage article for more details. I also rethought and redesigned the commands I describe below… the part about nested attributes will not work for now. I’m planning to reimplement that soon.
I’ve been thinking for a while that a generator that creates view scaffolding for an existing model or models would be really useful. For example, I want to be able to write this by hand:
class Group < ActiveRecord::Base has_many :people end class Person < ActiveRecord::Base belongs_to :group end
… and then run a generator and get a working view to edit and display groups of people. Recently I started developing a generator called “ViewMapper” that does this; see: http://github.com/patshaughnessy/view_mapper.
The idea is that it creates a view that maps to your existing models. Writing model classes is often very easy; using ActiveRecord allows you to write concise, short code files. However, writing a view that displays a form for these models is often very hard, requiring knowledge of a long list of Rails functions, macros, classes, as well as the usual HTML and Javascript. Right now ViewMapper it just a plugin; so you can install it into your app like this:
$ ./script/plugin install git://github.com/patshaughnessy/view_mapper.git
Probably it should be packaged as a gem instead… this is still work-in-progress. At the moment it supports:
- Creating simple form based on the columns of an existing model
- Creating a complex form based on the columns of two associated models in a many-one relationship, using nested attributes
Example 1: Create a view based on the columns of an existing model
Let’s say I have an existing model class that manages a series of person records:
class Person < ActiveRecord::Base end
Now to create the corresponding view with ViewMapper, just run this command:
$ ./script/generate view_for person exists app/controllers/ exists app/helpers/ create app/views/people exists app/views/layouts/ exists test/functional/ create test/unit/helpers/ exists public/stylesheets/ create app/views/people/index.html.erb create app/views/people/show.html.erb create app/views/people/new.html.erb create app/views/people/edit.html.erb create app/views/layouts/people.html.erb create public/stylesheets/scaffold.css create app/controllers/people_controller.rb create test/functional/people_controller_test.rb create app/helpers/people_helper.rb create test/unit/helpers/people_helper_test.rb route map.resources :people
If you run your Rails app you will see the usual scaffolding UI:
This looks just like the scaffolding page you would have gotten from the Rails scaffolding generator. In fact, I’ve based the “view_for” generator class (ViewForGenerator) on the existing Rails ScaffoldGenerator class, so it generates the same code.
The difference here is that the scaffolding code was based on the properties of the existing Person model. Here’s what the view_for generator did:
- Find the specified model class
- Inspect it and get a list of column names and data types
- Create the scaffolding code as usual
$ ./script/generate scaffold person name:string age:integer
ViewMapper provides you with the flexibility to create the view scaffolding after the fact for a model you or someone else has already created in your app.
Example 2: Create a view using a complex form based on two associated models in a many-one relationship, using Rails 2.3 nested attributes
Let’s suppose you have two related models that describe groups of people:
class Group < ActiveRecord::Base has_many :people end class Person < ActiveRecord::Base belongs_to :group end
Again, writing model classes is really easy. With just a few lines of code I have told ActiveRecord to manage two separate tables and their relationship.
Now you can use ViewMapper to create a complex form for creating and editing groups and people at the same time like this:
$ ./script/generate nested_attributes_view_for group containing:people error Model 'Group' does not accept nested attributes for child models 'people'
This error indicates that my Group model is missing the “accepts_nested_attributes_for” directive. Since the generator creates view code that relies on the nested attributes feature, it won’t let you create the view for a model that is missing this line. This sort of helpful error message is possible since ViewMapper is inspecting an existing model class, and not just creating new scaffold code.
Now if we add the missing line (suddenly my model is less concise... nested attributes aren't that easy to use yet!):
class Group < ActiveRecord::Base has_many :people accepts_nested_attributes_for :people, :allow_destroy => true, :reject_if => proc { |attrs| attrs['name'].blank? && attrs['age'].blank? } end
…we can re-run the generator:
$ ./script/generate nested_attributes_view_for group containing:people exists app/controllers/ exists app/helpers/ create app/views/groups exists app/views/layouts/ exists test/functional/ create test/unit/helpers/ exists public/stylesheets/ create app/views/groups/index.html.erb create app/views/groups/show.html.erb create app/views/groups/new.html.erb create app/views/groups/edit.html.erb create app/views/layouts/groups.html.erb create public/stylesheets/scaffold.css create app/controllers/groups_controller.rb create test/functional/groups_controller_test.rb create app/helpers/groups_helper.rb create test/unit/helpers/groups_helper_test.rb route map.resources :groups
And now if we run our app, we get view scaffolding illustrating how to create and edit people and groups all at the same time:
If I save the group…
And re-editing the same group:
More to come… as I said this is work-in-progress. As a next step I’m planning to improve the scaffolding code for nested attributes by adding javascript to dynamically add/delete the child objects. But after that I'm thinking about:
- Adding a “paperclip_view_for” generator that creates a file upload form for a model that contains a “has_attached_file” directive.
- Adding a “auto_complete_view_for” generator that adds “auto_complete_for” to the controller and uses “text_field_for_auto_complete” in the form.
- Adding the ability for you to extend this with your own scaffolding code.