Tuesday, May 5, 2009

Converting legacy Rails apps to Grails : The Views

Alrighty, we're getting close to the finish line here. So far, we've covered General Project Setup, Migrating the Domain, Migrating the Controllers, and now we'll talk about migrating the views. After that, if I have a little bit of life left in me, I'll briefly speak about replacing the existing plugins in the Rails app w/ equivalent Grails plugin, and that should be the end of this series.

Now, on to the content.

  1. General Setup

  2. Layouts

  3. Page-by-page migration

  4. Tags

  5. Conclusion






1. General Setup





As you're probably used to it by now, the Grails and the Rails app have very similar approaches to storing views and templates. As you can see on the screenshots, the Rails views are in the "Views" project node, whereas in the Grails project, they're located in the "View and Layouts" project node. Inside of this folder, the views are partitioned by controller, e.g. the views and templates for controller "FooController" in the Grails app, sit inside of the view/foo subfolder.



2. Layouts

Another interesting folder in both Rails and Grails is the Layouts folder (in Grails, Views and Layouts - layouts project folder). There, the projects store the differet project layouts. The general idea here is mostly the same: different parts of the app will have different layout needs. Migrating the layouts from Rails to Grails involves mostly tag-for-tag conversion of the rhtml to gsp. A couple of useful facts about that process.

1. As mentioned in the previous post, both frameworks have a reasonable set of defaults for the layout selection. I can't quite remember all the details about how Rails chooses its defaults, but the converted application mostly specified on a per-controller basis by specifying the element, e.g. :


class FooController
layout "internal"
end

This Rails snippet will use the views/layouts/internal.rhtml layout.

There is no direct equivalent for specifying the desired layout inside of a controller in Grails. Instead, a user can add a layout with the same name as the controller (e.g. layouts/foo.gsp for FooController). Although I don't recall this being used in the Rails app, Grails also provides the ability to specify a template to use for rendering a view by specifying a <meta name="layout" content="landing"></meta>. Rendering a view that specifies the layout in this way will use the view/layouts/internal.gsp layout.

2. Converting the Rails layouts
The Rails layouts that I worked with used the following statements in the <head> element:



<head>
<%= stylesheet_link_merged :base %>
<%= stylesheet_link_merged :print, 'media' => 'print' %>

<%= javascript_include_merged :base %>
<%= javascript_include_merged :application %>
</head>


I can't quite say I know what all of the above statements do. I inspected the output the actual HTML output and replaced it with the following in my Grails template:


<head>
<link rel="shortcut icon" href="${createLinkTo(dir:'images',file:'favicon.ico')}" type="image/x-icon" />
<link rel="stylesheet" href="${createLinkTo(dir:'stylesheets/active_scaffold',file:'stylesheet.css')}" media="screen" type="text/css" />
<link href="${createLinkTo(dir:'stylesheets',file:'styles.css')}" media="screen" rel="Stylesheet" type="text/css" />
<link href="${createLinkTo(dir:'stylesheets',file:'print.css')}" media="print" rel="Stylesheet" type="text/css" />
<link href="${createLinkTo(dir:'stylesheets/active_scaffold/default',file:'stylesheet.css')}" media="screen" rel="Stylesheet" type="text/css" />
<!--[if IE]>
<link href="${createLinkTo(dir:'stylesheets/active_scaffold/default',file:'stylesheet-ie.css')}" media="screen" rel="Stylesheet" type="text/css" />
<![endif]-->
<g:layoutHead />
<g:javascript library="application" />
<g:javascript library="prototype" />
<g:javascript library="scriptaculous" />
</head>


A couple of things to note here :
* Using the ${createLinkTo()} tag inside of the stylesheet links : it is very convenient and makes the generation of the links pretty foolproof.
* Using the <g:layoutHead /> statement : allows the inclusion of any elements from the <head> element of the "client" page (the page that is using the layout).

Inside of the body of the template, the Rails template use the <%= yield%> statement to include the body of the client page. The equivalent statement in the Grails layout is the <g:layoutBody>

Finally, Rails allows client views to contribute "additional" content into the final output. In other words, the template can define an "area" where the client template can contribute markup, in a way that the said markup shows up in the parts of the layout that are generally rendered by the template. For example, if the client pages need to contribute markup to the content of the "sidebar", then, the layout would use something like the following:

<% if !@content_for_sidebar.nil? %>
<div id="right_sidebar_content_main">
<%= yield(:sidebar) %>
</div>
<% end %>


The client template, contributes to the layout as follows:

<% content_for :sidebar do%>
<div> This content will show up under the 'right_sidear_content_main' section in the final output </div>
<% end %>


In Grails, in order to implement the same feature, we have to resort to a less used and somewhat obscure feature of the underlying templating system that Grails users : Sitemesh. Here's the equivalent in the Grails project:

<g:if test="${pageProperty(name:'page.sidebar')!=null && pageProperty(name:'page.sidebar')!=''}">
<div id="right_sidebar_content_main">
<g:pageProperty name="page.sidebar" />
</div>
</g:if>


Now, in the "client" page, add content to the sidebar as follows:

<content tag="sidebar">
<g:render template="course_notes_side_bar" />
</content>

There is some additional info on using this feature in the Grails docs' for pageProperty tag, but more so in the Sitemesh user docs and just random blogs


3. Page-by-page migration
There is in most cases a 1-1 relationship between the views in both frameworks. Converting from the Rails views to the Grails views was mostly mechanical : see the Rails tag, find the equivalent Grails tag, and then figure out how to map the Rails tag attributes to the Grails tag attributes. In most cases, the two are similar enough and the conversion is fairly easy. At other times, Rails did provide some more features not present in Grails and migrating the pages did require some level of thought and effort. Even in that case, even for the tags that don't have a Grails equivalent, after the first couple of tags the conversion is always the same. Here's an example of an easy conversion:

In Rails:

<%= link_to_remote "Upload File",
{:url => { :controller => 'activity_items', :action => 'show_upload_form', :activity_id => @act_id},
:before => "Element.show('show_upload_form')" ,
:success => "Element.hide('show_upload_form');",
:update => { :success => "upload_form", :failure => "upload_form_errors" }},
{:class => "action", :title =>"Add a new file."} %>


In Grails:

<g:remoteLink controller='activityItems' action='show_upload_form'
params="[act_id:act_id]"
before="Element.show('show_upload_form')"
onSuccess="Element.hide('show_upload_form')"
update="[success:'upload_form', failure:'upload_form_errors']"
class="action" title="Add a new file"> Upload File </g:remoteLink>



Here's another one.

Rails:

<%= form_remote_tag :url => {:action => 'update',:controller => "activities", :id => @activity.id},
:before => "Element.show('form-indicator-activities')" ,
:success => "Element.hide('form-indicator-activities')" %>


Grails:

<g:formRemote url="[action:'update', controller:'activities', id:activity.id]" name="editActivityForm"
before="Element.show('form-indicator-activities')"
onSuccess="Element.hide('form-indicator-activities')">



I found that because the Rails app assumed to be deployed at the root of the context (e.g. http://localhost:3000/), whereas the Grails app always assumes that it will be deployed to a non-root context path (e.g. /foo), in many cases I found myself replacing static image references with links created with ${createLinkTo()}

FROM:
<img src="/images/indicator.gif" id='addurl' style='display:none;' />


TO:
<img src="${createLinkTo(dir:"images",file:"indicator.gif")}" id='addurl' style='display:none;' />




Here's an example of a Rails tag that didn't have a direct equivalent in Grails.

Rails:

<%= text_field_with_auto_complete(:course, :title, {:class=>"SearchTextBox", :value => " type here, then hit enter", :onclick=>"this.value=(this.value == ' type here, then hit enter')?'':this.value;", :onblur => "this.value=(this.value == '')?' type here, then hit enter':this.value;"}, completion_options = {}) %>


In Grails, I created a template that contained the same html + javascript that the Rails tag produced:

<%-- The content below was migrated from the Rails app, could be improved if using a plugin providing a cleaner autocompletion setup --%>
Search

<style type="text/css">
div.auto_complete {
width: 350px;
background: #fff;
}
div.auto_complete ul {
border:1px solid #888;
margin:0;
padding:0;
width:100%;
list-style-type:none;
}
div.auto_complete ul li {
margin:0;
padding:3px;
}
div.auto_complete ul li.selected {
background-color: #ffb;
}
div.auto_complete ul strong.highlight {
color: #800;
margin:0;
padding:0;
}
</style>
<input autocomplete="off" class="SearchTextBox" id="course_title" name="course.title" onblur="this.value=(this.value == '')?' type here, then hit enter':this.value;" onclick="this.value=(this.value == ' type here, then hit enter')?'':this.value;" size="30" value=" type here, then hit enter" type="text">

<div style="position: absolute; left: 1275px; top: 161px; width: 230px; display: none;" class="auto_complete" id="course_title_auto_complete">
</div>


<script type="text/javascript">
//<![CDATA[
var course_title_auto_completer = new Ajax.Autocompleter('course_title', 'course_title_auto_complete', "${createLink(controller:'courses', action:'auto_complete_for_course_title')}", {})
//]]>
</script>


Finally, Rails definitely has some more advanced scaffolding features that Grails didn't support out of the box or did not have a direct equivalent. Similarly to the example above, I just used the html that the scaffold generated, stuffed that into a separate template and used that.

Rails:

<%= render :active_scaffold => "notes", :constraints => {:user_id => current_user.id, :course_id => @course.id} %>


In Grails, this became a standalone template (_notes_scaffold.gsp), which initially contained the static HTML generated by Rails, which I then rigged to support dynamically generate the needed markup (e.g. loop, etc). In the end, the call to the scaffold code above, becomes the following:

<g:render template="/notes/notes_scaffold" model="[notes:UserNote.findAllByUserAndCourse(user?:current_user(),course)]" />


4. Helpers and Tags
In a couple of instances, the Rails app depended on the "helpers" - seemingly a collection o methods that are available to be executed either from the view or contoller. I ended up encapsulating some of these common operations into taglibs, so that the usage of the said tags in grails is as follows:

Rails (a sample Helper located in the Helpers project node):




require 'bluecloth'
module ApplicationHelper
include TagsHelper
def link_to_button(label)
"<table cellpadding=0 cellspacing=0 class=button_action_link><tr><td align=right style=\"background: url('/images/left_button_curve.gif') no-repeat; width: 8px; height: 23px;\"></td><td nowarp=\"nowrap\" style=\"background: url('/images/center_button_bg.gif') repeat;\"> #{label} </td><td style=\"background: url('/images/right_button_curve.gif') no-repeat; width: 29px; height: 23px;\"></td></tr></table>"
end
end





In Grails, it becomes the following:
class CosTagLib {

def linkToButton = { attrs ->
out << "<table cellpadding=0 cellspacing=0 class=button_action_link><tr><td align=right style=\"background: url('${createLinkTo(dir:"images",file:"left_button_curve.gif")}') no-repeat; width: 8px; height: 23px;\"></td><td nowarp=\"nowrap\" style=\"background: url('${createLinkTo(dir:"images",file:"center_button_bg.gif")}') repeat;\"> ${attrs.label} </td><td style=\"background: url('${createLinkTo(dir:"images",file:"right_button_curve.gif")}') no-repeat; width: 29px; height: 23px;\"></td></tr></table>"
}
}


An alternative to adding common helper functionality in tag libraries is to add the same methods as public methods on the superclass. For example, the parent controller contains the following closure:

def current_user = {
if (this.currentUser==null && session.user) {
this.currentUser = User.get(session.user.id)
}
println "Returning currentUser ${currentUser} "
return currentUser;
}


Then, in GSPs, one can use the closure as follows:




5. Conclusion

It's interesting that the migration of views/templates is probably the least complicated part of migrating a Rails app to Grails. Yet, at the same time, together w/ migrating the controllers it was possibly the most time consuming task. Understandably, these artifacts represent ARE the web application. While there probably isn't a good way to automatically convert the controller code, the view code is much more amenable to such an automated conversion, tag-for-tag.

For a lot of these repetitive tasks of converting the app UI tag by tag ( I didn't spend the time to create an auto-converter), I ended up creating a couple of NetBeans live templates that give me parameter and code completion of attributes, jumping between different params, etc.

1 comment:

  1. I have a problem in grails 1.1 content tags does not work for me.. but whenever I try to place the layoutBody tags it works. Custom content tags don't work. I can't post tags here.. any solutions regarding the content tags??

    ReplyDelete

Popular Posts

Related Posts with Thumbnails