Continuing with Rails

In my blog post on Rails "12 Lines of Code" I showed an overview of a *simple* rails application. OK, application might be a little strong, it was just a step or two ahead of a "Hello World"

One of the questions I had when I started looking at Rails was, how quickly does this run out of steam? Last year a I looked at some Java-based Rails-like tools. Frankly, I wasn’t impressed. It feels like Java has gotten so complex as it has grown into the tool for everything that it is too heavy for some uses.

So, let’s see what happens when we make a few changes to our application. How much re-work is required? What is the cycle time? What is the complexity?

Something that every DB-backed app has is data in multiple tables with relations between the tables expressed in one or more ways. Usually with Foreign Keys in the DB, in application logic, and ultimately in the user interface. A simple extension to our application would be to add a table of "meeting categories" that we can use to classify the type of meeting. We’ll repeat the process (eventually) to allow us to have a list of meeting atendees, a list of agenda items and discussions, and perhaps action items for folks at the meeting. Let’s get to it!

From the command line:

C:\devblogMeetingMinutes>ruby script/generate model Category exists app/models/ exists test/unit/ exists test/fixtures/ create app/models/category.rb create test/unit/category_test.rb create test/fixtures/categories.yml exists db/migrate create db/migrate/002_create_categories.rb C:\devblogMeetingMinutes>ruby script/generate controller Category exists app/controllers/ exists app/helpers/ create app/views/category exists test/functional/ create app/controllers/category_controller.rb create test/functional/category_controller_test.rb create app/helpers/category_helper.rb

Much like before, we generate a controller and a model. Let’s edit the just-created migration for our category model in 002_create_categories.rb:

class CreateCategories < ActiveRecord::Migration def self.up create_table :categories do |t| t.column :name, :string # ADD THIS LINE end end def self.down drop_table :categories end end

Edit 001_create_meetings.rb to add an id column to our table definition:

class CreateMeetings < ActiveRecord::Migration def self.up create_table :meetings do |t| t.column :meetingname, :string t.column :meetingdate, :date t.column :description, :text t.column :category_id, :integer # add this line end end def self.down drop_table :meetings end end

Now open the two models category.rb and meeting.rb. We need to tell Rails that there is a relationship between these two tables.

category.rb

class Category < ActiveRecord::Base has_many :meetings # tell rails about the relationship # from the category side end

meeting.rb

class Meeting < ActiveRecord::Base belongs_to :category # tell rails about the relationship # from the meeting side end

The takes care of the plumbing. What we did, in review, was:

  • Generate a new model (including a migration) and a new controller
  • Set up one column in the migration for the new model
  • Add one column to the meeting model
  • Define the relationship between the two models

The has_many and belongs_to methods are just two of several Rails methods for specifying relationships. There are others for many-to-many relationships, trees, lists and more. There are options to modify the default behavior, for instance adding cascading deletes automatically. We have just a few more details to take care of and we’ll be ready to try out our new zoomy app.

Notice above that we created a controller for our Category. This isn’t, strictly speaking, necessary. It just gives us a convenient place to plunk in our scaffolding for the new table — which will let us quickly add a few categories for testing. Let’s go ahead and add the scaffolding to category_controller.rb:

class CategoryController < ApplicationController scaffold :category # add this line end

Why don’t we stop here and check our work? If all goes well we should have CRUD views for our new category model. The meeting views should be unchanged — Rails is smart enough to know that the new column on meetings is part of a relationship, but not smart enough to know what we want to do with it in the view. From the command line we’ll re-create our database tables from ground zero, start the server and take a look. We’re re-creating the tables from zero by migrating the database to version 0, then running all of the migrations to bring it current. We’re doing this because we changed an already-applied migration. An alternate would have been to add the column through our newly-created migration. I’ve decided that I want to keep the table definitions together until I get to a release point. Subsequent changes to tables for the next release will be done via incremental migrations.

C:\devblogMeetingMinutes>rake db:migrate version=0 (in C:/dev/blog/MeetingMinutes) == CreateMeetings: reverting ================================================== — drop_table(:meetings) -> 0.0470s == CreateMeetings: reverted (0.0470s) ========================================= C:\devblogMeetingMinutes>rake db:migrate (in C:/dev/blog/MeetingMinutes) == CreateMeetings: migrating ================================================== — create_table(:meetings) -> 0.1250s == CreateMeetings: migrated (0.1250s) ========================================= == CreateCategories: migrating ================================================ — create_table(:categories) -> 0.1250s == CreateCategories: migrated (0.1250s) ======================================= C:\devblogMeetingMinutes>ruby script/server [snip]

The first migrate rolls the schema back to version 0, the second rolls it forward to the latest migration we have. A quick look at the create meeting form show that it is unchanged, and that we have the basic CRUD views for categories. Lets create a few categories, then go fix the views for the meeting.

So far, so good. We’re up to 21 lines of code. We’re going to stray from the land of dynamic scaffolding now and build a custom view. This will require adding a method in the controller for each view, and then creating a template file to actually render the view. Templates can be done as .rhtml (Ruby scripting to generate HTML), .rxml (Ruby scripting to generate XML) and .rjs (Ruby scripting to generate Ajax/Javascript). For this example we’re going to use the .rhtml flavor.

In app/views/meetingminutes create list.rhtml

OK, what is going on here? As you can probably guess, this iterates over something called @meetings and creates a table of meeting listings. Note that the meeting "knows" how to go look up the category name already! The link_to is a Rails helper method that will (in this case) show the meeting name and create a link to the "show" view. Where does the @meetings thingie come from? That is the controller’s job. The controller prepares the data for the view, like this:

class MeetingminutesController < ApplicationController scaffold :meeting def list # add this method @meetings = Meeting.find(:all) end end

OK, let’s check our work so far. We added a view called "list", which replaces the dynamic scaffolding "list". We added a "list" method to the controller to initialize the @meetings object. Let’s take a look at our work, if you still have the server running just browse to /meetingminutes/list, if the server isn’t running just start it.

That works, but we don’t have any meetings. Clicking the "create new meeting" link takes us to the "new" view generated by the dynamic scaffolding. Let’s go ahead and replace that now. We’ll need to add "new" and "create" methods to the controller first.

class MeetingminutesController < ApplicationController scaffold :meeting def list @meetings = Meeting.find(:all) end def new # add this method @meeting = Meeting.new @categories = Category.find(:all) end def create # add this method @meeting = Meeting.new(params[:meeting]) if @meeting.save redirect_to :action => ‘list’ else render :action => ‘new’ end end end

The new method creates a new instance of Meeting. The create method gets the data back from the view and tries to save it. At first glance it might look like we’re creating two meetings. Well, actually, we are. Only one actually gets saved to the database. The object created in new is used to initialize the form, the create method grabs the meeting back from the parameter stream, creates a new meeting object and saves it to the database. We still need a "new" view. It should look like this:

You can see that there is a lot of heavy lifting being done by Rails for us here. There are helper methods to create the form and the fields in the form. Rails takes care of passing the data back as a hash, and disecting it to sonctruct a new object. The call to form.collection_select is pretty cool, it creates the combo box, populates it and pre-selects the category if one is selected. That’s handy, because next time we’re going to refactor the body of this form into a Rails partial (partial view) that can be used for both creating new meetings and editing existing meetings. For now, here is our create form:

Notice that the create form has our combo box, and the list view has the category name pulled through the relationship. We’re still not into rocket science, but we have two database tables created, relationships between the tables, schema versioning, CRUD forms for both tables including hand built "list" and "new" forms that utilizes the relationship and we’re up to a whopping 37 lines of code.

We have only begun to take advantage of Rails. For example, adding field validation is trivial, it is just a few helper method calls in the model. We need to look at a few more areas of Rails to really begin to appreciate its power. Stay tuned!


Posted by Joe McGlynn on January 9th, 2007 under Ruby and Rails |



3 Responses to “Continuing with Rails”

  1. Mario Alejandro Montoya C. Says:

    Great.

    But if you think rails is great, then look at django: http://www.djangoproject.com/!

    I’m using it as is very-very addictive. It auto-generate the admin site (as in contrast to all other frameworks I know, is a very good one) and have a very easy to get ORM layer.

    Plus is python, and that have a ancient relationship with Delphi ;)

  2. Harriv Says:

    TurboGears is another feature loaded web application framework for Python: http://www.turbogears.org/

  3. David Glassborow Says:

    If you are interested in Ruby, its probably also worth looking at JRuby, the initiative to implement ruby running on the JVM. One of the key applications they test with is Rails. I’m interested in this for deploying Rails applications in corporate environments where Ruby would not get the OK, but Java is used everywhere.

    http://jruby.codehaus.org/

    Blog by one the developers (now working at Sun): http://headius.blogspot.com/

Leave a Comment


Server Response from: dnrh1.codegear.com

 
© Copyright 2008 Embarcadero Technologies, Inc. All Rights Reserved. Contact Us  |   Site Map  |   Legal Notices  |   Privacy Policy  |   Report Software Piracy