|
|
RUBY ON RAILS introduction | rails webapp basics | rails webapp features | practicalities
Last updated: March 2008
The language Ruby offers an abbreviated syntax for development and has
become increasingly popular since a web framework called Rails was
built upon it.
|
|
|
introduction For the Ruby on Rails version of WebOfContacts these technologies were used:
> rails -d mysql webofcontactsThis creates a whole directory structure: an app directory with models, views, and controllers, a config directory with "routes" for the application, database scripts, test classes, and much more. The -d mysql switch indicates that a MySQL database should be
used instead of the default SQLLite.
Continuing from the command-line:
> rake db:create:allThis will call CREATE DATABASE for all the databases defined
in the database.yml file.
It is now necessary to give Ruby on Rails the database password by
editing the generated database.yml file. After that:
> ruby script/generate scaffold Contact first_name:string last_name:string priority:integer phone_nums:string email:string web_page:string photo_uri:string > rake db:migrateThis sets up the database with a single entity Contact as
defined above. Notice how brief it is: you don't have to go into the
database and create tables manually as with other frameworks. Ruby on
Rails tries to be a total solution.
The server is then started:
> ruby script/server webrickand there is already a basic CRUD application available at: http://localhost:3000/contacts. If you open the project in Eclipse (having first installed the Ruby plugin) you can see the directories that have been created. The screenshot below includes some files that were created during development.
|
|
|
rails webapp basics The pages for the view are under the views directory and are based on HTML as usual. To create a link from one page to another it is possible to use a raw HTML link, but these can be a bit fragile (on converting to a WAR the context root changes for instance). So it's better practice to use Ruby's equivalent of tags like link_to
and image_link. For instance:
<%= link_to 'CREATE CONTACT', :controller => 'contacts', :action => 'new' %>This code generates the link http://localhost:8080/webofcontacts/contacts/new which winds up calling the function new in a file called
contacts_controller.rb.
It is also easy to bind an entity (such as a Contact) to a form using Rails tags.
In the view (*.html.erb files), code to generate a text box for the first
name of a contact looks like this:
<%= text_field :contact, :first_name %>This assumes that the contact object is in scope.
The code in edit.html.erb to generate the
HTML tags looks like this:
<% form_tag :action => 'change', :id => @contact.id.to_s do -%> ... <% end %>This specifies that the URL called on submit of the form will look like http://localhost:3000/contacts/change/:id which is defined in the routes.rb file to call the update method of the contacts
controller.
Both edit.html.erb (updating the contact) and new.html.erb (creating the
contact) can use the same basic
form, so within the form_tag block they both include form.html.erb
(inserted by render). For example, in the update page:
<%= render :partial => 'form', :locals => { :f => @contact} %>
<p><%= submit_tag 'Update' %></p>
Notice that the contact object is put in scope for form.html.erb to use (see
the text_field code above).
Page composition is managed using layouts.
Each entity has its own controller, and each controller has its own layout.
Here is a snippet from app/views/layouts/contacts.html.erb:
<td id="leftnav" width="200">
<%= render "shared/leftnav" %>
</td>
<td id="body" valign="top">
<%= yield %>
</td>
Notice that the leftnav.html.erb is included so that navigation will
be on pages that use this layout. (The header and footer are included
in code not shown here.) Then the <%= yield %> part
indicates where the main body of the page should go for each
individual page.
This defines the layout for all the Contact CRUD pages... what about the
rest of the pages? How can they all use the same layout? The answer
it simply to go into each of the other controllers (e.g. comments_controller.rb)
and at the top write:
layout 'contacts'Regarding application tiers, Ruby on Rails uses a strict MVC architecture. Above we've mostly dealt with the View. A sample Model class looks like this: class Contact < ActiveRecord::Base has_many :comments, :order => "created_at desc" ... endThe superclass ActiveRecord is an important class which
is used for all the objects in our model. It tells Ruby on Rails to
bind this class to a table in the database and manage the ORM. The
has_many :comments line defines the relationship
between Contact and Comment. The Comment class mirrors this
relationship with a belongs_to :contact line.
Next: the Controller class. This is the biggest chunk of code. It
contains a method for each possible action on the entity. In
contacts_controller.rb, the default action is to list all the
contacts in the database:
def index
@contacts = Contact.find(:all)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @contacts }
end
end
The respond_to block allows the controller to return
either HTML or XML output depending on what was requested. In either
case the contacts variable will be returned to the View
to loop through.
The method that is called in the controller depends on how URLs are
mapped in config/routes.rb. Here is a sample line:
map.connect 'contacts/change/:id', :controller => 'contacts', :action => 'update'This means that when a URL with "contacts/change/" and an ID in it is requested, the update method in the contacts controller will be called on the contact so identified. |
|
|
rails webapp features
Form validation rules are defined not in the View, but
in the Model. For example from contacts.rb:
|
|
|
practicalities Ruby on Rails automatically generates classes for testing, which as usual you need to modify and build on as you see fit. These test classes are fairly comprehensive, including unit tests for the model, functional tests for the controller, integration tests to run across different classes and fixtures to set up the data for the tests. Here is an example of a unit test I created to test CRUD operations for the Contact
model:
class ContactTest < ActiveSupport::TestCase
def test_crud
james = Contact.new(
:first_name => "James",
:last_name => "Mccabe",
:priority => 5,
:phone_nums => "303233443",
:email => "jamesc@oranda.com",
:web_page => "http://www.oranda.com",
:photo_uri => "JamesMcCabe.jpg")
assert james.save
contact = Contact.find(james.id)
assert_equal(james.first_name, contact.first_name)
contact.last_name = "McCabe"
assert contact.save
assert contact.destroy
end
end
That is fairly self-explanatory.
Next is a functional test
class for the contact controller. All it does is test
that the front page can be loaded after log in. Normally
of course, there would a lot more tests.
class ContactsControllerTest < ActionController::TestCase
def test_should_get_index
login_admin
get :index
assert_response :success
assert_not_nil assigns(:contacts)
end
def login_admin
controller_bak = @controller
@controller = LoginController.new
post :login, :username => "admin", :password => "admin"
@controller = controller_bak
end
end
The login_admin method uses a little trick.
Because the post method defaults to using the current
controller, the contact controller, it is necessary to make the
login controller the default temporarily in order to log in.
Rails comes with its own server -- WEBrick -- and you can use this to test
development, test, and production versions of your application. However,
it is now also possible to WAR up the webapp and run it on a Java container
like Tomcat. JRuby is a Ruby interpreter written in Java and the Goldspike
plugin has been built on JRuby to transform Ruby code into Java. You generally
need to install the ActiveRecord-JDBC gem and then use Goldspike to create the
WAR like this:
rake war:standalone:createA full set of instructions for using Goldspike is here. The process is still a bit involved, so a gem called Warbler exists to streamline the process. However my experience was that on both Linux and Windows, there are some problems with Warbler (e.g. your RAILS_ROOT gets changed relative to your resources, dislocating things). So Goldspike seems to be still a more stable solution. Ruby on Rails prides itself on its usability and speed of development. In a 2005 article, Curt Hibbs famously claimed that Ruby on Rails gives 10 times greater productivity than a typical Java framework. It was good to provoke debate, but I would say the real figure is more like 2 than 10: it took me about half as long to develop the sample application in Ruby as in Struts or JSF. It's still an impressive result. Most of the extra features like file upload and authentication were either available out-of-the-box or fairly easy to track down. One use for RoR could be as a rapid prototyping tool, helping to flesh out a specification. It helps of course that Ruby is interpreted meaning that you can just make your changes in the code and see the effects immediately. One complaint is that when Rails 2.0 came out there were a lot of changes, so that broke a lot of the tutorials and casual code that developers google for. Of course there have been a lot of improvements to Rails, including the release of a faster server (Mongrel as opposed to the old WEBrick). Furthermore Ruby on Rails is not a flawless piece of software. For example, as of writing sometimes if you change the database account details in database.yml, you will inexplicably get the error: Access denied for user: ‘root@localhost’ (Using password: NO)This problem is very frequently reported, and there appears to be no single solution. It may be that there is a problem with the Rails' adapter for MySQL. I have noticed that if you leave the username field blank in database.yml, it defaults to 'root' rather than 'anonymous' as it should. This could be the cause of some of the confusion out there. |