CodeIgniter to Ruby on Rails: A Conversion

From CodeIgniter to Ruby on Rails: A Conversion

Jul 30th in Ruby by Dan Harper

Today, we'll be creating a simple shoutbox using the CodeIgniter PHP framework. We'll then port this exact application, piece-by-piece, to Ruby on Rails!

PG

Author: Dan Harper

This is a NETTUTS contributor who has published 11 tutorial(s) so far here. Their bio is coming soon!

Final Product

Throughout this tutorial, we'll create a simple shoutbox application in CodeIgniter, and then port it over to Ruby on Rails. We'll be comparing the code between the two applications to see where they are similar, and where they differ. Learning Rails is much easier if you are already familiar with an MVC (Model, View, Controller) framework structure.

Do I Need Previous CodeIgniter Experience?

Yes, or experience in PHP and another MVC framework (CakePHP, Zend etc.). Check out some of the other CI tutorials here on Nettuts, including the from scratch video series!

Do I Need Previous Ruby/Rails Experience?

While it would help, no. I've done my best to make direct comparisons between Ruby and PHP, and between Rails and CodeIgniter, so by applying your existing knowledge it won't be too hard.

Hello, CodeIgniter

Begin by downloading the CodeIgniter framework to your local/web server, and set up your database (I named it ci_shoutbox) with the following SQL commands:

CREATE TABLE `shouts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL,
  `email` varchar(255) NOT NULL,
  `message` text NOT NULL,
  PRIMARY KEY (`id`)
);

INSERT INTO `shouts` (`id`,`name`,`email`,`message`)
VALUES
  (1,'Dan Harper','email@example.com','Hello, World!'),
  (2,'Bob','a@a.com','This looks great!'),
  (3,'Dan Harper','email@example.com','Hey, thanks for the comment!');

As usual in CodeIgniter, enter your details into /system/application/config/database.php:

$db['default']['hostname'] = "localhost";
$db['default']['username'] = "root";
$db['default']['password'] = "";
$db['default']['database'] = "ci_shoutbox";

Then config.php:

$config['base_url']	= "http://localhost/shoutbox/";
...
$config['global_xss_filtering'] = TRUE;
...
$config['rewrite_short_tags'] = TRUE;

And autoload.php:

$autoload['libraries'] = array('database', 'form_validation', 'session');
$autoload['helper'] = array('url', 'form');
...
$autoload['model'] = array('shout');

Finally set the default controller inside routes.php:

$route['default_controller'] = "shouts";

Controller & Model

Since this is such a simple application, we only need one controller and one model. Inside /controllers/ create shouts.php:

<?php
class Shouts extends Controller {

  function Shouts() {
    parent::Controller();
  }

}

And the model as /models/shout.php:

<?php
class Shout extends Model {

  function Shout() {
    parent::Model();
  }

}

Retrieving the Shouts

Our front page, the index, outputs the 10 latest shouts from the database. Start by adding the following to the Shouts controller:

function index() {
  $data['shouts'] = $this->shout->all_shouts();
  $this->load->view('shouts/index.php', $data);
}

On line 2, we call the all_shouts() function from the Shout model.
Following this, we load the relevant view file, and pass the $data variable along to it (CodeIgniter will automatically take apart the $data array, so that we can access the shouts simply as $shouts instead of $data['shouts']).

Calling the Database

Let's create the all_shouts() function in our model now:

function all_shouts() {
  $data = array();
  $this->db->order_by('id', 'DESC');
  $q = $this->db->get('shouts', 10);
  
  if ($q->num_rows() > 0) {
    foreach ($q->result() as $row) {
      $data[] = $row;
    }
  }
  
  return $data;
  $q->free_result();
}

This is relatively straight-forward. We retrieve the 10 most recent records from the 'shouts' table, and output them in descending order. This will build the following SQL command behind-the-scenes:

SELECT * FROM `shouts` ORDER BY `id` DESC LIMIT 10;

If any records were found, they are added to the $data array and returned to the controller.

The View

Inside the /views/ directory, create the following files and folders:

  • footer.php
  • header.php
  • /shouts/
    • index.php

Add the following to /shouts/index.php:

<?php
$this->load->view('/header.php');

if ($shouts) {
  echo '<ul>';
  foreach ($shouts as $shout) {
    $gravatar = 'http://www.gravatar.com/avatar.php?gravatar_id=' . md5(strtolower($shout->email)) . '&size=70';
    ?>

    <li>
      <div class="meta">
        <img src="<?= $gravatar ?>" alt="Gravatar" />
        <p><?= $shout->name ?></p>
      </div>
      <div class="shout">
        <p><?= $shout->message ?></p>
      </div>
    </li>

    <?php
  }
}
else {
  echo '<p class="error">No shouts found!</p>';
}

$this->load->view('/footer.php');

On line 2 we include header.php.
If any shouts were returned from the database, we loop through them using foreach and create a basic list with the author's Gravatar, their name and the message. If no shouts were found, we display an error message.
Finally, we include the footer.php.

Note that on line 7, we build the Gravatar URL. The URL contains an MD5 hash of the user's email address. We use PHP's strtolower() function to ensure the email is all lower-case in case the Gravatar service is case-sensitive.

Continuing on, we need a form at the bottom of the page to add a new shout. Before we include the footer view file, add the following:

echo form_open('shouts/create'); ?>
  <h2>Shout!</h2>

  <div class="fname">
    <label for="name"><p>Name:</p></label>
    <input type="text" name="name" value="<?= set_value('name') ?>" />
    </div>

  <div class="femail">
    <label for="email"><p>Email:</p></label>
    <input type="text" name="email" value="<?= set_value('email') ?>" />
  </div>

  <textarea name="message" rows="5" cols="40"><?= set_value('message') ?></textarea>

  <p><input type="submit" value="Submit" /></p>
  <?php
echo form_close();
$this->load->view('/footer.php');

On line 1 we use the form_open() function from CodeIgniter's Form helper. This generates a <form> opening tag with the relevant parameters, and will point to shouts/create (the Create function inside the Shouts controller).
The rest is a normal HTML form, except we echo the set_value() function for each 'value'. We will be adding validation to the form shortly and this ensures that if there are any errors, the submitted data is automatically re-entered into the form.

Finally, we'll need to create the header & footer. Add the following to /views/header.php:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Shoutbox in CodeIgniter</title>
<link rel="stylesheet" href="<?php echo base_url(); ?>css/style.css" type="text/css" />
</head>
<body>
<div id="container">

  <h1>Shoutbox</h1>
  <h5>
    <a href="http://www.danharper.me" title="Dan Harper">Dan Harper </a> :
    <a href="http://net.tutsplus.com" title="Nettuts - Spoonfed Coding Skills">Nettuts</a>
  </h5>

  <div id="boxtop"></div>
  <div id="content">

See on line 8, when including the stylesheet, we use the base_url() function from CodeIgniter's URL helper to generate the URL to the root of the site – this will put the stylesheet at http://example.com/css/style.css.

Add the following to /views/footer.php:

  </div><!--/content-->
<div id="boxbot"></div>
</div><!--/container-->
</body>
</html>

Submission & Validation

Since the form directs to a Create function in the Shouts controller, let's create that now:

function create() {
  $data['shouts'] = $this->shout->all_shouts();
  
  $this->form_validation->set_rules('name', 'Name', 'required|max_length[255]|htmlspecialchars');
  $this->form_validation->set_rules('email', 'Email', 'valid_email|required|max_length[255]|htmlspecialchars');
  $this->form_validation->set_rules('message', 'Shout', 'required|htmlspecialchars');
  
  if ($this->form_validation->run() == FALSE) {
    $this->load->view('shouts/index.php', $data);
  }
  else {
    $this->shout->create();
    $this->session->set_flashdata('success', 'Thanks for shouting!');
    redirect('shouts/index');
  }
}

On line 2 we retrieve the shouts (as we did on the index function), as they still need to be shown.
Lines 4-6 are our validation rules. We first pass the input's 'name' from the submitted form to check, followed by a 'human-readable' version. The third parameter holds a pipe-separated ('|') list of validation rules.

For example, we set all the fields as 'required' as well as run them through 'htmlspecialchars' before submission. 'Name' and 'Email' have a max length set, and the submitted email field must resemble an email address.

If the validation fails, the index view is loaded again; otherwise we run the create() function from the Shout model. On line 13, we set a success message in the flash data which will be displayed on the next executed page.

Before continuing, we must display the error and success messages (see images above) in our view. Add the following directly after we include the header in /views/shouts/index.php:

if ($this->session->flashdata('success')) {
  echo '<p class="success">' . $this->session->flashdata('success') . '</p>';
}

echo validation_errors('<p class="error">', '</p>');

Insert Into Database

Inside the Shout model, enter the following:

function create() {
  $data = array(
    'name'    =>  $this->form_validation->set_value('name'),
    'email'   =>  $this->form_validation->set_value('email'),
    'message' =>  $this->form_validation->set_value('message')
  );
  $this->db->insert('shouts', $data);
}

Very simple, we compile an array containing the submitted data, and insert it into the `shouts` table.

Styling

In the very root directory for your application – in the same directory as CodeIgniter's /system/ folder, create two new folders: /css/ and /images/.
In the CSS folder, add the following to a file named style.css:

* {
margin: 0;
padding: 0;
}

body {
background: #323f66 top center url("../images/back.png") no-repeat;
color: #ffffff;
font-family: Helvetica, Arial, Verdana, sans-serif;
}

h1 {
font-size: 3.5em;
letter-spacing: -1px;
background: url("../images/shoutbox.png") no-repeat;
width: 303px;
height: 66px;
margin: 0 auto;
text-indent: -9999em;
color: #33ccff;
}

h2 {
font-size: 2em;
letter-spacing: -1px;
background: url("../images/shout.png") no-repeat;
width: 119px;
height: 44px;
text-indent: -9999em;
color: #33ccff;
clear: both;
margin: 15px 0;
}

h5 a:link, h5 a:visited {
color: #ffffff;
text-decoration: none;
}

h5 a:hover, h5 a:active, h5 a:focus {
border-bottom: 1px solid #fff;
}

p {
font-size: 0.9em;
line-height: 1.3em;
font-family: Lucida Sans Unicode, Helvetica, Arial, Verdana, sans-serif;
}

p.error, .errorExplanation li {
background-color: #603131;
border: 1px solid #5c2d2d;
padding: 10px !important;
margin-bottom: 15px;
}

p.success {
background-color: #313d60;
border: 1px solid #2d395c;
padding: 10px;
margin-bottom: 15px;
}

#container {
width: 664px;
margin: 20px auto;
text-align: center;
}

#boxtop {
margin: 30px auto 0px;
background: url("../images/top.png") no-repeat;
width: 663px;
height: 23px;
}

#boxbot {
margin: 0px auto 30px;
background: url("../images/bot.png") no-repeat;
width: 664px;
height: 25px;
}

#content {
margin: 0 auto;
width: 600px;
text-align: left;
background: url("../images/bg.png") repeat-y;
padding: 15px 35px;
overflow: hidden;
}

#content ul {
margin-left: 0;
margin-bottom: 15px;
}

#content ul li {
list-style: none;
clear: both;
padding-top: 30px;
}

#content ul li:first-child {
padding-top:0;
}

.meta {
width: 85px;
text-align: left;
float: left;
min-height: 110px;
font-weight: bold;
}

.meta img {
padding: 5px;
background-color: #313d60;
}

.meta p {
font-size: 0.8em;
}

.shout {
width: 500px;
float: left;
margin-left: 15px;
min-height: 110px;
padding-top: 5px;
}

form {
clear: both;
margin-top: 135px !important;
}

.fname, .femail {
width: 222px;
float: left;
}

form p {
font-weight: bold;
margin-bottom: 3px;
}

form textarea {
width: 365px;
overflow: hidden;
}

form input, form textarea {
background-color: #313d60;
border: 1px solid #2d395c;
color: #ffffff;
padding: 5px;
font-family: Lucida Sans Unicode, Helvetica, Arial, Verdana, sans-serif;
margin-bottom: 10px;
}

And save the following images into the /images/ folder: (the images will be the correct size when saved!)

back.png
bg.png
bot.png
shout.png
shoutbox.png
top.png

To confirm, your directory structure should look like the image below:

And... done! If you think we wrote very little actual back-end code in CI, wait until we get to Rails where all database interaction is automated!

 

Hello, Rails

For this tutorial, I assume you already have Ruby, Rails and MySQL (or your preferred database engine) installed correctly on your system. If not, see the Rails installation guide and MySQL download page. If you happen to be running Mac OSX Leopard, Ruby and Rails both come pre-installed.

It's also useful to know how to navigate in your operating system using the command line. For example to change directory, use cd foldername, or cd C:\Users\Dan etc.

Rails provides a number of command-line tools to speed up development of your application. Inside the Terminal (or Command Prompt in Windows), navigate to the folder you wish to store your Rails projects in. Personally I store them in a /rails/ directory in my User area.

The Set Up

Run the following to dump a copy of Rails for your application:

rails -d mysql shoutbox

Here, Rails will install into a /shoutbox/ directory. As MySQL is my preferred database engine, I specify -d mysql. If you prefer to use MySQLlite (the default in Rails), you would simply run rails shoutbox.
Navigate into the shoutbox directory:

cd shoutbox

Now, we'll generate the Controller and Model:

ruby script/generate controller Shouts

ruby script/generate model Shout

Rails has now generated our 'Shouts' controller and 'Shout' model. It's important to know that Rails encourages a strict naming style.
Your controller should be named the plural of whatever it will deal with (eg. Shouts, Users, Listings). A controller typically will only deal with one model, which is named in the singular (eg. Shout, User, Listing). Following this standard will ensure Rails can use all it's functionality.

Inside your shoutbox directory, open the /config/database.yml file, this stores our database details. Enter your details under the 'development' section:

development:
  adapter: mysql
  encoding: utf8
  reconnect: false
  database: shoutbox_development
  pool: 5
  username: root
  password:
  socket: /tmp/mysql.sock

You can see Rails has automatically set the configuration file to be using the MySQL adapter. You will likely only need to alter the username and password. Don't worry, you should not have created the shoutbox_development database yet. We'll create that next.

Next, open the /db/migrate/***_create_shouts.rb file (most versions of Rails will prefix the file name with the date and time):

class CreateShouts < ActiveRecord::Migration
  def self.up
    create_table :shouts do |t|

      t.timestamps
    end
  end

  def self.down
    drop_table :shouts
  end
end

This is a database migration file. Rails use these to help developers keep their databases structured correctly since they can become messy when several developers are altering database tables at the same time.
You will also be able to roll your database back to a previous version if you need to.

In here, we will add our database fields:

class CreateShouts < ActiveRecord::Migration
  def self.up
    create_table :shouts do |t|
      t.string :name
      t.string :email
      t.text :message
      t.timestamps
    end
  end

  def self.down
    drop_table :shouts
  end
end

This may look scary, but it's very simple! t.string :name creates a 'name' field in the database, which will be stored as a string (ie. 'varchar'). The same goes for the 'email' field.
A 'message' field is added and stored as text (as it is in SQL).

You will notice Rails automatically included t.timestamps. This includes 'created_at' and 'updated_at' fields which Rails will automatically fill in when a field is created or updated.
Also, Rails will automatically create an 'id' field, so we don't need to define that ourselves.

This is equivalent to the following SQL command:

CREATE TABLE `shouts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `message` text,
  `ipaddress` varchar(15) DEFAULT NULL,
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
);

While it can take a little getting-used-to when creating these migrations, they're much easier to type than SQL!

Finally, run the following two commands in the Terminal to create and set up the database:

rake db:create

rake db:migrate

The first command creates the database, named 'shoutbox_development'. The second runs any out-standing database migrations (in this case, our ***_create_shouts.rb file).

Start the server with and then visit http://localhost:3000 (or whatever port the server says it is running on – in most cases it's 3000):

ruby script/server

To remove the default welcome page, first remove the file at /public/index.html, then inside /config/routes.rb, insert the following anywhere between the first and last lines:

map.root :controller => "shouts"

You will need to restart the server whenever you make alterations to the controller or to the routes – press Ctrl+C, then re-run ruby script/server.

That concludes the configuration process for Rails. While this whole process may seem complicated right now, you can do it in just a few minutes, and once you get used to it, it's much quicker than setting up a CodeIgniter project.

Dummy Data

Before display the shouts, we'll need some data in the database to display. You could create these entries through your preferred database management program, however we're going to use a great Rails feature instead – the interactive console.

From your application folder in the Terminal, run ruby script/console. This is an interactive Ruby interpreter, which is also hooked up to our Rails application, so we can interact with it!

One very important thing to remember with regard to Rails, is that your model is automatically linked with the corresponding database table. For example, your 'Shout' model is linked with the 'shouts' table in your database – this is a key reason why following Rails' naming conventions can be very useful!

Run the following ruby code inside the interactive console:

shout = Shout.new(
  :name => 'Dan Harper',
  :email => 'email@example.com',
  :message => 'Hello, World!')

Here, we invoke Rails' new method on the 'Shout' model – this will create a new record in the shouts database table. We also pass a hash containing the data we want to enter.
This is stored in the 'shout' variable.

Note: A 'hash' is what PHP calls an associate array – an array where you can also set your own key.
A rough PHP equivalent of this code would be:

$shout = $this->shout->new(
  'name' => 'Dan Harper',
  'email' => 'email@example.com',
  'message' => 'Hello, World!');

Finally, save this into the database with:

shout.save

If you entered the code correctly, ruby should return 'true'.
You can now retrieve this from the database with:

shout = Shout.find :last

shout.name

The first command will find the most recent row in the shouts table and store it in the 'shout' variable. We can find look at a specific item with shout.name which should return whatever you entered as the 'name' for the record.

Repeat the Shout.new and Shout.save process a few more times to add more records to the database.

Displaying Shouts

Inside the /app/controllers/shouts_controller.rb file, enter the following between the existing class statement:

def index
  @shouts = Shout.all_shouts
end

We call the all_shouts method from the 'Shout' model, and store the result inside the shouts instance variable (as seen by the @).

In CodeIgniter, the equivalent code was:

class Shouts extends Controller {

  function Shouts() {
    parent::Controller();
  }

  function index() {
    $data['shouts'] = $this->shout->all_shouts();
    $this->load->view('shouts/index.php');
  }

}

Some noticeable differences are:

  1. Rails uses the '<' symbol in place of 'extends' when creating a class;
  2. To create a function/method we use def instead of function;
  3. Rails doesn't require a constructor method;
  4. Rails automatically loads the view file for the current method. In this case, the view file is located at /app/views/shouts/index.html.erb;
  5. It is already noticeable that code is much cleaner in Rails. You don't need parenthesis (brackets) when defining a funcion/method if it doesn't contain any parameters, we don't need to add a semi-colon at the end of each statement and there are no curly brackets in sight!

The Model

Inside /app/models/shout.rb enter the following inside the class to define the all_shouts function:

def self.all_shouts
  Shout.find(:all, :limit => 10, :order => 'id DESC')
end

Here, we run Rails' find on the Shout model (ie. the shouts table). We pass a hash telling the method to limit the number of results to 10 in descending order.
This is the equivalent CodeIgniter code:

function all_shouts() {
  $data = array();
  $this->db->order_by('id', 'DESC');
  $q = $this->db->get('shouts', 10);

  if ($q->num_rows() > 0) {
    foreach ($q->result() as $row) {
      $data[] = $row;
    }
  }

  return $data;
  $q->free_result();
}

Yeah… so much simpler!
Also note that in Ruby, if you don't specifically return something, the last statement is automatically returned. For example, we could have used the following instead (although it would make no difference):

def self.all_shouts
  return Shout.find(:all, :limit => 10, :order => 'id DESC')
end

The View

In CodeIgniter, we had to manually include header & footer files in each view. Rails takes a slightly different approach. Inside /views/layouts/ create a file named application.html.erb with the following inside:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Shoutbox in Ruby on Rails</title>
<%= stylesheet_link_tag 'style', :media => 'screen' %>
</head>
<body>
<div id="container">

  <h1>Shoutbox</h1>
  <h5>
    <a href="http://www.danharper.me" title="Web Developer">Dan Harper </a> :
    <a href="http://net.tutsplus.com" title="Nettuts - Spoonfed Coding Skills">Nettuts</a>
  </h5>

  <div id="boxtop"></div>
  <div id="content">

    <%= yield %>

  </div><!--/content-->
<div id="boxbot"></div>
</div><!--/container-->
</body>
</html>

This is predominantly a normal HTML layout, combining both our header and footer. On line 7 we include a link to a stylesheet. Rails provides a /public/ folder in which you include images, stylesheet, javascript files and any static HTML files. In this case, the code will output a link to a stylesheet at /public/stylesheets/style.css.

Further down, on line 21 is <%= yield %>. This will insert the main view file for the specific page in this place.

Why .html.erb? All view files in Rails first have the extension for the specific format you want to output as, followed by .erb so the server will interpret the Ruby code inside it.
This means you could also have a .xml.erb file if you want to export in XML for certain pages, etc.

<%= %> Ruby uses <% and %> to wrap any Ruby code to be interpreted, in the same way PHP uses <?php and ?>.
On HTML pages, we use <%= ... %> (note the equals) to 'print' code. In PHP we use either <?php echo ... ?> or <?= ... ?> to 'echo' code.

Inside /public/stylesheets/ create a file named style.css containing the same CSS we used in the CodeIgniter section.
Also, paste the images from the CodeIgniter section into /public/images/.

If you restart the server now, and reload your browser, you should see a 'Template is missing' error page. This is because we haven't yet created the actual view for the current page.

The 'Template is missing' error page tells us what file it was expecting: "Missing template shouts/index.html.erb in view path app/views" So let's create that file now, and enter the following to test it out:

Hello, World!

Reload the page, and you should be greeted with the following (if not, check you have the stylesheet & images in the correct locations):

Looping

You will remember that in CodeIgniter, we used the code below to loop through and display each retrieved shout:

<?php
echo '<ul>';
foreach ($shouts as $shout) {
  $gravatar = 'http://www.gravatar.com/avatar.php?gravatar_id=' . md5(strtolower($shout->email)) . '&size=70';
  ?>

  <li>
    <div class="meta">
      <img src="<?= $gravatar ?>" alt="Gravatar" />
      <p><?= $shout->name ?></p>
    </div>
    <div class="shout">
      <p><?= $shout->message ?></p>
    </div>
  </li>

  <?php
}
echo '</ul>';

The equivalent in Ruby is the following (replace the 'Hello, World!' message with this):

<ul>
<% for shout in @shouts

  gravatar = 'http://www.gravatar.com/avatar.php?gravatar_id=' + Digest::MD5.hexdigest(h(shout.email.downcase)) + '&size=70' %>

  <li>
    <div class="meta">
      <img src="<%= gravatar %>" alt="Gravatar" />
      <p><%= shout.name %></p>
    </div>
    <div class="shout">
      <p><%= shout.message %></p>
    </div>
  </li>

<% end %>
</ul>

On line 2 is a normal 'foreach'-style loop in Ruby. Instead of the foreach ($shouts as $shout) in PHP, we use for shout in @shouts in Ruby.

The loop closes at end on line 16.

On line 4 we build the Gravatar URL in almost the same way as in PHP, with a few exceptions:

  1. In PHP, we use the dot character (.) to concatenate statements, Ruby uses the plus character (+), just like in JavaScript;
     
  2. Ruby's version of PHP's md5() function is Digest::MD5.hexdigest();
     
  3. The h() function in Ruby is equivalent to PHP's htmlspecialchars();
     
  4. PHP uses -> to access an object (eg. $shout->email). Ruby uses a dot, (eg. shout.email);
     
  5. To ensure the email address is in lower-case, we use strtolower($shout->email) in PHP. In Ruby, everything is an object and so we simply add .downcase to the end of a string or variable – shout.email.downcase. You could also .upcase, .reverse, etc.

Refresh the page in your browser, and you should now see the shoutbox messages!

Submission Form

Continuing on, we need to include the form at the bottom of the page for submitting new shouts. First, take a look at the following line:

<% form_for :shout, :url => { :action => 'create' } do |f| %>
  ... code here ...
<% end %>

This is a block from Rails' form helper to easily create forms. form_for :shout links the form directly with the Shout model.

:url = { :action = 'create ' } is a hash detailing where the form will direct to when submitted. In this case, the form will go to /shouts/create/ (Shouts controller, Create method). We could have typed: :url = { :controller = 'shouts', :action = 'create }, however the lack of a 'controller' key in the hash tells Rails to use the current controller.

Finally, do |f| stores everything before in an f variable. For example, we could create a textbox linked to this using f.text_field

Now we've explained that, enter this full form code at the bottom of your index view file:

<% form_for :shout, :url => { :action => 'create' } do |f| %>

  <h2>Shout!</h2>

  <div class="fname">
    <label for="name"><p>Name:</p></label>
    <%= f.text_field :name, :size => 20 %>
  </div>

  <div class="femail">
    <label for="email"><p>Email:</p></label>
    <%= f.text_field :email, :size => 20 %>
  </div>

  <%= f.text_area :message, :rows => 5, :cols => 40 %>

  <p><%= submit_tag 'Submit' %></p>

<% end %>

And compare it with the CodeIgniter PHP version:

echo form_open('shouts/create'); ?>
  <h2>Shout!</h2>

  <div class="fname">
    <label for="name"><p>Name:</p></label>
    <input type="text" name="name" value="<?= set_value('name') ?>" />
  </div>

  <div class="femail">
    <label for="email"><p>Email:</p></label>
    <input type="text" name="email" value="<?= set_value('email') ?>" />
  </div>

  <textarea name="message" rows="5" cols="40"><?= set_value('message') ?></textarea>

  <p><input type="submit" value="Submit" /></p>
  <?php
echo form_close();

We make more use of Rails' form helper when creating the input fields, textarea and submit button. With f.text_field :name, :size = 20 we are creating a textbox named 'name' (matching the column in our database this should insert into). This would output in HTML as:

<input type="text" name="shout[name]" id="shout_name" size="20" />

In the CodeIgniter version of the app, we set each input's 'value' field to <?= set_value('email') ?> so CodeIgniter will automatically re-populate the fields if they fail the validation tests.
In Rails, this is handled automatically since we are using Rails' form helper to create the inputs.

Refresh the page in your browser, and you should see the form at the bottom:

Submission & Validation

The next step is to insert any submitted data into the database. The form directs to shouts/create, so let's create the create action now (inside the Controller):

def create
  @shouts = Shout.all_shouts

  @shout = Shout.new(params[:shout])
  if @shout.save
    flash[:notice] = 'Thanks for shouting!'
    redirect_to :action => 'index'
  else
    render :action => 'index'
  end
end

On line 2, we retrieve all our current shouts from the database since they still need to be displayed.

At line 4 we load Rails' new function on our Shout model. You will remember we used Shout.new when inserting dummy data into the database via the interactive console.
We pass new our submitted data with params[] – this is how Rails accesses POST data.
This is loaded into the @shout instance variable.

When we used the interactive console, we used shout.save to save the data. If it was successful, it returns 'true', otherwise 'false' is returned. So on line 5 we check whether the shout can be saved into the database, if so, we load a success notice in the session's 'flashdata', and redirect back to the index.
Otherwise, we load index's view file through the render function.

Validation

You may be wondering where our validation is handled, since we did this in the controller in CodeIgniter. In Rails, we place the rules inside the Model.
When Rails attempts to insert data to the database (eg. with @shout.save), the Model will automatically check the data against our rules before attempting to save it. Thus, if validation fails, then @shout.save will return 'false', the view will be re-loaded and the error messages will display.

Inside the Shout model, insert the following before we define self.all_shouts:

validates_presence_of :message
validates_length_of   :name,  :within => 1..255
validates_format_of   :email, :with   => /^[a-z0-9_.-]+@[a-z0-9-]+\.[a-z.]+$/i

In true Ruby style, the validation rules read pretty much like natural English. On line 1 we ensure a 'message' exists.

Next we check the length of the 'name' field – it must be between 1 and 255 characters in length. 1..255 defines a range. We could define a range of between 10 and 15 with 10..15, for example.

Finally, we check the submitted email is in the format of an email address using a regular expression. Basically, the submitted email must contain five parts:

  1. A string containing a combination of letters, numbers, an underscore, full-stop or hyphen;
  2. The @ symbol;
  3. A string containing a combination of letters, numbers or a hyphen;
  4. A dot;
  5. A string containing a combination of letters or a hyphen.

And finally...

We now just need to display the error/success messages in the view (see above). Add the following at the top of the index view file:

<%= error_messages_for :shout, :header_message => nil, :message => nil %>
<%= '<p class="success">' + flash[:notice] + '</p>' if flash[:notice] %>

On the first line we render any error messages for the shout form. We set :header_message and :message to nil (Ruby uses 'nil', PHP uses 'null') to stop the function displaying the normal messages it displays (we just want the error messages).

The second line displays our flash'ed success message. Note the if flash[:notice] at the end of the line. This ensures everything before it will attempt to display if the flash notice exists.
Obviously, we could also have done the following instead; however it would have had the same effect:

<% if flash[:notice] %>
  <%= '<p class="success">' + flash[:notice] + '</p>' %>
<% end %>

That's It!

Go try it out, it should function exactly as the CodeIgniter version yet in quite a bit less code (which is also much easier to read!)

CodeIgniter:
Controller: 31 lines
Model: 33 lines
View: 69 lines
Total: 133 lines

Ruby on Rails:
Controller: 19 lines
Model: 11 lines
View: 64 lines
Total: 94 lines

While this tutorial may have made the process of developing a Rails application long-winded, take a look back through your code and see how simple it all is!

If this tutorial has sparked your interest in Ruby and Ruby on Rails, I strongly recommend picking up a copy of Rails for PHP Developers, Agile Web Development with Rails and/or Programming Ruby from the Pragmatic Programmers.

Now go! Go explore the world of Rails!


Related Posts

Check out some more great tutorials and articles that you might like

Enjoy this Post?

Your vote will help us grow this site and provide even more awesomeness

Plus Members

Source Files, Bonus Tutorials and
More for $9 a month for all TUTS+
sites in one subscription.

Join Now

User Comments

( ADD YOURS )
  1. PG

    mary July 30th

    Looks interesting, will have to give this a thorough read. Although I much prefer the Zend framework, the crossover aspect to ROR is interesting.

    thanks!

    ( Reply )
  2. PG

    techietim July 30th

    Just wondering why you did this:

    return $data;
    $q->free_result();
    }

    ( Reply )
    1. PG

      Jeffrey Way July 30th

      It’s considered a best practice.

      ( Reply )
      1. PG

        Roberto July 30th

        Is it best practice to put some code that must be evaluated after a return statement?

        I didn’t know.

      2. PG

        techietim July 30th

        No, but remember that function execution stops at the return statement, thus rendering that method call useless.

    2. PG

      eques July 30th

      if i remember correctly it is to clean your memory, normally it’s done for you, but just to be sure, you can clear it yourself

      ( Reply )
    3. PG

      Dan Harper July 30th

      Oops. Yeah, that was meant to be:

      $q->free_result();
      return $data;

      On an application this small, it’s not required at all, but it’s good to get in the habit of doing it for when you create a larger application.

      ( Reply )
  3. PG

    Joe July 30th

    Nice side by side tute, this is exactly how I stepped into rails, CI for a year to get my feet wet with mvc then over to rails to simplify and accelerate development. Good job.

    ( Reply )
  4. PG

    jakot July 30th

    Hi there!,
    Nice tutorial. What about a performance comparison?, could be very interesting.
    Regards,

    ( Reply )
  5. PG

    Warn3r July 30th

    I have experimented with both Rails and Codeigniter. Rails is great but took a while for me to get used to Ruby.

    ( Reply )
  6. PG

    Kyle July 30th

    Great stuff! That helped clear some of the things I was wondering about in Rails.

    Thanks for the great tut!!!

    ( Reply )
    1. PG

      David July 30th

      Would be cool to see a performance comparison yes.

      ( Reply )
      1. PG

        Brian Temecula July 31st

        Yes, I would like to see a performance comparison too.

  7. PG

    ijlal July 30th

    Very nice tutorial. Rails is good.

    ( Reply )
  8. PG

    Duane Gran July 30th

    It would be really interesting to have people versed in various frameworks to work up this example on other systems. I would volunteer to reproduce this in symfony. Any takers for other frameworks?

    (site admins — email me if you are interested)

    ( Reply )
  9. PG

    Odd creations July 30th

    Really awesome tutorial! I didn’t know that Ruby can be so beautiful. I will really consider learning ruby.

    ( Reply )
  10. PG

    Dan Harper July 30th

    @ Jeffrey:

    Would it be possible for you to add styling to Nettuts' CSS file? Just to change the font to a monospaced one. It'll make tutorials where lots of code is referenced in the text to stand out more. :)

    ( Reply )
    1. PG

      Dan Harper July 30th

      That was: add styling to Nettuts' CSS file.

      ( Reply )
      1. PG

        Dan Harper July 30th

        lol. CODE styling (as in the HTML tag).

  11. PG

    Juan July 30th

    Great tutorial! I’m really falling in love with the CodeIgniter framework

    ( Reply )
  12. PG

    Rails Guru July 30th

    Exactly why I love Rails, and left php in the dust.

    ( Reply )
  13. PG

    Crysfel July 30th

    I really like RoR, thanks for share :D

    ( Reply )
  14. PG

    Amr July 30th

    Viva Ruby on Rails! :)

    ( Reply )
  15. PG

    Don July 30th

    RoR may have less code, but man… just having to use command-line makes me avoid it. I even find CodeIgniter’s “code” vs. RoR “syntax” much nicer. But thanks for the article! When I stop being afraid of RoR, I’ll go back to this article.

    ( Reply )
    1. PG

      Dan Harper July 30th

      I used to he like you. The command line put me off. But now, I realise it’s one of Rails’ stongest points. Becoming more comfortable with the command line is probably one of the best decisions I’ve made programming-wise.

      And I have to completely disagree on the syntax point. The whole point of Ruby is to be readable by humans first, machines second, and I feel Ruby pulls this off flawlessly.

      But, each to his own :)

      (posted via iPhone)

      ( Reply )
    2. PG

      Johan July 30th

      You actually don’t need to use the command line.
      Except for starting/stopping the local dev server… :)

      I might be wrong…

      ( Reply )
      1. PG

        Dan Harper July 30th

        Yeah, you don’t _need_ it, and you could create the files manually like in CI, however I doubt there are any Rails devs who would even consider it :P

    3. PG

      Duane Gran July 31st

      Don’t be discouraged by the command line. RoR, like the php symfony framework, utilize the command line to save you a lot of time writing code by hand. The commands generate base code so that you spend your time implementing functions.

      Your first project in RoR or symfony will take longer (due to getting acclimated) but the pay off comes typically about 30-60 days later. You won’t regret the investment.

      ( Reply )
  16. PG

    Mirek July 30th

    Thank you, Dan. Nice to see CI and RoR side by side like this.

    ( Reply )
  17. PG

    Dave July 30th

    Nice TUT. Great to see the CI to ROR conversion

    ( Reply )
  18. PG

    David July 30th

    Don’t know if this has been posted on another codeigniter tutorial yet, but ci has a built in command for returning the results of the query as an array.

    Instead of:
    if ($q->num_rows() > 0) {
    foreach ($q->result() as $row) {
    $data[] = $row;
    }
    }
    return $data;

    You can use:
    return $q->result_array();

    See result_array() @ http://codeigniter.com/user_guide/database/results.html.

    ( Reply )
    1. PG

      Soninke August 1st

      result() – this function returns the query result as an array of objects, or an empty array on failure.

      Instead of:
      if ($q->num_rows() > 0) {
      foreach ($q->result() as $row) {
      $data[] = $row;
      }
      }
      return $data;

      You can use:
      return $q->result(); // $q->result() == $data

      ( Reply )
  19. PG

    Yigit Ozdamar July 30th

    Nice tut! Keep your article style mate!

    ( Reply )
  20. PG

    Corey Worrell July 30th

    Wow, I’ve heard a lot of good things about Ruby, but never wanted to look into it because it was so different to me than PHP. But this is really a great resource for comparing the two.
    I like how you explained every little part of it, and compared/contrasted between the two languages. Really helped me understand it all.
    Who knows, I might give Ruby a chance now.

    ( Reply )
  21. PG

    Shane July 31st

    A great tutorial for people who are evaluating RoR and CodeIgniter. Whilst I still think that RoR is riding the ‘cool’ wave, CodeIgniter’s a fantastic framework (highlighted wonderfully well by Jeff’s great vid tutorials).

    If you know PHP, what compelling reasons are there for learning and adopting RoR?

    ( Reply )
  22. PG

    James July 31st

    Fantastic tut! I’m really interested in this so decided to read through the entire tutorial and I’ve got to say that you did a fine job here Dan! In essence RoR and CI are quite similar because both follow the MVC pattern. I think the only barrier standing between me and adopting RoR is the syntax and how it differs massively from what I’m used to. Can you recommend a tutorial for learning JUST the Ruby language? – It’s probably best if I start there before hopping on Rails. Thanks! :)

    ( Reply )
    1. PG

      Dave Kennedy July 31st

      As the author recommended “Rails for PHP Developers” is a fantastic resource.

      ( Reply )
      1. PG

        Dave Kennedy July 31st

        Oh and I meant to add, from the rails guide there is a link to http://poignantguide.net/ruby/, talking foxes may seem irrelevant but its a fine read!

    2. PG

      Dan Harper July 31st

      Thanks :)

      Check out the ‘Get Started’ section in the sidebar at http://ruby-lang.org/ for some great exercises for learning Ruby – all interactive :)

      Also, as I mentioned at the bottom of the tutorial, I’d recommend picking up a copy of ‘Learning Ruby 1.9′ and ‘Rails for PHP Developers’ if you have the time. Both are fantastic books.

      The ‘Rails for PHP Developers’ book has an accompanying website (http://railsforphp.com/) which compares most of PHP functions with their Ruby/Rails counterparts.

      ( Reply )
      1. PG

        Dan Harper July 31st

        Edit: Almost forgot the best sites :)

        http://peepcode.com/ – A load of Ruby/Rails-based screencasts available for $9. I’d recommend the ‘Rails 2 from Scratch’ series, then ‘REST for Rails 2′.

        http://railscasts.com/ – Probably the best, free, screencast resource for picking up Rails tips.

        http://railsforum.com/ – Check out the Tutorials section.

        I’ve spent the past few weeks learning Ruby and Rails so I’ve gone through quite a few of these :)

  23. PG

    Drazen Mokic July 31st

    Well done, i like it, except one thing.

    You load view`s inside of view files and thats by far not a clear way. A good practice would be to load all views over the controller.

    Beside that, thanks for this tut!

    ( Reply )
    1. PG

      Dan Harper July 31st

      That’s so obvious I’m confused as to why I never considered trying that. Thanks :)

      ( Reply )
  24. PG

    Jonas De Smet July 31st

    Don’t feel like this is the best compare-situation. Codeigniter isn’t the “smallest” way to code PHP with a Framework. If you use KohanaPHP (based on CI) you will see you use less code then CI and also have some of ORM like in RoR. The difference would be less…

    If I have some more time, I would look into RoR…

    ( Reply )
    1. PG

      Dan Harper July 31st

      I went with CodeIgniter mainly because that’s the PHP framework I’m most comfortable with, and also because it’s the framework Nettuts is focusing on right now, so it will probably appeal to our audience more :)

      ( Reply )
  25. PG

    Chalda July 31st

    In codeigniter
    [code]
    function all_shouts() {
    return $this->db->order_by('id', 'DESC')->get('shouts', 10)->result();

    }
    [/code]

    It’s better :)

    ( Reply )
    1. PG

      Dan Harper July 31st

      Ah, thanks for that. I didn’t know you could return the result that way :)

      ( Reply )
  26. PG

    DemoGeek July 31st

    You are missing the end on the /shouts/index.php code.

    ( Reply )
    1. PG

      DemoGeek July 31st

      Hmmm….that comment was supposed to be [quote]You are missing the ending UL on the /shouts/index.php code. Wouldn’t validate[/quote]

      ( Reply )
      1. PG

        Dan Harper July 31st

        Oh yeah, thanks :) It’s there in the Rails version, though.

  27. PG

    DemoGeek July 31st

    Great Tut Dan! Got drifted away from RoR to CI for its deployment overhead, authentication complication and the plugins usage.

    This tut has triggered my interest back to RoR. I hope I stick with it this time at least. My major turn-offs with RoR were its deployment model (looks complicated at least), missing reliable and simple authentication mechanism and the usage of plugins. If you can shed some light on these topics it would certainly make it a point to stick with it.

    I’m sure there are many people who has this same concern that’s making them move away from RoR. You seem to explain things pretty well. If you can write something about these topics that would be great. Or at least if you can point us in the right direction to get a better understanding of these topics that would help as well.

    Thanks for your time.

    ( Reply )
    1. PG

      Dan Harper July 31st

      By “missing reliable and simple authentication mechanism”, I assume you mean Rails provides no out-of-the-box way of logging in/registering etc.? If so, that’s correct, but for good reason.

      The core Rails development team try to keep the framework to it’s minimum by only including features which will benefit the community as a whole.
      For this reason, Rails has a thriving plugin community to extend it’s functionality when needed.
      The two best authentication plugins at the moment are ‘Authlogic’ and ‘RESTful Authentication’, both of which are explained in details at http://railscasts.com/

      The reliance on plugins for seemingly-simply tasks such as authentication/pagination can be a turn-off, but it seems to improve competition and thus creates better plugins.
      An example of this, would be that older versions of Rails included a pagination system built-in, which wasn’t very good at all and created code bloat.
      This has been removed from Rails for ages, and a much better pagination system now exists as a plugin – will_paginate. And, of course, users are free to use something different if they chose to.

      Hope this helps :)

      ( Reply )
  28. PG

    Soner Gönül July 31st

    Thanks!

    Really good!

    twitter.com/sonergonul
    friendfeed.com/sonergonul

    ( Reply )
  29. PG

    tribui July 31st

    Not that performance is the only factor, like lines of code but for comparison sake, what is the requests per second for both CI and ROR?

    CodeIgniter:
    Controller: 31 lines
    Model: 33 lines
    View: 69 lines
    Total: 133 lines

    Ruby on Rails:
    Controller: 19 lines
    Model: 11 lines
    View: 64 lines
    Total: 94 lines

    133 CI/94 ROR Lines of Code

    ### Requests per second CI / ?? Requests per second ROR
    Same hardware.

    ( Reply )
  30. PG

    Christian Dalsvaag July 31st

    I was quite the opposite from you guys when I started Ruby on Rails – I hated PHP, and I was sick and tired of the terrible syntax. I had been writing PHP for maybe 6 years.

    When I discovered Ruby I was amazed by the beautiful simple syntax, and I wanted to learn it right away. What’s nice about learning Ruby is that you don’t _have to_ use it for the web. You can write scripts to do various tasks for you. Some people even built Wolfenstein 3D in Ruby – fully playable.

    Rails is a great framework, and I think that it isn’t riding on the cool wave anymore – at least not as much as in the beginning. Rails is simple, yet very powerful.It took me three tries to completely leave PHP behind, and take on Rails – and I have become a better programmer; now I think about stuff I never thought about before.

    The stuff mentioned on nettuts just isn’t good enough (Sorry, Dan) – I mean there are so much more brilliant stuff to Ruby on Rails that should be here as well – that being RSpec, Cucumber (testing frameworks for RoR) that are used for instance in BHD (Behavior Driven Development) and the included testing framework like Test::Unit.

    You also start thinking about more advanced deployment topics, which is good for you.

    Starting to get to know the command line is the best thing I have done, since it is really powerful and gives you full access to your computer.

    I really recommend that you try out Ruby on Rails – and see if it fits you.

    ( Reply )
  31. PG

    Christian Dalsvaag July 31st

    Sorry about saying that the stuff here isn’t good enough – I misphrased myself. BHD, RSpec, Cucumber and Test::Unit is beyond the scope of this tutorial/guide. I just ment that articles here was a bit thin on RoR. Once again, sorry about that.

    ( Reply )
    1. PG

      Dan Harper July 31st

      Yeah, trying Rails’ more advanced, but essential, techniques would have been way beyond the scope of the tutorial, and would likely have scared away any readers who I had just started to win over to Rails by throwing the ’scary’ bits at them :P

      I’d love to see more Ruby & Rails tutorials here at Nettuts, but right now it’s a largely PHP community; maybe that will change with time.

      Like you, I’ve mainly been won-over to Rails by Ruby. The syntax just blows everything else out the water! But I found the best way to learn Rails, was to directly compare it with what I already knew :)

      I’ve only been learning Rails for about a little over a month now, and still have a long way to go, but it’s damn fun! :)

      ( Reply )
      1. PG

        DemoGeek August 2nd

        Dan – I completely agree with you, if it’s not for Ruby I won’t be looking at much of Rails, BTW. This coming from a guy with >8 years on .NET platform, you can imagine how much I love Ruby. It’s been one of the best language I’ve ever came across.

        Would certainly love to see more of Rails related tuts here at Nettuts, particularly from you. Great work!

  32. PG

    Chandru July 31st

    Hey Dan,

    That was an awesome tutorial. I couldn’t resist porting it to a Java web application framework which I’ve been developing recently. Hope you wouldn’t mind the use of your CSS and images in that tutorial. If you feel I shouldn’t have included them in mine, I’d happy to remove them.

    My port to Java can be found at http://easyweb4j.blogspot.com/2009/07/porting-rails-shoutbox-to-easyweb4j.html

    ( Reply )
  33. PG

    Koen July 31st

    The comparison of the forms code is in favor of CI though. Very readable. The RoR version will make a designer resign.

    ( Reply )
  34. PG

    Andy August 2nd

    I’m just getting into Rails now, thanks for the tips!

    ( Reply )
  35. PG

    kohanafan August 3rd

    you should try kohana, with orm :) i bet it would beat even ror

    ( Reply )
  36. PG

    Jhnd August 10th

    Hi.

    At

    def create
    @shouts = Shout.all_shouts

    @shout = Shout.new(params[:shout])
    if @shout.save
    flash[:notice] = ‘Thanks for shouting!’
    redirect_to :action => ‘index’
    else
    render :action => ‘index’
    end
    end

    I think there is a mistake.

    When I hit localhost:3000/shouts/create, what really gets rendered is /shouts/index.

    The submission form comes up when I disable the line:
    render :action => ‘index’.

    The following part of code
    “..
    else
    render :action => ‘index’
    ..”

    always gets rendered when you do localhost:3000/shouts/create.

    “if @shout.save” becomes true when you hit submit the form

    Take a look at it. I did run the code multiple times and I ran into the same errors.

    def create
    @shouts = Shout.all_shouts

    ( Reply )
    1. PG

      Dan Harper August 18th

      In this tutorial, we have included the submission form at the bottom of the index, so how it is in the tutorial, is correct.

      In other Rails projects, you will have a ‘new’ method in the controller which will be for your submission form. ‘create’ will then process the submission.

      Hope that helps (and sorry for the late reply).

      ( Reply )
  37. PG

    evan August 14th

    wrong way! wrong way!!!

    ( Reply )
  38. PG

    Robert Rouse August 20th

    Using plugins like Jose Valim’s Inherited Resources shrinks the controller code to almost nonexistent in this case since you don’t need anything fancy. Of course, you only use something like that after you learn how the CRUD stuff is done in Rails.

    ( Reply )
  39. PG

    BrianDHall August 25th

    Something that made a big difference for me was finding out about ORM in CI: http://www.overzealous.com/dmz

    It’s the Overzealous extension to DataMapper. You have to do a little more in setting up the DB, but it allows you to easily move form validation into the Model itself – so if you have 10 different forms that all care about usernames, for example, none of them need to worry about validating the username – it’s done in the model.

    I was seriously considering something like Kohanna or Rails, but I’m finding that if you keep learning CI and it’s available extentions you just keep learning more cool stuff that makes your life much easier.

    For instance I found tutorials to make CI work well with an IDE like PHPed, ORM that’s very friendly (IgnitedRecord was a turn off to me), you can use dynamic Routes to improve search engine friendliness without bloating your code (so you can call your controller “nettuts” but have it show as “site.com/nettuts-codeigniter-ruby-tutorials/”, etc), setting up group/role based authentication is so easy without having to understand ‘join’ SQL syntax using ORM, SSL/XML/CURL/Encryption extentions each can easily save days of headaches and development time any time you come across a need for them, mix-and-match GET and POST (often spoken of as a limitation of CI, minor changes make it work)…etc, etc.

    I’m sure Ruby is the same way, but everyone should be aware that all these tools are like that – there is more to Ruby and Ruby on Rails and PHP and CodeIgniter than can be learned in a year – or two, or three. Pick what you enjoy working with and keep going, and the grass is often precisely as green on both sides of the fence.

    Oh, but thanks for this great tutorial – it was perfect in helping me understand the similarities and differences between these often misunderstood technologies.

    Great work!

    ( Reply )
  40. PG

    Ben October 3rd

    This article was perfect to me. Perfect. Thank you Dan, I wish I wrote that :)

    ( Reply )
  1. Arrow
    Gravatar

    Your Name
    October 3rd