ConfigurationElementCollection Gotcha

November 1st, 2008

Here’s a fun way to waste 2 hours of your life. I was writing a custom config section, and trying to figure out why this wasn’t throwing a duplicate key error:

<monitors>
	<monitor name="File" />
	<monitor name="File" />
</monitors>

After surfing Google endlessly, I finally found this on MSDN:

Note that elements with identical keys and values are not considered duplicates, and are accepted silently. Only elements with identical keys but different values are considered duplicates.

That was fun. :-/

The Laptop Mafia

October 6th, 2008

Long story longer. A group of various friends hang out at a local coffee shop working on their mostly-Apple laptops. Someone referred to use as the ‘laptop mafia’. In a cool twist of fate, laptopmafia.com was available.

We’re working on it now and it’s being built using Django. That’s fun learning considering we’re all mostly .NET people. Just got the setup and directories and svn configured.

CsvDataReader Updates

September 25th, 2008

A few batches of udates to CsvDataReader this week. I added support for schema.ini files including all of the TabDelimited, CsvDelimited, FixedLength and Delimited(*) formats. I also added support and tests for using Streams.

Bugs and maybe some option flags not withstanding, I think its done for now.

CsvDataReader

September 17th, 2008

Yes, there a few of these out there. Some free. Some not free. Some with IDataReader. Some without IDataReader. Some support IsDbNull. Some insist on always using Empty. Some are NotInheritable. Some are.

I wanted a mix of those features and I also wanted to define the columns manually at times using the existing DataColumn bits from the Data namespace.

Thus, the gazillionth CsvDataReader was born. Use it. Steal it. Send patches.

MVC Marathon Part 3: Creating a Restaurants Controller and View

August 10th, 2008

Welcome to part 3 of MVC Marathon, a multipart excursion into creating an application in the major MVC frameworks available today.

The source code for this part can be found here: http://github.com/claco/mvc-marathon/tree/part3/

Part 3: Creating a Restaurants Controller and View

In our last installment, we created a database, schema and model to get at our list of restaurants. In this episode, I’m going to create a controller and view that uses that model to list the restaurants in our database. Where available, I will also use any existing CRUD/scaffolding to enter some test data.

You can jump to any specific framework using the links below.

ASP.NET MVC

While .NET 3.0 and MVC have made a lot of strides with LINQ and Entity Framework (ORM), they completely lack any kind of scaffolding or helpers for data management. I’m sure someone is working on it, and there are 3rd party package available. For now, we’ll stick to just creating a controller for the restaurants.

Update: I dropped the ball on this one. There is the new Dynamic Data scaffolding in .NET 3.5. It appears that you can create an entirely separate Dynamic Data site, or try and work it into an existing site. As it stands, a separate site doesn’t count as part of the MVC solution, and the steps to integrate it are woefully long and complicated. I declare that the same as no real new user friendly solution for the context of this series.

Creating the Data

Before we create our controller, we need to add some test data. First, Double-Click the BurningPlate.mdf file in the App_Data directory to open the Server Explorer pane. Right-Click on the Restaurants table and select “Show Table Data”.


create data

Now enter two restaurants into the Name field: “Siamone Thai” and “Azteca”.

Creating the Controller

Now that we have our test data, we’re going to create a controller to list that data. Right-Click on the Controllers folder and choose “Add New Item”.


create controller

Select “MVC Controller Class”, type RestaurantsController.cs for the name and click “Add”. By convention in .NET MVC, the controller class name should always end in Controller. The default routing will assume that any controller path named /foo/ will map to the controller FooController. This of course can be changed by tweaking the routing, but why create more work for yourself?

Much like inflection to relate models to tables automatically, controller and view naming is typically referred to as “convention or configuration”. You can follow naming conventions and have things “Just Work”, or you can usually deviate from the norm and use configuration to over come situations where you need t name things differently.

Update: The file name in the add item dialog above is wrong. :-)


new controller

Now we have a shiny new controller class that by default spits out a NotImplementedException. Let’s add some code to get a list of restaurants and feed that to a view.


new controller code

public ActionResult Index()
{
BurningPlateDataContext db = new BurningPlateDataContext();
List<Restaurant> restaurants = db.Restaurants.ToList();

return View(restaurants);
}

In the code above, all we’ve had to do is instantiate an instance of the BurningPlateDataContext class that was auto generated when we created our Model. Then we asked it for a list of restaurants using ToList. Finally, we passed that list of restaurants to the View method.

Explore the View method when you have time. It has many options, and there is more than one way to get data into a view. In our case we’ve chosen to pass the restaurants list using the Model parameter. You can also pass it in the ViewData itself. I personally think that the main model data should be passed in the Model, and ViewData is reserved for all of the extra information like page titles, meta tag keywords, links, etc.

Creating the View

Now that we have a controller that assembles the data, we need a way to display it. My much like the routes to controller mappings, .NET MVC will look for a view matching the ControllerName/Action path. Since our controller is Restaurants and our action is Index, we need to create an Index view in the Restaurants folder.

Just like the routing naming conventions, this can also be customized to use views with alternate names and in alternate locations using the various View method options.

To create the view, Right-Click the Views folder and create a folder called “Restaurants”. Right-Click that new folder and select “Add Item”.


new view

Because .NET MVC supports master pages (wrapper templated layouts) by default, select “MVC View Content Page” and name it after our default RestaurantController method “Index” and click “Add”. You will then be prompted to select a master page. Select the “Site” master page in the Shared folder and click “OK”.


new view master select

Now that we have a new view page, we need to tell it about the type of model data we’re passing it from the controllers View method call in the controller. In our case, we’re passing a list o restaurants. Right-Click on your new Index.aspx page and select “View Code”.


new view model type

public partial class Index : ViewPage<List<Restaurant>>

To tell the view to expect a list of restaurants, we simply add a type definition to the view class definition. In our case, that means changing ViewPage to ViewPage<List<Restaurants>>. This tells intellisense that when accessing the ViewData.Model property that it is a List of Restaurants objects.

Now that we have told the page what the model is, we simply need to add a loop to the template to display the list of restaurants. Double-Click your Index.aspx file to open the page view.


new view model loop

<ul>
    <% foreach (var restaurant in ViewData.Model) { %>
        <li><%= restaurant.Name %></li>
    <% } %>
</ul>

In our view, we added a loop against the ViewData.Model and printed out each restaurants name in an unordered list.

Now that we have a controller and a view, let’s check out our work by going to /restaurants/.


new view model browser

As a final touch, let’s add a link to the home page to get to our new restaurants index view. To do that, open the Site.Master view and between the Home and About Us links, add a call to Html.ActionLink to build our link.


new view model browser

<li>
<%= Html.ActionLink("Restaurants", "Index", "Restaurants")%>
</li>

Hardcoding links suck. MVC takes care of this by using the routing tables to create a link using the controller and action names. This means that if the controller or action names change, the links to then automatically change as well. No need to go rooting through code to update links.

Let’s check out our link to make sure it works.


new view model browser

CakePHP

Unlike .NET MVC, CakePHP has CRUD scaffolding already built into
the framework. Because of this, we’re going to create our
controller first and then create our data.

Creating the Controller

Just like when we created our model, we’ll use
cake bake to create the controller for us.

claco@mbp ~/mvc-marathon/cakephp/BurningPlate $ cake bake

Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
App : BurningPlate
Path: /Users/claco/mvc-marathon/cakephp/BurningPlate
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[Q]uit
What would you like to Bake? (D/M/V/C/P/Q)
> C
---------------------------------------------------------------
Bake Controller
Path: /Users/claco/mvc-marathon/cakephp/BurningPlate/controllers/
---------------------------------------------------------------
Possible Controllers based on your current database:
1. Restaurants
Enter a number from the list above, type in the name of another controller, or 'q' to exit
[q] > 1
---------------------------------------------------------------
Baking RestaurantsController
---------------------------------------------------------------
Would you like to build your controller interactively? (y/n)
[y] >
Would you like to use scaffolding? (y/n)
[n] > y

---------------------------------------------------------------
The following controller will be created:
---------------------------------------------------------------
Controller Name:  Restaurants
var $scaffold;
---------------------------------------------------------------
Look okay? (y/n)
[y] >

Creating file /Users/claco/mvc-marathon/cakephp/BurningPlate/controllers/restaurants_controller.php
Wrote /Users/claco/mvc-marathon/cakephp/BurningPlate/controllers/restaurants_controller.php
Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n)
[y] >

You can download the Cake test suite from http://cakeforge.org/projects/testsuite/

Baking unit test for Restaurants...

Creating file /Users/claco/mvc-marathon/cakephp/BurningPlate/tests/cases/controllers/restaurants_controller.test.php
Wrote /Users/claco/mvc-marathon/cakephp/BurningPlate/tests/cases/controllers/restaurants_controller.test.php
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[Q]uit
What would you like to Bake? (D/M/V/C/P/Q)
> Q

Here we’ve asked to create a new controller. Cake knew what models
and tables we have and prompts us to choose one. It also asked if
we want to use scaffolding and create unit tests.

<?php
class RestaurantsController extends AppController {

var $name = 'Restaurants';
var $scaffold;
}
?>

As we see form the controller that was created, the only code
needed to CRUD data is var $scaffold;. Also, just
like .NET MVC, the class is named RestaurantsController, which
routing will map to the /restaurants/ path.

Creating the View

Because we’ve chosen to use scaffolding, the views and all of the
necessary code needed to maintait the restaurants table is already
taken care of. Let’s take a look at what it provides:


cake list


cake new


cake list

With one variable, we have a full working scaffolding to manage

our data. Now, let’s add a link to the front page going to our
new restaurants controller.

To add our link we need to edit the home.ctp page in the Views
fåolder.

<a href="<?php echo Router::url(array('controller' => 'Restaurants', 'action' => 'index'));?>">Restaurants</a>

Just like .NET MVC, we’re using the routing information to
generate a link rather than hard coding it, passing in the
controller name and the action we’re linking to. Now that should
appear on the default home page.


new view model browser

Catalyst

Because Catalyst doesn’t force any specific ORM upon you, that
also means that there is no official CRUD or scaffolding support
out of the box. Depending on the ORM you choose, there are some
pretty good options.

Class::DBI users have Catalyst::Enzyme.
Rose::DB users have Rose::DBx::Garden::Catalyst
(based on CatalystX::CRUD). While there are CatalystX::CRUD Models
, you still have to write forms views and processing for it.
There is also Catalyst-Example-InstantCRUD seems to be old and dead.
There’s also CatalystX::ListFramework and CatalystX::ListFramework::Builder
which requires some wireup work for the former and the latter
creates an entire application, apparently separate from the public application.

Since we’re using DBIx::Class, and the current options don’t meet
our needs without a divergence from this series, we’ll skip CRUD
scaffolding for the time being.

Creating the Data

Since we have no CRUD scaffolding, we need to create the data the
old fashioned way. You have to choices. First, use the dbicadmin script
to run insert statements. Second, use the sqlite command line
utilities to run SQL insert statement. I chose the latter. I won’t
cover that utility usage here.

To add the CRUD scaffolding to our add, we need to load the CatalystX::ListFramework::Builder plugin, point it to our extjs javascript files and tell it to live in the admin url space.

To load the plugin, simple add it to the use Catalyst line in your application file:

# Start the application
__PACKAGE__->setup(qw/
-Debug
ConfigLoader
+CatalystX::ListFramework::Builder
Static::Simple
/);

Now we need to tell it where our ExtJS files are located. We do this by adding enries to our configuration file for the plugins root controller. This is covered in its documentation. While we’re in the config file, we can also tell it to put the CRUD in the admin url namespace:

extjs2   /static/extjs
<Controller::LFB::Root>
<action>
<base>
PathPart admin
</base>
</action>
</Controller::LFB::Root>

Currently, the CRUD requires it’s own Model to access the database. I expect this will change in the future. In the mean time, we can just create a model that inherits from the model we’ve previously created.

package BurningPlate::Model::LFB::DBIC;

use strict;
use base 'BurningPlate::Model::DB';

1;

The latest versions of LFB automatically detect any models that use DBIC::Schema and adds them to the main admin index page.

Now fire up your application and go to the admin url you specified in config. From there we can use the interface to add data.


catalyst list


catalyst add


catalyst add


catalyst list

Creating the Controller

To create the controller, we’ll just call the scripts/*create.pl
script, giving it the name of the controller to create.

claco@mbp ~/mvc-marathon/catalyst/BurningPlate $ script/*create.pl controller Restaurants
exists "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../lib/BurningPlate/Controller"
exists "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../t"
created "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../lib/BurningPlate/Controller/Restaurants.pm"
created "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../t/controller_Restaurants.t"

The script has created a new controller class for us along with
a unit test file for that controller. Now we need to add code
to retrieve a list of restaurants and send that to a view.

$c->stash->{'restaurants'} = [$c->model('Restaurant')->search->all];

In the code above, we ask the Restaurant model to search for all
restaurant records and assign the results to an array called
restaurants in the stash. The stash is just like the ViewData in
.NET MVC, holding data for the view to access and render.

Creating the View

Now that we have our restaurants, we need a view to display them.
Catalyst doesn’t assume out of the box that your views will be
in any specific language, or even that they will use templates at all.
This means we need to create a view class to render templates and
an actual template.

We’ll use the create.pl script again to create a view class.

claco@mbp ~/mvc-marathon/catalyst/BurningPlate $ script/*create.pl view TT TT
created "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../lib/BurningPlate/View"
exists "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../t"
created "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../lib/BurningPlate/View/TT.pm"
created "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../t/view_TT.t"

Here’s we’ve created a view class using the TT (Template Toolkit)
helper. By default, Catalyst will use the first view it finds as the
default view renderer. If you have multiple views, you can call
the view explicitly or set the default_view or current_view
config options.

Now we need to create a template for our controller. Just like
.NET MVC above, the TT view will look for a template file matching
the controllers name and action. In our case, the file would be
named index.tt in a restaurants folder in the root directory.
You can override the name of the template using the template
stash variable.

<ul>
[% FOREACH restaurant IN restaurants %]
<li>[% restaurant.name %]</li>
[% END %]
</ul>

Just like the index template for .NET MVC, we simply use the template
language of choice to create an unordered list, loop through each
restaurant and print out the name.

Now that we have our view, let’s check out our restaurants list
by going to /restaurants/. Just like Cake and .NET, the path is
derived form the name of the class. Unlike Cake and .NET, there
is no central routing configuration to tweak if you need to
change the urls. Instead, you can simply change the path
configuration variable in each controller. You can however change
that config parameter within the main configuration file too,
so I guess there technically is a central routing table. :-)


catalyst browser

Django

Because Django was born our of the publishing industry, it has
very strong CRUD/scaffolding support out of the box. One needs
only to load the “admin” modules in their configuration, tell
it what models to administer, set the admin url and create
the necessary tables and user using manage.py.

Creating the Data

In our settings.py, we add a line to load the django.contrib.admin
module:

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'BurningPlate'
)

Now we need to tell the admin code which models to administer. We can
do this anywhere, but I’ll put it in our model for now.

admin.site.register(Restaurant)

Now we need to tell Django what url to use for admin access. To do this,
all we have to do is uncomment some like already in the urls.py file:

from django.conf.urls.defaults import *

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
# Example:
# (r'^BurningPlate/', include('BurningPlate.foo.urls')),

# Uncomment the next line to enable admin documentation:
# (r'^admin/doc/', include('django.contrib.admindocs.urls')),

# Uncomment the next line for to enable the admin:
(r'^admin/(.*)', admin.site.root),
)

Now that we loaded the admin code and told it which models to administer,
we need to update the database to include the necessary admin related
tables. To do this, simply run syncdb.

claco@mbp ~/BurningPlate $ python manage.py syncdb
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table django_admin_log
Creating table BurningPlate_restaurant

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (Leave blank to use 'claco'): admin
E-mail address: claco@chrislaco.com
Password:
Password (again):
Superuser created successfully.
Installing index for auth.Permission model
Installing index for auth.Message model
Installing index for admin.LogEntry model

Django created the needed tables for us and also prompted us to create
a username/password for access. Now let’s check out the admin web
interface. and add our data.


django list


django new


django list


django list

Creating the Controller

While Django is an “MVC” like framework, it considers the Controller
to be the “view”, and the View as the “template”. Aside from this
naming juggle, it’s still the same in practice: a separation of
getting the data and displaying the data.

To create our controller, simply create a restaurants directory
and define the views for it.

from django.shortcuts import render_to_response
from BurningPlate.models import Restaurant

def index(request):
restaurants = Restaurant.objects.all()
return render_to_response('restaurants/index.html', {'restaurants': restaurants})

In the code above, we loaded request handling modules, loaded our
Restaurants model and defined an index action that loads all restaurants
and renders the index.html template, passing it the restaurants list in much
the same we use the stash in Catalyst of the ViewData in .NET.
Like Catalyst, the template can be named anything you want it to be.

Unlike the other frameworks, url mapping is a manual process. To
expose the restaurants controller, we need to add it to our urls.py
file:

(r'^restaurants/$', 'BurningPlate.restaurants.views.index')

Creating the View

By default, Django looks for
its templates in a path outside of the application root.

For the sake of keeping it all in the same directory in the
repository, I’ve tweaked the config to point to the current
directory as the root template directory.

TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
"."
)

Now in our restaurants directory, we create an index.html with
the code to print out the restaurants.

<ul>
{% for restaurant in restaurants %}
<li>{{ restaurant.name }}</li>
{% endfor %}
</ul>

Looks familiar doesn’t it? Let’s check it out in our browser.


django browser

Ruby on Rails

Unfortunately for me, I upgraded to Leopard, which means I also got
the latest Rails 2.1. The scaffolding in 2.1 has changed from
previous versions. It also apparently can’t be added to an existing
model as it insists on creating it’s own model as part of the process.
This means we have to ditch the model we built in the last
article.

Creating the Controller/View/Data

To create the controller and scaffolding, simply run the generate
scaffold command, passing it the name of the model and the fields
to create:

claco@mbp ~/mvc-marathon/rails/BurningPlate $ script/generate scaffold restaurant name:string
exists  app/models/
exists  app/controllers/
exists  app/helpers/
create  app/views/restaurants
create  app/views/layouts/
exists  test/functional/
exists  test/unit/
create  public/stylesheets/
create  app/views/restaurants/index.html.erb
create  app/views/restaurants/show.html.erb
create  app/views/restaurants/new.html.erb
create  app/views/restaurants/edit.html.erb
create  app/views/layouts/restaurants.html.erb
create  public/stylesheets/scaffold.css
create  app/controllers/restaurants_controller.rb
create  test/functional/restaurants_controller_test.rb
create  app/helpers/restaurants_helper.rb
route  map.resources :restaurants
dependency  model
exists    app/models/
exists    test/unit/
exists    test/fixtures/
create    app/models/restaurant.rb
create    test/unit/restaurant_test.rb
create    test/fixtures/restaurants.yml
exists    db/migrate
create    db/migrate/20080731023309_create_restaurants.rb

As you can see, this created everything we need to get started.
The mode, the migration, the views, the controller and the
tests/test fixtures. Normally you would run rake db:migrate to
setup the database. But because we’ve already done the same in our
previous article, we don’t need to do that here.

Let’s check out what we have in the browser.


rails list


rails new


rails list


rails list

Conclusions

  • It would be nice if ASP.NET MVC, Catalyst and Django created
    controllers that pushed the idea of using an app level
    base class like Cake and Rails do. I hear “use a base class”
    all the time in Catalyst discussions in response to people
    wanting to create plugins. It would be better for the new
    user to be led to practice what is preached.
  • While Rails scaffolding is nice and can be customized,
    it can’t be added to and existing controller/model. This means
    you need to know that you have to use it first. It would be
    much more useful if you could at least point it to an existing
    model.
  • CakePHP scaffolding is quite nice but it’s not very easy
    to customize like the Django admin section is. It is pretty
    easy to move into an admin set of controllers namespace though.

    Ruby strikes a nice balance here as well. It generates CRUD scaffold for you, but in the end it’s just code on your app you can tweak any way you want.

  • Django admin scaffolding is the bomb. It’s in it’s own
    directory with authentication already rolled in.
  • CRUD scaffolding in Catalyst is mostly non existent
    for the DBIx::Class user (the ORM pushed in the tutorials)
    unless you’re willing to write some glue classes. If you’re running
    Rose::DB as your ORM, you’re golden. The ListFramework is promising and so is the CatalystX::CRUD stuff. I imagine that given some time, it will have strong CRUD like Django does.
  • .NET MVC falls flat in the CRUD Scaffold arena.

MVC Marathon Part 2: Creating a Database and Model

July 12th, 2008

Welcome to part 2 of MVC Marathon, a multipart excursion into creating an application in the major MVC frameworks available today.

The source code for this part can be found here: http://github.com/claco/mvc-marathon/tree/part2/

Part 2: Creating a Database and Model

Now that we have a bunch of shiny new applications, we need to add
a database to store our data, and a model to get at the data. For
this part of the project, I’m going to try and bypass the need
to deal with mysql/postgres/mssql issues and go with the most
simple database I can: SQLite. While there is an SQLite driver
for .NET, it does not yet play well with LINQ, so in ASP.NET I’ll
be using SQL Server Express, which is part of the Studio install.

Since BurningPlate is a website about restaurants peppery hot menu
items, we’re going to need a table to hold restaurant records. For
now, the restaurants table needs only two fields: a record id and
the name of the restaurant. The id field will be an auto-increment
integer field and the name will be a 100 character field, both
disallowing nulls.

In general, there are at two distinct ways to go about creating a
new database its models.

First, all of the frameworks can simply be pointed at a database,
told the name of tables and the models will inspect the schema and
provide their own glue. In this scenario, the user is generally
expected to alter the database, and have the model follow along.

The second method is really a reverse of the first. You create
a schema in a generic language, or define your models and the
database schema is generated and/or deployed to the database from
the models themselves. Some frameworks like Ruby and Catalyst
go one step further and allow that schema to be versioned,
allowing easier upgrades/downgrades of the schema itself.

Since the second method is more exciting and offers more insight
into how the various frameworks work, that will be what I do when
the framework supports it.

Today we’ll explore how to create database and model in the
various frameworks and explore the difference between them. You
can jump to any specific framework using the links below.

ASP.NET MVC

With .NET 3.0 came LINQ, a new way to model database tables to
strongly typed classes. This new method works much like the
strongly typed datasets of old from the user perspective. Pick a
table, map it to properties, map stored procedures for actions,
then code away with strongly types classes representing your
table data.

Unfortunately, ASP.NET MVC is a database-first framework. You must
create the database and tables first. You can’t create the LINQ to
SQL classes first and then create the database/tables from the
LINQ classes. Maybe this will change with IronRuby.

Creating the Database

First, to create the database, Right-click the project file and
choose “Add New Item”. Select “SQL Server Database”. Name the new
database “BurningPlate.mdf” and click “Add”.


rails run

Studio gripes about the file type and asks us if we want to put it
it in our App_Data folder. Say “Yes” and it will. :-)


rails run


rails run

Now that we have a database, we need to create a new table.
Double-click the new database and a Server Explorer will open on
the left.


rails run

Right-click on he Tables folder and choose “Add New Table”


rails run

As we’ll see in other frameworks, LINQ to SQL classes have some
singular/pluralization (’inflection’) naming magic to them. In our
case, a restaurants table is a collection of rows, where each row
is a restaurant. For now, lets name our table “Restaurants”.


rails run

Now that we have a new table, we’ll add our id and name columns.
We’ll follow the frameworks naming conventions and add an Id field,
setting the type to INT. We also need to set the column property
“Is Identity” below to make it an auto-increment field. Add the
Name column, setting the type to VARCHAR and change the length to
100 and click the Save button.


rails run

Creating the Model

Now that we have our database and table, we need to create the
model class to access that data. To create our new model,
Right-click the projects Models folder and select “Add New Item”.
Choose “LINQ to SQL Classes” and name it “BurningPlate.dbml”.


rails run

Now all we have to do is drag the Restaurants table from the
Server Explorer into the new BurningPlate.dbml designer window.


rails run

That’s it. We have a new database, table and model for use in
ASP.NET MVC applications. Notice how the model was given the name
“Restaurant”. That’s inflection. You can of course name your
model anything you want, and you can alter the name of the table
it points to at any time.

CakePHP

Like most other frameworks and unlike the ASP.NET MVC framework,
CakePHP can create database tables from a schema definition
using it’s own generic descriptive methods/properties to describe
tables. Before we do that, we need to continue the database
configuration that we skipped when we
created
or new CakePHP application.

To finish configuring our database information, just rerun
cake bake within our app directory.

Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
App : Burningplate
Path: /Users/claco/mvc-marathon/cakephp/Burningplate
---------------------------------------------------------------
Your database configuration was not found. Take a moment to create one.
---------------------------------------------------------------
Database Configuration:
---------------------------------------------------------------
Name:
[default] >
Driver: (db2/firebird/mssql/mysql/mysqli/odbc/oracle/postgres/sqlite/sybase)
[mysql] > sqlite
Persistent Connection? (y/n)
[n] >
Database Host:
[localhost] >
Port?
[n] >
User:
[root] >
Password:
>
The password you supplied was empty. Use an empty password? (y/n)
[n] > y
Database Name:
[cake] > burning_plate.db
Table Prefix?
[n] >
Table encoding?
[n] >

---------------------------------------------------------------
The following database configuration will be created:
---------------------------------------------------------------
Name:         default
Driver:       sqlite
Persistent:   false
Host:         localhost
User:         root
Pass:
Database:     burning_plate.db
---------------------------------------------------------------
Look okay? (y/n)
[y] >
Do you wish to add another database configuration?
[n] >

Creating file /Users/claco/mvc-marathon/cakephp/Burningplate/config/database.php
Wrote /Users/claco/mvc-marathon/cakephp/Burningplate/config/database.php

Note: We’ve given the database path as a
relative path. The cake command line utils consider
the root path of my app as the BurningPlate folder, but
CakePHP under Apache thinks the root path is
BurningRiver/webroot. This means when using a
relative database path, one or the other won’t find the
database. Not very portable by default. In order to fix that,
I had to add the following to the database config:

class DATABASE_CONFIG {
function __construct() {
$this->default['database'] = APP . $this->default['database'];
}

Creating the Database

Now that we’ve told CakePHP where and what database we’re
going to use, we need to create our restaurants table. Since
we’re trying to always create the tables from the models, we’ll
do that first.

CakePHP uses inflection to automagically wire up default code
and save the user as much time as possible if you
follow certain naming conventions. To create tables from
definitions, you need to create definitions in the
config/sql/schema.php file. Of course, you can also
point a model at any old table and it will work. Schemas aren’t
mandatory.

As above, we want a table that has an id integer as the primary
key and a name varchar field, both not null. Unlike ASP.NET
where we have to tell the database that the pk field was auto
and let it manage the pk auto increment, CakePHP assumes that
any integer primary key field is an auto increment field and
will manage the ids automatically. It will work as expected
even if you happen to have an existing table where the pk is
defined as auto increment. You can even use UUIDs for the
primary key just by making that field a CHAR(36) instead. But
I digress…

class BurningPlateSchema extends CakeSchema {
public $restaurants = array(
'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
'name' => array('type' => 'string', 'null' => false, 'length' => 100),
'indexes' => array('PRIMARY' => array('column' => 'id', 'unique' => 1))
);
}

Now that we have a schema defined, we need to create the table
from the schema definition. Let’s see what the command help
has to offer:

claco@mbp ~/mvc-marathon/cakephp/Burningplate $ cake schema
Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
App : Burningplate
Path: /Users/claco/mvc-marathon/cakephp/Burningplate
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------
The Schema Shell generates a schema object from
the database and updates the database from the schema.
---------------------------------------------------------------
Usage: cake schema <command> <arg1> <arg2>...
---------------------------------------------------------------
Params:

-connection <config>
set db config <config>. uses 'default' if none is specified

-path <dir>
path <dir> to read and write schema.php.
default path: /Users/claco/mvc-marathon/cakephp/Burningplate/config/sql

-file <name>
file <name> to read and write.
default file: schema.php

-s <number>
snapshot <number> to use for run.

-dry
Perform a dry run on 'run' commands.
Queries will be output to window instead of executed.

-f
force 'generate' to create a new schema.
Commands:

schema help
shows this help message.

schema view
read and output contents of schema file

schema generate
reads from 'connection' writes to 'path'
To force generation of all tables into the schema, use the -f param.

schema dump <filename>
dump database sql based on schema file to filename in schema path.
if filename is true, default will use the app directory name.

schema run create <schema> <table>
drop tables and create database based on schema file
optional <schema> arg for selecting schema name
optional <table> arg for creating only one table
pass the -s param with a number to use a snapshot
To see the changes, perform a dry run with the -dry param

schema run update <schema> <table>
alter tables based on schema file
optional <schema> arg for selecting schema name.
optional <table> arg for altering only one table.
To use a snapshot, pass the -s param with the snapshot number
To see the changes, perform a dry run with the -dry param

Now we’ll create our new table:

claco@mbp ~/mvc-marathon/cakephp/Burningplate $ cake schema run create
Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
App : Burningplate
Path: /Users/claco/mvc-marathon/cakephp/Burningplate
---------------------------------------------------------------
Cake Schema Shell
---------------------------------------------------------------

The following tables will be dropped.
restaurants

Are you sure you want to drop the tables? (y/n)
[n] > n

The following tables will be created.
restaurants

Are you sure you want to create the tables? (y/n)
[y] > y
Creating tables.
restaurants updated.
End create.

Creating the Model

Now that we’ve created our table and database, we need to create
a model. Once again we’ll use cake bake to get the job
done.

claco@mbp ~/mvc-marathon/cakephp/Burningplate $ cake bake
Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
App : Burningplate
Path: /Users/claco/mvc-marathon/cakephp/Burningplate
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[Q]uit
What would you like to Bake? (D/M/V/C/P/Q)
> M
---------------------------------------------------------------
Bake Model
Path: /Users/claco/mvc-marathon/cakephp/Burningplate/models/
---------------------------------------------------------------
Possible Models based on your current database:
1. Restaurant
Enter a number from the list above, type in the name of another model, or 'q' to exit
[q] > 1
Would you like to supply validation criteria for the fields in your model? (y/n)
[y] > n
Would you like to define model associations (hasMany, hasOne, belongsTo, etc.)? (y/n)
[y] > n

---------------------------------------------------------------
The following Model will be created:
---------------------------------------------------------------
Name:       Restaurant
Associations:
---------------------------------------------------------------
Look okay? (y/n)
[y] >

Baking model class for Restaurant...

Creating file /Users/claco/mvc-marathon/cakephp/Burningplate/models/restaurant.php
Wrote /Users/claco/mvc-marathon/cakephp/Burningplate/models/restaurant.php
Cake test suite not installed.  Do you want to bake unit test files anyway? (y/n)
[y] > y

You can download the Cake test suite from http://cakeforge.org/projects/testsuite/

Baking test fixture for Restaurant...

Creating file /Users/claco/mvc-marathon/cakephp/Burningplate/tests/fixtures/restaurant_fixture.php
Wrote /Users/claco/mvc-marathon/cakephp/Burningplate/tests/fixtures/restaurant_fixture.php

Baking unit test for Restaurant...

Creating file /Users/claco/mvc-marathon/cakephp/Burningplate/tests/cases/models/restaurant.test.php
Wrote /Users/claco/mvc-marathon/cakephp/Burningplate/tests/cases/models/restaurant.test.php
---------------------------------------------------------------
Interactive Bake Shell
---------------------------------------------------------------
[D]atabase Configuration
[M]odel
[V]iew
[C]ontroller
[P]roject
[Q]uit
What would you like to Bake? (D/M/V/C/P/Q)
> Q

CakePHP simply inspected the database, asked us which table to
create a model for and created the necessary files, including
tests.

Again, take note of the inflection here. We created a table
called restaurants but the model created was Restaurant. If
you were to manually created a model called Post, it would
by default look for a table called posts. This can, of course,
be overridden using the $useTable variable.

While there is a cake console that we could use to
test out model and create a record, I couldn’t get it to work.
It could connect to the database and find records, but it
would not save a new record, even though it said
“Saved record for Restaurant”.

Catalyst

One of the Perl mantras is TIMTOWTDI: There is more than one way
to do it. As such, Catalyst doesn’t come with database support
out of the box. One reason for this is that there are more than
one ORM packages for Perl and different reasons for an author to
prefer one over the other. However, if you install the bundle
referenced in the official tutorial, it will install of the
necessary bits to do database work.

Much like CakePHP, you can have three distinct layers when using
a database in Catalyst: The models generated from the schema, the
schema describing the database and the database itself. You can
choose to statically or dynamical create the schema from the
database or do the reverse: create the database from the schema.

For this application, I’m going to use the new schema versioning
built into DBIx::Class. Like CakePHP above, we’re
going to create our schema first, deploy it to the database and
then create models from our schema.

Creating the Database

Unlike CakePHP, we need to get down and dirty with writing some
code manually to get our schema started and to maintain the
schema versions and upgrades. This isn’t as bad as it sounds.
Most of the heavy lifting is already coded for us. First, we need
to create out schema file in lib/BurningPlate/Scheme.pm

package BurningPlate::Schema;
use strict;
use warnings;
use base 'DBIx::Class::Schema';
our $VERSION = 0;

__PACKAGE__->load_classes;
__PACKAGE__->load_components('+DBIx::Class::Schema::Versioned');
__PACKAGE__->upgrade_directory('sql/');
__PACKAGE__->backup_directory('sql/backups/');

1;

In a nutshell, we’re telling DBIx::Class to load all
BurningPlate::Schema::* child classes, load the versioning module,
and place the schema upgrade/backup files in the given directories.
Take note of the $VERSION variable. This is used by
the versioning module to determine what version the schema is
compared to the database versioning information table.

We could create our table schema and deploy the schema to the
database. DBIx::Class currently considers any database without
a versioning table to be the current version and will not upgrade
anything even if it difference from your schema classes. Because
of this, it’s better to consider version 1 a blank database and
work up from there. We’re going to deploy what we have now before
we add the restaurants table.

DBIx::Class doesn’t come with console scripts to generate schema
versioning files or upgrade database. But because they’re simple
method calls, they’re easy enough to create. First our schema sql
script script/burningplate_schema.pl

#!/usr/bin/perl -w
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use BurningPlate::Schema;

my $version = BurningPlate::Schema->schema_version;

BurningPlate::Schema->connect->create_ddl_dir(
['SQLite'],
$version > 1 ? $version : undef,
'sql',
$version ? $version-1 : $version
);

The script basically loads the schema, finds the version, then
calls create_ddl_dir to create sql ddl files from the
current schema version. If the version is greater than 1, we also
create an sql file that contains the difference between the
versions. This is the heart of schema versioning. Database changes
can be rolled out incrementally from one version to the next. We
specified SQLite, but you can create multiple schemas at the same
time for multiple database vendors.

Now, we’ll run the script to generate our version 1 ddl files.

claco@mbp ~/mvc-marathon/catalyst/BurningPlate $ script/*schema.pl
Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.

Now, in our sql directory, we have a new file:

--
-- Created by SQL::Translator::Producer::SQLite
-- Created on Tue Jul  8 19:44:05 2008
--
BEGIN TRANSACTION;

COMMIT;

Since we have no tables yet, we have no sql to deploy but we must
still run an upgrade to install the versioning table and the
current version number. Since we have no console script to do that
, we’ll create one in script/burningplate_upgrade.pl

#!/usr/bin/perl -w
use strict;
use warnings;
use FindBin;
use lib "$FindBin::Bin/../lib";
use BurningPlate::Schema;

BurningPlate::Schema->connect(@ARGV)->upgrade;

In this script, we simply connect to the specified database and
upgrade it to the latest version. Now we can deploy our first
version and the versioning tables.

claco@mbp ~/mvc-marathon/catalyst/BurningPlate $ script/*upgrade.pl dbi:SQLite:burning_plate.db
Your DB is currently unversioned. Please call upgrade on your schema to sync the DB.

Now, let’s define our restaurants schema and deploy it to the
database. First, we need to increase the schema $VERSION to 2.
Next, we’ll create BurningPlate/Schema/Restaurant.pm

package BurningPlate::Schema::Restaurant;
use strict;
use warnings;
use base 'DBIx::Class';
__PACKAGE__->load_components('Core');
__PACKAGE__->table('restaurants');
__PACKAGE__->add_columns(
id => {
data_type => 'INT',
is_nullable => 0,
is_auto_increment => 1
},
name => {
data_type => 'VARCHAR',
size => 100,
is_nullable => 0
}
);
__PACKAGE__->set_primary_key('id');

1;

Much like the CakePHP schema, we are telling the schema the
names and properties of the columns and a primary key. Now
that we have a complete schema, we need to deploy it to a
database. First, we’ll generate the version 2 sql.

claco@mbp ~/mvc-marathon/catalyst/BurningPlate $ script/*schema.pl

Now, if you look in the sql folder, we have two new files.
First, we have BurningPlate-Schema-2-SQLite.sql.
This contains the entire schema as it exists in version 2 if
we were to deploy the entire schema from scratch.

--
-- Created by SQL::Translator::Producer::SQLite
-- Created on Tue Jul  8 22:52:12 2008
--
BEGIN TRANSACTION;
--
-- Table: restaurants
--
DROP TABLE restaurants;
CREATE TABLE restaurants (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(100) NOT NULL
);

COMMIT;

We also have a second file: BurningPlate-Schema-1-2-SQLite.sql.
This file contains all the sql necessary to upgrade a database from
version to version 2.

-- Convert schema 'sql/BurningPlate-Schema-1-SQLite.sql' to 'sql/BurningPlate-Schema-2-SQLite.sql':

BEGIN;

CREATE TABLE restaurants (
id INTEGER PRIMARY KEY NOT NULL,
name VARCHAR(100) NOT NULL
);

COMMIT;

Later on if we were to add a column to version 3, the 2->3 file
would contain an ALTER TABLE statement. Now, we just need to deploy
the new version to the database using the same command we ran before:

claco@mbp ~/mvc-marathon/catalyst/BurningPlate $ script/*upgrade.pl dbi:SQLite:burning_plate.db
Versions out of sync. This is 2, your database contains version 1, please call upgrade on your Schema.

The warnings are a little misleading. They’re generated by the call
to connect() which do before calling upgrade.

Creating the Model

Now that we have our schema and database, we need to create a model
to access that data. There are many ways to do this in Catalyst.
One can manually create a model and load the schema directly or
you can bypass the schema class and write database access code
to use the database directly. Let’s see what the create script
has to say:

claco@mbp ~/mvc-marathon/catalyst/BurningPlate $ script/burningplate_create.pl
Usage:
burningplate_create.pl [options] model|view|controller name [helper]
[options]

Options:
-force        don't create a .new file where a file to be created exists
-mechanize    use Test::WWW::Mechanize::Catalyst for tests if available
-help         display this help and exits

Examples:
burningplate_create.pl controller My::Controller
burningplate_create.pl controller My::Controller BindLex
burningplate_create.pl -mechanize controller My::Controller
burningplate_create.pl view My::View
burningplate_create.pl view MyView TT
burningplate_create.pl view TT TT
burningplate_create.pl model My::Model
burningplate_create.pl model SomeDB DBIC::Schema MyApp::Schema create=dynamic\
dbi:SQLite:/tmp/my.db
burningplate_create.pl model AnotherDB DBIC::Schema MyApp::Schema create=static\
dbi:Pg:dbname=foo root 4321

See also:
perldoc Catalyst::Manual
perldoc Catalyst::Manual::Intro

The easiest way to use our schema as a model without writing code
is to use Catalyst::Model::DBIC::Schema listed above.
This will automatically load a schema class and create a model for
each table (resultsource in DBIC speak) found. To create our
model, we simply call the create script, passing in the
appropriate options:

claco@mbp ~/mvc-marathon/catalyst/BurningPlate $ script/burningplate_create.pl model DB DBIC::Schema BurningPlate::Schema dbi:SQLite:burning_plate.db
exists "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../lib/BurningPlate/Model"
exists "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../t"
created "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../lib/BurningPlate/Model/DB.pm"
created "/Users/claco/mvc-marathon/catalyst/BurningPlate/script/../t/model_DB.t"

Just like CakePHP, it generated a model file and created a test
file for us. If we reload the application, we now see that the
new model is loaded and ready for action.

[debug] Loaded components:
.-----------------------------------------------------------------+----------.
| Class                                                           | Type     |
+-----------------------------------------------------------------+----------+
| BurningPlate::Controller::Root                                  | instance |
| BurningPlate::Model::DB                                         | instance |
| BurningPlate::Model::DB::Restaurant                             | class    |
'-----------------------------------------------------------------+----------'

In contrast to CakePHP, there is no inflection magic here. Name your model
and your table whatever you want without worry of breaking things
without really knowing.

Unlike CakePHP and as we’ll see later, Django and Rails, there is
no interactive console to work with your newly created models. But
there is a utility called dbicadmin that can be used to
perform CRUD operations with the schema. On the down side, the schema
doesn’t hold the database connection information; the model does. So
using the utility isn’t practical in this case without altering code.

Django

Django takes a more compact approach than CakePHP and Catalyst w/
DBIx::Class. In Django, there is no separation between models and
the schema. In fact, all of the models are defined in one single
models.py class instead of putting separate classes in a models
directory like Catalyst/CakePHP do.

Creating the Model

Creating models in Django is just a matter of creating the
models.py file. You also get a modesl.py file for free when you
run manage.py startapp, which creates a new application
sudirectory in the project we previously created. Since we’re not
really creating multiple apps right now, we’ll just create the
models.py file in the project root.

from django.db import models

class Restaurant(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)

In the file above, we created a new Restaurant class and defined
the id and name fields. AutoField implies a code managed
(not db managed) auto increment int field, and we’ve set primary
key and the name length.

Creating the Database

Now that we have a model, we need to create a database from it.
First, we need to tell Django how to conect to the new database
by changing settinga.py

DATABASE_ENGINE = 'sqlite3'           # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'ado_mssql'.
DATABASE_NAME = 'burning_plate.db'    # Or path to database file if using sqlite3.

While we’re in here, we need to tell this Django project that it
also should load our new BurningPlate application, which is now
only a models.py file. If we wanted to use other apps in our project,
we could just laod them here.

INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'BurningPlate'
)

Now, to create our database, we just run the syncd command.

claco@mbp ~/mvc-marathon/django/BurningPlate $ python manage.py syncdb
Creating table auth_permission
Creating table auth_group
Creating table auth_user
Creating table auth_message
Creating table django_content_type
Creating table django_session
Creating table django_site

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): no
Installing index for auth.Permission model
Creating table BurningPlate_restaurant

That’s all there is to it. Django created a bunch of auth related
tables and then our restaurant table. Let’s take a gander at the
schema:

CREATE TABLE "BurningPlate_restaurant" (
"id" integer NOT NULL PRIMARY KEY,
"name" varchar(100) NOT NULL
);

Django also doesn’t play any inflection games. The model name
is the table name. However, since Django projects are assumed to
have multiple apps within them, the name of the app has been
prepended to the table name to avoid name collisions. You can
override the table name using the db_table variable.

Now, let’s fire up the interactive console and test out our model.

claco@mbp ~/mvc-marathon/django/BurningPlate $ python manage.py shell
Python 2.5.1 (r251:54863, Oct 17 2007, 22:46:25)
[GCC 4.0.1 (Apple Computer, Inc. build 5367)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from BurningPlate .models import Restaurant
>>> Restaurant.objects.all()
[]
>>> r = Restaurant(name="Azteca")
>>> r.save()
>>> r.id
1
>>> r.name
'Azteca'
>>> Restaurant.objects.all()
[<Restaurant: Restaurant object>]
>>> quit()

Ruby on Rails

Rails has migrations which is a lot like the Catalyst/DBIx::Class
schema versioning with a little more glue around the edges to make
thing go quicker. Like CakePHP and Catalyst, the model, schema (or
migrations) and the database are three separate layers. Unlike
Django models, Rails (ActiveRecord based) models don’t describe
the schema. Instead, they operate by dynamically inspecting the
table they’re working on.

Creating the Model

Like Catalyst and CakePHP, we can use the console scripts to create
a new model and do all sorts of other things. First, let’s see what
generate tells us.

claco@mbp ~/mvc-marathon/rails/BurningPlate $ script/generate
Usage: script/generate generator [options] [args]

Rails Info:
-v, --version                    Show the Rails version number and quit.
-h, --help                       Show this help message and quit.

General Options:
-p, --pretend                    Run but do not make any changes.
-f, --force                      Overwrite files that already exist.
-s, --skip                       Skip files that already exist.
-q, --quiet                      Suppress normal output.
-t, --backtrace                  Debugging: show backtrace on errors.
-c, --svn                        Modify files with subversion. (Note: svn must be in path)

Installed Generators
Builtin: controller, integration_test, mailer, migration, model, observer, plugin, resource, scaffold, session_migration

More are available at http://rubyonrails.org/show/Generators
1. Download, for example, login_generator.zip
2. Unzip to directory /Users/claco/.rails/generators/login
to use the generator with all your Rails apps
or to /Users/claco/mvc-marathon/rails/BurningPlate/lib/generators/login
to use with this app only.
3. Run generate with no arguments for usage information
script/generate login

Generator gems are also available:
1. gem search -r generator
2. gem install login_generator
3. script/generate login

Now, let’s check out the model option.

claco@mbp ~/mvc-marathon/rails/BurningPlate $ script/generate model
Usage: script/generate model ModelName [field:type, field:type]

Options:
--skip-timestamps            Don't add timestamps to the migration file for this model
--skip-migration             Don't generate a migration file for this model
--skip-fixture               Don't generation a fixture file for this model

Rails Info:
-v, --version                    Show the Rails version number and quit.
-h, --help                       Show this help message and quit.

General Options:
-p, --pretend                    Run but do not make any changes.
-f, --force                      Overwrite files that already exist.
-s, --skip                       Skip files that already exist.
-q, --quiet                      Suppress normal output.
-t, --backtrace                  Debugging: show backtrace on errors.
-c, --svn                        Modify files with subversion. (Note: svn must be in path)

Description:
Stubs out a new model. Pass the model name, either CamelCased or
under_scored, and an optional list of attribute pairs as arguments.

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 model immediately.

This generates a model class in app/models, a unit test in test/unit,
a test fixture in test/fixtures/singular_name.yml, and a migration in
db/migrate.

Examples:
`./script/generate model account`

creates an Account model, test, fixture, and migration:
Model:      app/models/account.rb
Test:       test/unit/account_test.rb
Fixtures:   test/fixtures/accounts.yml
Migration:  db/migrate/XXX_add_accounts.rb

`./script/generate model post title:string body:text published:boolean`

creates a Post model with a string title, text body, and published flag.

We could pass in our column definitions in one shot, but where’s the
fun in that? Let’s create a new model.

claco@mbp ~/mvc-marathon/rails/BurningPlate $ script/generate model Restaurant
exists  app/models/
exists  test/unit/
exists  test/fixtures/
create  app/models/restaurant.rb
create  test/unit/restaurant_test.rb
create  test/fixtures/restaurants.yml
create  db/migrate
create  db/migrate/001_create_restaurants.rb

Just like CakePHP and Catalyst, the script was kind enough to create
some test files for the new model. It also generated a migration
file for us. Let’s have a look:

class CreateRestaurants < ActiveRecord::Migration
def self.up
create_table :restaurants do |t|
t.column :name, :string, :limit=>100, :null=>false
t.timestamps
end
end

def self.down
drop_table :restaurants
end
end

Unlike DBIx::Class schema version files which are sql based,
Rails migrations are based on code. Up describes what to do when
migrating from an older version to the current version. Down
describes what to do when migrating form the current version to the
previous version. For version 1 [from 0].

Creating the Database

Now that we have our model, let’s create our database from it.
We need to use rake, Rubys make, to migrate the database.

claco@mbp ~/mvc-marathon/rails/BurningPlate $ rake db:migrate
(in /Users/claco/mvc-marathon/rails/BurningPlate)
== 1 CreateRestaurants: migrating =============================================
-- create_table(:restaurants)
-> 0.0025s
== 1 CreateRestaurants: migrated (0.0027s) ====================================

Notice the table name: restaurants. Our old friend inflection is
back. We also didn’t have to setup our database connection information.
The default database.yml config already has a development
database setup in bd/development.sqlite3.

Another item of interest. If we take a look at the table that was
create, you’ll notice something that wasn’t in our model:

CREATE TABLE restaurants ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
"name" varchar(100) NOT NULL,
"created_at" datetime DEFAULT NULL,
"updated_at" datetime DEFAULT NULL);

When Rails creates a table from a model, it always adds the id,
create_at and updated_at fields for automatically set create and
updated dates when each record is created/modified. CakePHP will
also maintain create/updated fields but it will not declare them
for you when you create the tables.

Like most of the other frameworks, you can use an interactive
console to try out your new models.

claco@mbp ~/mvc-marathon/rails/BurningPlate $ script/console
Loading development environment (Rails 2.0.2)
>> Restaurant.create(:name => 'Azteca')
=> #<Restaurant id: 1, name: "Azteca", created_at: "2008-07-08 22:29:32", updated_at: "2008-07-08 22:29:32">
>> r = Restaurant.find_by_id(1)
=> #<Restaurant id: 1, name: "Azteca", created_at: "2008-07-08 22:29:32", updated_at: "2008-07-08 22:29:32">
>> r.name
=> "Azteca"
>> quit()

Conclusions

  • Catalyst is my alma mater. Perl is my favorite language, and
    I appreciate that there are many ways to do the same thing
    and nothing is forced upon me. However, as DBIx::Class
    is pushed in the tutorials, it is really the defacto ORM
    for Catalyst. As such, there really needs to be some glue to
    round off the sharp edges. 

    If DBIC is installed or there is a command line option to request it,
    catalyst should generate a Schema class,
    directories for the sql versioning and a helper script or
    two to help generate schema versioning files and update/deploy
    them. This would go a long way to improving the new user
    experience.

  • The CakePHP interactive console seems to be a bit immature.
    It doesn’t appear to add items, or at least my install
    wasn’t very happy. The other thing I’m not sure I can
    accept is that models return data, not objects. 

    The schema management only deploys new tables. It doesn’t
    deploy changes. It would be nice to see some sort of
    versioning or migrations like Catalyst/Rails.

    The fact that the console utilities and webroot look in
    two different places for a database path is a drag. It
    makes things less portable, esp if you have to put in a
    full path.

  • Compared to Rails migrations and DBIC versioning, Django feels week
    like CakePHP when it comes to syncing the model to the
    database. 

    I love that apps have been given consideration as movable
    reusable parts within many projects. This is something
    that is a little more obtuse in other frameworks.

  • I dig Rails migrations. While I can also have custom code
    when upgrading schema versions in Catalyst, migrations feel more
    natural to me. I like that upgrades and downgrades feel
    like they get equal attention and you can migrate to
    any version by number. 

    The magic adding of the id column and the created/updated
    columns scares me a little. If I already have a created
    column instead of a created_At column, well, that could
    lead to issues.

  • Inflections seem unnecessary to me, esp if you don’t speak
    Engrish as the primary language. But, to their credit, at
    least Rails and CakePHP give you control over the inflections
    if you need. Good Magic is magic that can be customized.
  • ASP.NET/ADO/SQL aren’t really build for versioning or for
    deploying a database schema from the models. To me, this
    makes deploying new versions a little more painful if you
    are stuck writing your alter scripts and such.
  • One of the things I like about CakePHP is that by default,
    new models and controllers inherit from an app local model
    and controller base class. Rails controllers also do this. This is easy to do in Catalyst,
    but it’s not forced upon you by default. Maybe it should be
    since I hear a lot of “use a base class” responses when
    people ask about creating plugins. 

    Ruby has more of a mixin model rather than multiple inheritance.
    Personally, I’d prefer that Rails models also inherited from an
    app local base class, then mixin-ed ActiveRecord on top of that.

MVC Marathon Part 1: Creating a New Application

June 29th, 2008

Welcome to part 1 of MVC Marathon, a multipart excursion into creating an application in the major MVC frameworks available today.

The source code for this part can be found here: http://github.com/claco/mvc-marathon/tree/part1-creating-a-new-application/

Part 1: Creating a New Application

The first step in any new application is creating a directory
structure and any necessary configuration files. Fortunately, most
of the frameworks now a days provide ready made templates or
scripts to make getting started as easy as possible.

Today we’ll explore how to create and run a new application in the
various frameworks and explore the difference between them. You
can jump to any specific framework using the links below. For this
series of articles, I’m going to assume that you already have the
basic frameworks installed.

ASP.NET MVC

Creating the Application

The easiest way to create a new ASP.NET MVC application is using
the application templates in Visual Studio. To do that, simply
select File -> New -> Project:


aspnetmvc create

In the New Project window, select Visual C# as the
language. Select the ASP.NET MVC Web Application, type in the name
of the new application, in our case, BurningPlate and click OK.
Visual Studio will create a new mvc application looking something
like this:


aspnetmvc create

Like nearly all MVC applications, we have the usual Controllers,
Models and Views folders along with some other .NET specific
folders.

Running the Application

To run your new application, simply hit F5, which will
start the application in Visual Studios local web server. This
means no messing with IIS until you have to. If all goes well, you
should end up with the default home page:


aspnetmvc run

CakePHP

There seems to be at least two ways to create a new CakePHP
application. The blog example on the CakePHP website say to just
download and extract the latest version and start creating your
application in the app subfolder. This seems like a
good approach if you’re creating app to put on a server where you
don’t know or control the version or even if CakePHP is installed.

If you’re running your own server, you can setup your env to point
to a centralized install of CakePHP and use cake to
create apps that reference the installed software. We’ll do the
latter as it closely follows how the other frameworks work.

Creating the Application

First, let’s see what cake tells us when we run it:

claco@mbp ~/mvc-marathon $ cake
Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
Current Paths:
-app: mvc-marathon
-working: /Users/claco/mvc-marathon
-root: /Users/claco
-core: /sw/cakephp/

Changing Paths:
your working path should be the same as your application path
to change your path use the '-app' param.
Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp

Available Shells:

vendors/shells/:
- none

cake/console/libs/:
acl
api
bake
console
i18n
schema
testsuite

To run a command, type 'cake shell_name [args]'
To get help on a specific command, type 'cake shell_name help'

Next, let’s run cake bake help to see what our options are:

claco@mbp ~/mvc-marathon $ cake bake help
Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
App : mvc-marathon
Path: /Users/claco/mvc-marathon
---------------------------------------------------------------
CakePHP Bake:
---------------------------------------------------------------
The Bake script generates controllers, views and models for your application.
If run with no command line arguments, Bake guides the user through the class
creation process. You can customize the generation process by telling Bake
where different parts of your application are using command line arguments.
---------------------------------------------------------------
Usage: cake bake <command> <arg1> <arg2>...
---------------------------------------------------------------
Params:
-app <path> Absolute/Relative path to your app folder.

Commands:

bake help
shows this help message.

bake all <name>
bakes complete MVC. optional <name> of a Model

bake project <path>
bakes a new app folder in the path supplied
or in current directory if no path is specified

bake plugin <name>
bakes a new plugin folder in the path supplied
or in current directory if no path is specified.

bake db_config
bakes a database.php file in config directory.

bake model
bakes a model. run 'bake model help' for more info

bake view
bakes views. run 'bake view help' for more info

bake controller
bakes a controller. run 'bake controller help' for more info

Since we’re starting from scratch, let’s give project
a whirl:

claco@mbp ~/mvc-marathon/cakephp $ cake bake BurningPlate
Welcome to CakePHP v1.2.0.7296 RC2 Console
---------------------------------------------------------------
App : cakephp
Path: /Users/claco/mvc-marathon/cakephp
---------------------------------------------------------------
Bake Project
Skel Directory: /sw/cakephp/cake/console/libs/templates/skel
Will be copied to: /Users/claco/mvc-marathon/cakephp/BurningPlate
---------------------------------------------------------------
Look okay? (y/n/q)
[y] >
Do you want verbose output? (y/n)
[n] >
---------------------------------------------------------------
Created: BurningPlate in /Users/claco/mvc-marathon/cakephp/BurningPlate
---------------------------------------------------------------

Creating file /Users/claco/mvc-marathon/cakephp/BurningPlate/views/pages/home.ctp
Wrote /Users/claco/mvc-marathon/cakephp/BurningPlate/views/pages/home.ctp
Welcome page created
Random hash key created for 'Security.salt'
CAKE_CORE_INCLUDE_PATH set to /sw/cakephp in webroot/index.php
CAKE_CORE_INCLUDE_PATH set to /sw/cakephp in webroot/test.php
Remember to check these value after moving to production server
Your database configuration was not found. Take a moment to create one.
---------------------------------------------------------------
Database Configuration:
---------------------------------------------------------------
Name:
[default] > ^C

Running the Application

At first glance, there appears to be no development server for
CakePHP applications. There is a script called
cakephp-instaweb
available on the CakePHP website geared for this purpose.
Ironically, the script relies on Python to get the job done. When
I ran it, it appeared to work, but all I ever got when trying to
hit it with the browser was a CGI SCript Error carping about
headers.

The second option is to run this application under Apache. Since
I’m on a MacBook Pro and OSX includes a running version of Apache
and PHP, all I needed to do was place the BurningPlate directory
in my Sites folder. After hitting
http://localhost/~claco/BurningPlate/ with my browser
we get:


cakephp run

Catalyst

Creating the Application

Just like CakePHP, Catalyst also has a command line utility for
creating a new application. Let’s see what what it has to say:

claco@mbp ~/mvc-marathon/catalyst $ catalyst.pl
Usage:
catalyst.pl [options] application-name

'catalyst.pl' creates a skeleton for a new application, and allows you
to upgrade the skeleton of your old application.

Options:
-force      don't create a .new file where a file to be created exists
-help       display this help and exit
-makefile   only update Makefile.PL
-scripts    only update helper scripts
-short      use short names, M/V/C instead of Model/View/Controller.

application-name must be a valid Perl module name and can include "::",
which will be converted to '-' in the project name.

Examples:
catalyst.pl My::App
catalyst.pl MyApp

To upgrade your app to a new version of Catalyst:
catalyst.pl -force -scripts MyApp

Looks like we just need an application name:

claco@mbp ~/mvc-marathon/catalyst $ catalyst.pl BurningPlate
created "BurningPlate"
created "BurningPlate/script"
created "BurningPlate/lib"
created "BurningPlate/root"
created "BurningPlate/root/static"
created "BurningPlate/root/static/images"
created "BurningPlate/t"
created "BurningPlate/lib/BurningPlate"
created "BurningPlate/lib/BurningPlate/Model"
created "BurningPlate/lib/BurningPlate/View"
created "BurningPlate/lib/BurningPlate/Controller"
created "BurningPlate/burningplate.conf"
created "BurningPlate/lib/BurningPlate.pm"
created "BurningPlate/lib/BurningPlate/Controller/Root.pm"
created "BurningPlate/README"
created "BurningPlate/Changes"
created "BurningPlate/t/01app.t"
created "BurningPlate/t/02pod.t"
created "BurningPlate/t/03podcoverage.t"
created "BurningPlate/root/static/images/catalyst_logo.png"
created "BurningPlate/root/static/images/btn_120x50_built.png"
created "BurningPlate/root/static/images/btn_120x50_built_shadow.png"
created "BurningPlate/root/static/images/btn_120x50_powered.png"
created "BurningPlate/root/static/images/btn_120x50_powered_shadow.png"
created "BurningPlate/root/static/images/btn_88x31_built.png"
created "BurningPlate/root/static/images/btn_88x31_built_shadow.png"
created "BurningPlate/root/static/images/btn_88x31_powered.png"
created "BurningPlate/root/static/images/btn_88x31_powered_shadow.png"
created "BurningPlate/root/favicon.ico"
created "BurningPlate/Makefile.PL"
created "BurningPlate/script/burningplate_cgi.pl"
created "BurningPlate/script/burningplate_fastcgi.pl"
created "BurningPlate/script/burningplate_server.pl"
created "BurningPlate/script/burningplate_test.pl"
created "BurningPlate/script/burningplate_create.pl"

Running the application

Unlike CakePHP, Catalyst includes a development server to get you
started. No need to futz with Apache:

claco@mbp ~/mvc-marathon/catalyst $ BurningPlate/script/burningplate_server.pl
[debug] Debug messages enabled
[debug] Statistics enabled
[debug] Loaded plugins:
.----------------------------------------------------------------------------.
| Catalyst::Plugin::ConfigLoader  0.20                                       |
| Catalyst::Plugin::Static::Simple  0.20                                     |
'----------------------------------------------------------------------------'

[debug] Loaded dispatcher "Catalyst::Dispatcher"
[debug] Loaded engine "Catalyst::Engine::HTTP"
[debug] Found home "/Users/claco/mvc-marathon/catalyst/BurningPlate"
[debug] Loaded Config "/Users/claco/mvc-marathon/catalyst/BurningPlate/burningplate.conf"
[debug] Loaded components:
.-----------------------------------------------------------------+----------.
| Class                                                           | Type     |
+-----------------------------------------------------------------+----------+
| BurningPlate::Controller::Root                                  | instance |
'-----------------------------------------------------------------+----------'

[debug] Loaded Private actions:
.----------------------+--------------------------------------+--------------.
| Private              | Class                                | Method       |
+----------------------+--------------------------------------+--------------+
| /default             | BurningPlate::Controller::Root       | default      |
| /end                 | BurningPlate::Controller::Root       | end          |
| /index               | BurningPlate::Controller::Root       | index        |
'----------------------+--------------------------------------+--------------'

[debug] Loaded Path actions:
.-------------------------------------+--------------------------------------.
| Path                                | Private                              |
+-------------------------------------+--------------------------------------+
| /                                   | /default                             |
| /                                   | /index                               |
'-------------------------------------+--------------------------------------'

[info] BurningPlate powered by Catalyst 5.7014
You can connect to your server at http://localhost:3000
[info] *** Request 1 (0.125/s) [1941] [Sat Jun 28 19:24:35 2008] ***
[debug] "GET" request for "/" from "127.0.0.1"
[info] Request took 0.017728s (56.408/s)
.----------------------------------------------------------------+-----------.
| Action                                                         | Time      |
+----------------------------------------------------------------+-----------+
| /index                                                         | 0.000507s |
| /end                                                           | 0.000745s |
'----------------------------------------------------------------+-----------'

The development server was nice enough to tell us where to go. Hitting
http://localhost:3000 in our browser yields:


catalyst run

Django

Creating the application

Rinse. Lather. Repeat. As with the previous two frameworks, we just
need to run a command line script to create a new application.

claco@mbp ~/mvc-marathon/django $ django-admin.py
Usage: django-admin.py action [options]
actions:
adminindex [appname ...]
Prints the admin-index template snippet for the given app name(s).

createcachetable [tablename]
Creates the table needed to use the SQL cache backend

dbshell
Runs the command-line client for the current DATABASE_ENGINE.

diffsettings
Displays differences between the current settings.py and Django's
default settings. Settings that don't appear in the defaults are
followed by "###".

dumpdata [--format][appname ...]
Output the contents of the database as a fixture of the given
format

flush [--verbosity] [--interactive]
Executes ``sqlflush`` on the current database.

inspectdb
Introspects the database tables in the given database and outputs
a Django model module.

loaddata [--verbosity] fixture, fixture, ...
Installs the named fixture(s) in the database

reset [--interactive][appname ...]
Executes ``sqlreset`` for the given app(s) in the current
database.

runfcgi [various KEY=val options, use `runfcgi help` for help]
Runs this project as a FastCGI application. Requires flup.

runserver [--noreload] [--adminmedia=ADMIN_MEDIA_PATH] [optional port number, or ipaddr:port]
Starts a lightweight Web server for development.

shell [--plain]
Runs a Python interactive interpreter. Tries to use IPython, if
it's available.

sql [appname ...]
Prints the CREATE TABLE SQL statements for the given app name(s).

sqlall [appname ...]
Prints the CREATE TABLE, initial-data and CREATE INDEX SQL
statements for the given model module name(s).

sqlclear [appname ...]
Prints the DROP TABLE SQL statements for the given app name(s).

sqlcustom [appname ...]
Prints the custom table modifying SQL statements for the given app
name(s).

sqlflush
Returns a list of the SQL statements required to return all tables
in the database to the state they were in just after they were
installed.

sqlindexes [appname ...]
Prints the CREATE INDEX SQL statements for the given model module
name(s).

sqlinitialdata
RENAMED: see 'sqlcustom'

sqlreset [appname ...]
Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the
given app name(s).

sqlsequencereset [appname ...]
Prints the SQL statements for resetting PostgreSQL sequences for
the given app name(s).

startapp [appname]
Creates a Django app directory structure for the given app name in
the current directory.

startproject [projectname]
Creates a Django project directory structure for the given project
name in the current directory.

syncdb [--verbosity] [--interactive]
Create the database tables for all apps in INSTALLED_APPS whose
tables haven't already been created.

test [--verbosity] [appname ...]
Runs the test suite for the specified applications, or the entire
site if no apps are specified

validate
Validates all installed models.

Options:
--version             show program's version number and exit
-h, --help            show this help message and exit
--settings=SETTINGS   Python path to settings module, e.g.
"myproject.settings.main". If this isn't provided, the
DJANGO_SETTINGS_MODULE environment variable will be
used.
--pythonpath=PYTHONPATH
Lets you manually add a directory the Python path,
e.g. "/home/djangoprojects/myproject".
--plain               Tells Django to use plain Python, not IPython, for
"shell" command.
--noinput             Tells Django to NOT prompt the user for input of any
kind.
--noreload            Tells Django to NOT use the auto-reloader when running
the development server.
--format=FORMAT       Specifies the output serialization format for fixtures
--indent=INDENT       Specifies the indent level to use when pretty-printing
output
--verbosity=VERBOSITY
Verbosity level; 0=minimal output, 1=normal output,
2=all output
--adminmedia=ADMIN_MEDIA_PATH
Specifies the directory from which to serve admin
media for runserver.

To create a new app, we’ll run the startproject command:

claco@mbp ~/mvc-marathon/django $ django-admin.py startproject BurningPlate

Unfortunately, no matter what –verbosity level I set, this is the
only output I get.

Running the Application

claco@mbp ~/mvc-marathon/django $ python BurningPlate/manage.py runserver
Validating models...
0 errors found.

Django version 0.96.2, using settings 'BurningPlate.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[28/Jun/2008 18:40:25] "GET / HTTP/1.1" 404 2065

Lets hit http://127.0.0.1:8000/ and see what we have:


django run

Ruby on Rails

Creating the Application

Another framework; another script.

claco@mbp ~/mvc-marathon/rails $ rails
Usage: /sw/bin/rails /path/to/your/app [options]

Options:
-r, --ruby=path                  Path to the Ruby binary of your choice (otherwise scripts use env, dispatchers current path).
Default: /sw/bin/ruby1.8
-d, --database=name              Preconfigure for selected database (options: mysql/oracle/postgresql/sqlite2/sqlite3).
Default: mysql
-f, --freeze                     Freeze Rails in vendor/rails from the gems generating the skeleton
Default: false

Rails Info:
-v, --version                    Show the Rails version number and quit.
-h, --help                       Show this help message and quit.

General Options:
-p, --pretend                    Run but do not make any changes.
--force                      Overwrite files that already exist.
-s, --skip                       Skip files that already exist.
-q, --quiet                      Suppress normal output.
-t, --backtrace                  Debugging: show backtrace on errors.
-c, --svn                        Modify files with subversion. (Note: svn must be in path)

Description:
The 'rails' command creates a new Rails application with a default
directory structure and configuration at the path you specify.

Example:
rails ~/Code/Ruby/weblog

This generates a skeletal Rails installation in ~/Code/Ruby/weblog.
See the README in the newly created application to get going.

Looks like Catalyst. Give it an app name and go:

claco@mbp ~/mvc-marathon/rails $ rails BurningPlate
create
create  app/controllers
create  app/helpers
create  app/models
create  app/views/layouts
create  config/environments
create  config/initializers
create  db
create  doc
create  lib
create  lib/tasks
create  log
create  public/images
create  public/javascripts
create  public/stylesheets
create  script/performance
create  script/process
create  test/fixtures
create  test/functional
create  test/integration
create  test/mocks/development
create  test/mocks/test
create  test/unit
create  vendor
create  vendor/plugins
create  tmp/sessions
create  tmp/sockets
create  tmp/cache
create  tmp/pids
create  Rakefile
create  README
create  app/controllers/application.rb
create  app/helpers/application_helper.rb
create  test/test_helper.rb
create  config/database.yml
create  config/routes.rb
create  public/.htaccess
create  config/initializers/inflections.rb
create  config/initializers/mime_types.rb
create  config/boot.rb
create  config/environment.rb
create  config/environments/production.rb
create  config/environments/development.rb
create  config/environments/test.rb
create  script/about
create  script/console
create  script/destroy
create  script/generate
create  script/performance/benchmarker
create  script/performance/profiler
create  script/performance/request
create  script/process/reaper
create  script/process/spawner
create  script/process/inspector
create  script/runner
create  script/server
create  script/plugin
create  public/dispatch.rb
create  public/dispatch.cgi
create  public/dispatch.fcgi
create  public/404.html
create  public/422.html
create  public/500.html
create  public/index.html
create  public/favicon.ico
create  public/robots.txt
create  public/images/rails.png
create  public/javascripts/prototype.js
create  public/javascripts/effects.js
create  public/javascripts/dragdrop.js
create  public/javascripts/controls.js
create  public/javascripts/application.js
create  doc/README_FOR_APP
create  log/server.log
create  log/production.log
create  log/development.log
create  log/test.log

Running the Application

claco@mbp ~/mvc-marathon/rails $ BurningPlate/script/server
=> Booting WEBrick...
=> Rails application started on http://0.0.0.0:3000
=> Ctrl-C to shutdown server; call with --help for options
[2008-06-28 19:20:36] INFO  WEBrick 1.3.1
[2008-06-28 19:20:36] INFO  ruby 1.8.6 (2008-03-03) [i686-darwin]
[2008-06-28 19:20:36] INFO  WEBrick::HTTPServer#start: pid=1929 port=3000
127.0.0.1 - - [28/Jun/2008:19:20:41 EDT] "GET / HTTP/1.1" 200 7557
- -> /
127.0.0.1 - - [28/Jun/2008:19:20:41 EDT] "GET /javascripts/prototype.js HTTP/1.1" 200 125605
http://localhost:3000/ -> /javascripts/prototype.js
127.0.0.1 - - [28/Jun/2008:19:20:41 EDT] "GET /javascripts/effects.js HTTP/1.1" 200 38916
http://localhost:3000/ -> /javascripts/effects.js
127.0.0.1 - - [28/Jun/2008:19:20:41 EDT] "GET /images/rails.png HTTP/1.1" 200 1787
http://localhost:3000/ -> /images/rails.png

Loading http://0.0.0.0:3000 in our browser, we see:


rails run

Conclusions

There weren’t too many shockers here. For the most part, it’s just a
matter of running a command to get an application structure up and
running. There are some areas that could use a few tweaks to help out
the people new to their respective frameworks:

  • CakePHP really could use a development server. For development,
    it’s nice to not have to mess with Apache configs.
  • Django needs to output something when it’s successfully created
    a new application. No output equates to thinking nothing happened
    or that something is broken.
  • Catalyst, Django and Rails would be a touch better if after they
    created the new applications would tell the user how to run them.
    i.e. “App created. please run xxx xxxx/xxxxx ro start your new
    app”.
  • All of the frameworks except for ASP.NET MVC tell the user where
    to go next and/or what files to start to configure. It would be
    nice if ASP.NET MVC did the ame thing rather than just display
    the “Home” page.

MVC Marathon

June 29th, 2008

Over the last few years, I’ve spent a fair amount of time on the same projects, using the same tools, in the same languages. By day, I’m a mild mannered .NET programmer. By night, I fly the Perl flag, and spend a lot of time using Catalyst, one of the Perl based MVC frameworks. While I’m not abandoning those technologies, I think it’s time for something new.

They say you should learn a new programming language every year or
so, and I’m long over due. Why not learn three languages at the
same time to make up for lost time? :-)

The Challenge

The challenge is to build the same mvc application on all of the
frameworks listed below. While I’m comfortable with Catalyst/Perl
and ASP.NET, I’d like to be able to work on any project that comes
along rather than simply answering “Sorry, I can’t help. I don’t
program in that frameowork/language.” I started my career as a
Windows-only web programmer and over time, learned my way through
FreeBSD, Apache, Perl and Catalyst.

Will I be an ‘expert’ in Ruby or Python when I’m done? Surely not.
But 3 years ago, I couldn’t program in Perl either and 10 years
ago, I could’t even begin to get a *nix OS installed and
configured to run Apache.

The Frameworks

For this adventure, I’ve picked what might be considered the most
popular mvc framework for each language. These frameworks will be:

ASP.NET MVC: Preview 3

While my day job includes programming in ASP.NET 2.0 WebForms and
I have followed the MVC progress, I have not yet created an
ASP.NET MVC app. Just to be a little more challenging, I will be
doing this project in C# instead of the usual VB.NET that we use
at work.

CakePHP: 1.2RC2

I’ve read as much of the manual as I can at this point. This will
be my first CakePHP application and my first PHP programming since
PHP4 was released.

Catalyst: 5.7

Catalyst is one of the many MVC frameworks written in Perl. I’ve
been using Perl and Catalyst for about 3 years. While this could
be my strongest framework, I’ll be using none of my existing bag
of tricks.

Django: 0.96

I’ve never written a single line of Python before. After looking
at the Python and Ruby syntax on multiple occasions in the past,
Python seems to be the language furthest from how my brain works
now compared to Perl/C#.

Ruby on Rails: 2.1

Just like Django/Python, I’ve never written a line if Ruby in my
life. I’ve been through the screen casts and sifted around a few
articles and book excerpts. However, unlike Python, Ruby seems
to fit how my brain works with Perl a littler cleaner.

The Application

I’m sick of the usual blog demo applications so I’m going to go
with something a little closer to my heart. Those who know me know
that I’m a big fan of hot food: If you don’t sweat, it’s not hot
enough. So for this challenge, I’m going to build a site that
tracks lists of restaurants and a list of menu items for each
eatery hot dish.

The application will be built in stages. Each stage will be built
in every framework before moving on to the next step. After
completing each stage, I’ll post a new blog entry covering what
code needed to be created in each framework and the pros and cons
each framework provides. You can follow the source code progress
in my subversion repository:


http://github.com/claco/mvc-marathon/

Step by Step

  1. Part 1. Creating a New Application
  2. Part 2. Creating a Database and Model
  3. Part 3. Creating a Restaurants Controller and View