5.2. Scaffolding and Migrations

You can get a head start on building your simple blog engine by employing the scaffold generator. The guiding idea behind scaffolding is that you can use it to obtain a basic CRUD application that displays and manipulates the data within a table, without having to write a single line of code (as briefly seen in Chapter 1). This then becomes a foundation that can be customized and which allows you to build a more complex application that looks and behaves the way you want it to.

As a bare minimum your blog will need to allow you to list, show, create, delete, and edit articles. For each article, you should keep track of its title (which is a string), body (which is a bunch of text), whether or not it is published already (so a Boolean) and its publication date and time. To translate this idea into an actual application, go ahead and run the following command:

C:projectslog> ruby script/generate scaffold article title:string body:text
published:boolean published_at:datetime

article is the resource, and the pairs title:string, body:text, published:boolean, and published_at:datetime specify its attributes and their data types.

The output of this command resembles the following (except that the exists lines have been removed for clarity):

create  app/views/articles
   create  app/views/articles/index.html.erb
   create  app/views/articles/show.html.erb
   create  app/views/articles/new.html.erb
   create  app/views/articles/edit.html.erb
   create  app/views/layouts/articles.html.erb
   create  public/stylesheets/scaffold.css
   create  app/controllers/articles_controller.rb
   create  test/functional/articles_controller_test.rb
   create  app/helpers/articles_helper.rb
    route  map.resources :articles
dependency  model
   create     app/models/article.rb
   create     test/unit/article_test.rb
   create     test/fixtures/articles.yml
   create     db/migrate
   create     db/migrate/20080710224642_create_articles.rb

The scaffold generator creates folders and files for the ArticlesController, the Article model, and the view layer. It generates a few basic functional tests (for testing the controller) and unit tests (for testing the model), and modifies the config outes.rb file to map URLs to actions in the controller. Each of these are analyzed in detail, but focus first on the last two highlighted lines.

5.2.1. Migrations

The scaffold generator created a migrate directory inside the db one. Within it, it placed a file called, in this specific case, 20080710224642_create_articles.rb.

The file name is determined by a timestamp of the UTC time of creation, and the name of the table it will create, based on the resource (for example, article) you passed as an argument to the scaffold generator. The numeric part of your file name will therefore definitely be different. If it isn't, two of the following things are possible. Your computer time is wrong and you miraculously managed to get the same timestamp, in which case I suggest that you buy a lottery ticket; or you're a time traveler and I suggest that you go back to mid 2004 and start studying Rails back then. That too is arguably one way to win a lottery of sorts.

5.2.1.1. Using Ruby to Define Tables

This file defines the structure of the articles table with Ruby code. Take a look at the code of the 20080710224642_create_articles.rb file, which is automatically generated for you:

class CreateArticles < ActiveRecord::Migration
  def self.up
    create_table :articles do |t|
      t.string :title
      t.text :body
      t.boolean :published
      t.datetime :published_at

      t.timestamps
    end
  end

  def self.down
    drop_table :articles
  end
end

The create_table method on the third line is used in its block form. It accepts the table name as a symbol (that is, :articles) or a string (that is, "articles"), a series of options (missing in this particular case), and a block in which you can define columns by using the syntax t.datatype :column_name, where t is the argument of the block and it represents the table definition. Each column definition can have a series of options as well (not shown in this snippet of code).

You may notice that the scaffold generator added a t.timestamps line as well. This adds two special columns called created_at and updated_at that are automatically handled by Rails. The first stores the date and time for when an instance of the Article model (a row) gets created, and the second one stores the date and time of its last update. You can remove this feature if you wish, but there is usually no harm in keeping it.

You might be wondering why we needed to specify a published_at attribute, if we have a created_at column "for free." In general we don't, but in this particular case I wanted to provide the author of the blog with the ability to schedule the publication of a post in the future, as you'll see later on.

5.2.1.2. Rails Data Types

The mapping of Rails data types with the actual database types is defined within the Active Record's adapter for the database that's in use. For example, the DB2 adapter I created for IBM supports an xml data type, but this will not be available for your SQLite3 database.

A few exceptions aside though, the following data types are native: binary, boolean, date, datetime, decimal, float, integer, string, text, time, and timestamp. There is also a special type called primary_key.

string is usually implemented as a varchar (or equivalent) with a default limit of 255 characters. This limit can be overwritten by passing a hash of options containing the :limit symbol as a key to the column definition method (for example, t.string :name, :limit => 80) to the string.

The following are the available options when it comes to defining columns:

  • :limit defines the maximum length for the column. This option is supported by default by the following data types: string, text, integer, and binary.

  • :default defines the default value for the column. If NULL is the desired default value, use :default => nil.

  • :null defines whether or not null values are accepted in the column. By default they are (except for primary_key, of course), but you can specify :null => false if you wish to not permit them.

  • :precision and :scale are two options for decimal columns. They can be used independently, but often go together (for example, t.decimal :budget, :precision => 15, :scale => 2).

You may be confused by date, datetime, timestamp, and time all being very similar data types. The sqlite3 adapter, for example, maps all of them, except date, to the SQL datetime data type, so you might as well consider timestamp and time as aliases for datetime. Other adapters (like mysql and postgresql) may map datetime and timestamp to the same SQL data type, but have date and time mapped to distinct SQL data types. Don't worry too much about this though, because you can always inspect a table to verify what these "abstract" data types actually map to in the definition of your table in the database.

As a Microsoft developer you are probably interested in the mapping of the data types from Active Record to Microsoft SQL Server. The following table describes mapping between Active Record and the SQL Server Adapter.

Active RecordMicrosoft SQL Server
primary_keyint NOT NULL IDENTITY(1, 1) PRIMARY KEY
binaryimage
Booleanbit
datedatetime
datetimedatetime
decimaldecimal
floatfloat
integerint
stringvarchar
texttext
timedatetime
timestampdatetime
rakedb:migrate

Remember how the rails command generated a configdatabase.yml file, but you had to use a Rake task to create the actual databases? The same principle applies here. The scaffold generator created a migration file containing the table definition, but didn't actually create the table in the database. To do that you'll need to use the Rake task db:migrate. So run the following to create the articles table:

C:projectslog> rake db:migrate
(in C:/projects/blog)
== 20080710224642 CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.1090s
== 20080710224642 CreateArticles: migrated (0.1250s) ==========================

The preceding output shows that the table was created and there were no errors whatsoever. Once again, given that you didn't specify otherwise with a RAILS_ENV argument, the table was created in the development database.

Open the SQLite3 console as follows (also available through sqlite3 db/development.sqlite3):

C:projectslog> ruby script/dbconsole
SQLite version 3.6.10
Enter ".help" for instructions
Enter SQL statements terminated with a ";"sqlite>

Now you can show the tables within the database with .tables:

sqlite> .tables
articles           schema_migrations

And see the table definition for the articles table with .schema:

sqlite> .schema articles
CREATE TABLE "articles" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "title"
varchar(255), "body" text, "published" boolean, "published_at" datetime,
"created_at" datetime, "updated_at" datetime);

Use .exit to get out of the SQLite3 console.

As you can see in the SQL query, all the columns for the attributes specified as arguments of the scaffold generator, plus created_at and updated_at, are there; no surprise. The table also has a primary key called id. As mentioned before, this is an ActiveRecord convention, and it's automatically defined whenever you create tables by specifying their definition with the create_table method. This method allows you to overwrite the convention by either specifying :id => false or by indicating a different name for the primary key (for example, :primary_key => 'guid'). It's worth repeating that unless you have a good reason for overwriting a given convention (and occasionally you will), it's better to stick with what ActiveRecord, and more generally Rails, expects.

5.2.1.3. The schema_migrations Table

When you listed the tables in the SQLite3 console, you may have noticed that there was a schema_migrations table next to articles. This table is created by the db:migrate task if it doesn't already exist. If it does exist, it's used by the task to determine which migration files need to be applied to the current schema in the database. The table schema_migrations has a single column called version and every time a migration file is applied, the timestamp within its name gets stored as a string in the table.

When I ran rake db:migrate on my machine, the table schema_migrations was first created, then the class method up defined in 20080710224642_create_articles.rb was invoked, creating therefore the table articles, and lastly the value 20080710224642 was inserted into the schema_migrations table.

SELECT version FROM schema_migrations gets executed before and after each migration. This helps, for example, to ensure that a file hasn't already been migrated meanwhile by a different developer in your team.

5.2.1.4. Migrating Up and Down

Let's try to better understand how migrations work and why they are a very useful tool. In a traditional ASP or ASP.NET environment, you would define the database objects through a series of SQL scripts (T-SQL if using SQL Server) or directly from a GUI application. Any change or rollback to the database schema would typically have to be performed "manually."

Rails believes in the idea of defining database objects through Ruby code as much as possible, so migrations are a way to define and manipulate tables through Ruby instead of SQL as seen earlier (well, at least, in most cases). But migrations are much more than that.

Every time a migration file is generated (through scaffold or not) its name will be "timestamped." In this case, for example, the timestamp was 20080710224642, which is the UTC time in the year, month, day, hour, minute, and second format.

If at a later stage you add a second migration file that adds a column to the existing table, the timestamp for this file will be successive to the previous one. What this means is that the db:migrate task will execute them in succession, which is what you want.

scaffold is not the only generator that creates migration files. Any generator, whether it's built-in or provided by a plugin, that requires an alteration of the database somehow, will generate migration files. There is also a migration generator, which simply creates a migration file for you. We talk more about this in Chapter 7.

The fact that schema_migrations holds all the timestamps for the migration files already migrated means that the db:migrate can compare them with the files within the dbmigrate directory and determine which migration files need to be migrated. It will pick up migrations that are newer as well as migrations that have older timestamps, but which have not been run yet perhaps because they were committed at a later stage by a different developer in the team or are being merged from a different branch.

Running rake db:migrate runs all the migrations that haven't been executed yet, but you can also migrate or rollback up to a specific version (for example, rake db:migrate VERSION=20080711104321). You can rollback to the previous migration level if you realize you made a mistake by using db:rollback too. Specifying a STEP= argument, you can rollback a number of steps in the migration history, instead of passing a specific version number (for example, rake db:rollback STEP=3). Every time you migrate up or down, the entries within the schema_migrations change to keep track of which migration files were applied to the current schema.

5.2.1.5. Old Style Migrations

Before Rails 2.1 was released, migrations worked in a similar fashion (aside from a few methods introduced by Rails 2.1) but were not time-stamped and as such were more susceptible to collisions. The first migration file of a project had the 001 prefix instead of a timestamp, and each new migration file had a prefix that was incremented by one. At the time there was a schema_info table that kept track only of the latest version you'd migrated to, as opposed to schema_migrations in Rails 2.1 and successive, which keep tracks of every migration that you've run.

The problem with this approach was that it introduced unnecessary collisions. For example, say that the last migration file committed to the repository of the project is 011_create_users.rb. Now if you add a migration file to your local copy of the repository, this will be, say, 012_create_assets.rb. If another developer needs to add a new table called tasks, he will create, say, 012_create_tasks.rb in his local copy of the repository. Then you both commit to the repository. When migrating up, you'll encounter a conflict because both of your commits will be prefixed by 012, which is the version that db:migrate is supposed to migrate to (given that 11 is the version stored in the schema_info table).

In reality your changes may be entirely independent, so it wouldn't really matter whether or not your migration gets executed first. With timestamp-based migrations á la Rails 2.1, the probability of this type of collision is exceptionally slim, so the migrations for adding assets and tasks would have a different timestamp and could be migrated without encountering any problems.

Unnecessary collisions, where the order of the two migrations is irrelevant, are removed, but even the timestamp-based migrations can still have collisions that need to be resolved manually. For example, if a migration drops a certain table, and a second migration (from another developer who doesn't know about your change) adds a column to that table, the order of execution matters. If your migration has a timestamp older than the one of the other developer, you'll encounter a conflict when migrating up, because rake db:migrate will drop a table and then attempt to add a column to that table that was just dropped, resulting in an error.

Conceptually unavoidable conflicts aside, migrations are still much more convenient than modifying the database directly through SQL scripts. With that in mind, developing in Rails without using migrations is still perfectly possible.

5.2.1.6. The up and down Class Methods

If you take a look again at the migration file for the creation of the articles table, you'll notice that the CreateArticles migration inherits from ActiveRecord::Migration and defines two class methods, up and down:

class CreateArticles < ActiveRecord::Migration
  def self.up
    create_table :articles do |t|
      t.string :title

t.text :body
      t.boolean :published
      t.datetime :published_at

      t.timestamps
    end
  end

  def self.down
    drop_table :articles
  end
end

If db:migrate determines that this file should be included in the migration to evolve the schema of the database, CreateArticles.up gets invoked. When you are migrating down or rolling back to a schema version that predates this file, the effect of the up method gets cancelled out by invoking the CreateArticles.down method. You can also explicitly invoke the up and down methods of a specific migration by using the db:migrate:up and db:migrate:down tasks and passing a VERSION= argument to it (for example, rake db:migrate:down VERSION=20080711104321).

It is important that you define the opposite action of what you're doing in the up method in the down method of the migration, so that it can be safely rolled back. If you are adding a column through the add_column method, then in the down method you should use remove_column. If, like in this case, you are creating a table articles in the up method, then to restore the database to its previous state in case of a rollback, a drop_table :articles within the down method is required. If you are changing a column in the up method, change it back in the down method. You get the idea.

Migrations offer several handy methods to manipulate database objects. We cover them in Chapter 7.

Migrations are a concept that sounds complicated in theory, but that is very simple in practice. You ran rake db:migrate to create the articles table, but as you work with the application, you'll use migrations again to evolve the schema and add, at a bare minimum, a second table. You'll see that it couldn't be easier and migrations are a great tool for handling the incremental evolution of the schema in an "as automated as possible" way.

5.2.2. Putting It All Together: Creating a Rails Application

Before showing you (in the browser) what the scaffold generator actually accomplished, let's recap the steps that were required to achieve this. Migrations were discussed at length and introduced several new concepts, but don't get the false impression that scaffolding is hard. It's not. So far you haven't actually written a single line of code, and the following table shows you the commands employed up to this point.

CommandMeaning
rails blogGenerates a blog folder that contains the skeleton of the Rails application.
rake db:create:allCreates all the local databases defined in configdatabase.yml.
ruby script/generate
scaffold article title:string
body:text published:boolean
published_at:datetime

Generates an entire basic application for handling CRUD operations on the articles table.
rake db:migrateRuns the migration file defined by scaffold, creating the articles tables.
ruby script/serverRuns the Web server to serve the Rails application.

The last step in the table should be new to you. This is used to start a Web server to serve your Rails application. By default this is WEBrick, but if you installed Mongrel in the first chapter, it will automatically be picked up, providing you with a smoother and quicker experience as you try out the application in development mode.

If you are not using Windows, you can simply run ./script/server from the main folder of your project.

Go ahead and start the Web server in development mode:

C:projectslog> ruby script/server
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
=> Rails 2.2.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  INT => stop (no restart).
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.

If you are using Windows Vista you may have to approve the operation in a security popup that appears.

This will make the application available in development mode, on the localhost, on port 3000. These defaults can be changed of course, as shown by the output here:

C:projectslog> ruby script/server  --help
=> Booting Mongrel (use 'script/server webrick' to force WEBrick)
Usage: server [options]
    -p,  --port=port                  Runs Rails on the specified port.
                                      Default: 3000
    -b,  --binding=ip                 Binds Rails to the specified ip.
                                      Default: 0.0.0.0
    -d,  --daemon                     Make server run as a Daemon.
    -u,  --debugger                   Enable ruby-debugging for the server.
    -e,  --environment=name           Specifies the environment to run this server

under (test/development/production).
                                      Default: development

    -h,  --help                       Show this help message.

Now that you have the server up and running, take a look at your application. Direct your browser to http://localhost:3000. You should see the Rails' welcome message as shown in Figure 5-3.

If you are using Internet Explorer, you may be prompted with an information bar warning you about intranet settings. Click it and follow the instructions to make it disappear.

Figure 5.3. Figure 5-3

If you can see it on your screen, congratulations, it means that you are up and running with Rails. There is however, a problem: this has nothing to do with your articles table. The reason why this page is being served instead of the promised application, which is supposed to handle articles, is that the rails command generates a publicindex.html static file. By the same token, scaffolding mapped the address /articles, not /, to ArticlesController. Long story short, head over to http://localhost:3000/articles and you should be able to see a page that lists articles as shown in Figure 5-4.

If you take a look at the command prompt where you are running Mongrel, you'll notice a few pieces of information that have been logged for the request it just served. In this case:

Processing ArticlesController#index (for 127.0.0.1 at 2008-07-11 05:12:38) [GET]

  Đ[4;36;1mArticle Load (0.0ms)Đ[0m   Đ[0;1mSELECT * FROM "articles" Đ[0m
Rendering template within layouts/articles
Rendering articles/index
Completed in 16ms (View: 0, DB: 0) | 200 OK [http://localhost/articles]

Figure 5.4. Figure 5-4

Being able to see all this information is great when trying to figure out why something isn't working as expected. And given that you are in development mode, the file logdevelopment.log will contain the same (and more) information as well. If you were in production mode, the actual SQL query would not be logged.

Click New Article and you should see a form as shown in Figure 5-5.

Figure 5.5. Figure 5-5

It may not be the prettiest form that you've ever seen, but it's fully functional, and again, you didn't have to write a single line of code. Notice also how the URL changed from http://localhost:3000/articles to http://localhost:3000/articles/new. Let's add a title, some text, mark off the published checkbox, and click the Create button. This will create a record in the articles table and redirect you to http://localhost:3000/articles/1, where the record (which has id 1 in the table) is shown, as you can see in Figure 5-6.

Figure 5.6. Figure 5-6

Also notice how a confirmation message, "Article was successfully created." appears on the page.

Again, perhaps it's not the presentation that you intended for the final application, but it works and the aesthetics can always be customized later. If you click the Edit link you're brought to the address http://localhost:3000/articles/1/edit, from which you can update the record. Clicking Back brings you back to http://localhost:3000/articles, which lists all the records (well, you only have one record so far) as shown in Figure 5-7.

Figure 5.7. Figure 5-7

From there you can show records (Figure 5-6 without the "Article was successfully created." message), edit them, and even destroy them (upon clicking OK on a confirmation message box). So there you have it, not a single line of code (so far) and you have a front-end that's ready to perform CRUD operations on the back-end.

It doesn't look or behave like a blog quite yet, but that's not a problem; to fix that you can use incremental development. You can use this base and customize it for your needs. Furthermore, scaffolding doesn't just create a bunch of forms for you; it enables an application to work as a Web Service (a RESTful one, as you'll see in the next section). In fact, if you append an .xml to http://localhost:3000/articles/1 you'll obtain an XML representation of the first record in the articles table, as shown in Figure 5-8.

Figure 5.8. Figure 5-8

Before explaining the code that makes all this magic possible, there is one thing that annoys me. When I head over to http://localhost:3000 I don't want to see a welcome page, but would prefer to get a list of all the articles. This can be accomplished in two steps. First you can edit your config outes.rb file in order to include the highlighted line:

ActionController::Routing::Routes.draw do |map|
  map.root :controller => "articles"
  map.resources :articles

  # ...

This maps the root path of the application (that is, /) to the Articles controller. The second step is to delete the publicindex.html file, because being a static HTML file it would have precedence over the index action of the Articles controller (which gets invoked by default when you visit /articles or, from now on, when you visit / as well).

It is customary to say "Articles controller," even if the actual name of the class is "ArticlesController." They are used interchangeably throughout this book.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.15.43