Tuesday, 23 March 2010

Rails - Day 9 - Rendering Content

Rails  - Day 1 - The RubyGem Love Story
Rails -  Day 10 - Testing Rails Controllers
Rails -  Day 13 -ActiveRecord Relationships Part II
Rails - Day 15 - Rails Forms Part II
In the last post, we had a fairly in depth look at HAML, SASS, Compass and by association, the blueprint CSS framework.

In this post, I want to render some content that is dynamically rendered from data in our database.  I know a lot of us do not like to admit that we work on applications that can be classified as CRUD applications but I fear a lot of us do.  I very much include myself in this less than elite group.

In a previous post, we generated a User resource using the ruby script/generate resource User command.

In another post, I went on to explain how Rails uses REST to define several RESTful actions for each controller. The index action is generally used to list all or a subset of the resource in question, in our case Users. 

We are going to change the index action of our UsersController to the following which will, as I hope is obviously return a list of users.:

We will touch on the Rails ORM ActiveRecord later but I hope it is fairly obvious that we are returning all the Users in the users table and ordering the result set by the user_name field.  One exciting thing of note is that we have not had to define an intermediary mapping file as we would with Nhibernate or the entity framework.  

We have also defined an instance variable named @users that holds an array of User objects corresponding to all the User objects result set that is returned from the find method.  Any instance variables we declare in the controller are available in the view which as previously stated, is by convention the view file with the same name as the controller action located in the subdirectory named after the resource.  In the case of the Users resource index action, we have an index file in app/views/users/index.html.haml.

We are now going to refactor the index.html.haml file we updated at length in our last post to display the users objects that are be held in the @users instance variable, that is if there are any records:

Again, I must stress that HAML is not everyone's cup of tea and the above might be abhorrent to you.  I personally like its terseness.  You should notice from the above that we are checking the length of the @users member variable we declared in our index action.  If we have any elements in the array, we iterate over the @users array using ruby's block syntax.  One thing of note in both the if statement and the @users iteration code is that there is no end keyword to mark the end of the code block.  This is not required in HAML as significant whitespace is used to mark code blocks and regular ruby syntax would break these rules.

Rails Layouts (Master Pages in .NET)

In the last post, we split the content of our index.html.haml page into a header, sidebar, footer and content sections:

Obviously we do not want to cut and paste the header, sidebar and footer into each new view we create. 

.NET has had master pages as of .NET 2.0 and in Rails, we have layouts.  We are going to separate the common elements from our index.html.haml view and place them into a layout.

To do this, we create a new file named application.html.haml and place it in the designated place for layouts.  That is right kids, layouts have, as is now hopefully predictable, their own convention.  

The designated place is app/views/layouts folder:

Our choice of layout file name has a reason behind it which we will get to later.

We then cut and paste the code from index.html.haml into application.html.haml.  We will remove the code that iterates over the @users member variable and place it back into index.html.haml.  We then replace this code in application.html.haml with the following:

We have replaced the code at the beginning of the post with the = yield syntax.  Yield in this context will cause the entire contents of a view to be sucked into the layout before the page is rendered.

So how do we apply this layout to our view.  As you might imagine, there are a number of ways of doing this in rails and not all of them requires writing any code:

  • A controller specific layout will automatically be used if it exists.  Each controller can contribute a file into the app/views/layout directory if it follows the specific naming convention of ${controller}.html.haml. In our example, if we were to create a file in app/views/layouts/users.html.haml, then the layout will be applied automatically.

  • A site-wide default layout will be used if a controller-specific layout does not exist.  The site wide layout-file must be named application.html.haml as we have done in this example.  If the site wide layout file and the controller specific layout file both exist, the controller layout takes priority. This is the choice we have gone for in our example.  All views will have this layout supplied but we can override it with controller specific layouts if we require.

  • You can specify the layout file in your controller or action code.  

Rails Partials (User Controls)

Partials are reusable bits of template code that can be included in a view.  They are, as the heading suggests, analogous to User Controls in .NET and allow us to encapsulate and reuse UI behaviour in one place.  

In order to illustrate this practice, we are going place the code that renders the HTML table below and is currently in index.html.haml and create a partial from it:

Our first step is to create a file named _users.html.haml and place it in the app/views/users folder:

All partials in rails must start with the underscore.  We then cut and paste the table rendering code in the newly created partial.

We then update index.html.haml file to render the partial:

And that is it, easy peasy.  I will not take the discussion on layouts and partials any further that this.  Nested layouts are possible just as nested master pages are in .NET.  Partials have many nuances as well that you can find out about if you are interested to know more.

Rendering Xml or JSON

On a final note, I want to mention how we can just as easily render JSON or XML.  If we want to return JSON for example, we could change the index method to first of all retrieve all the users from the database and then return the result as JSON:

This is achieved in one line of code which is pretty damn cool in my book.  If we now call the index action through the browser with the url http://localhost:3000/users, we get the following result:

An xml output is similarly achieved by replacing json with xml in the method above.  It almost feels like we are cheating here.  We are writing very little code.

I will leave things here for now with respect to rendering content.

On my next post, I want to touch on the Testing

No comments:

Post a Comment