Rails: Finding Who Called A Partial

So you’re coding a partial and you want to know which template has pulled it in. The magic word is “@first_render”, which will give you something like “homepage/admin” for the homepage/admin.rhtml template. Trivial, I know, but I couldn’t find any mention of it after much googling, so I decided to output self.inspect and was pleased to find that property. I have no idea how reliable it is, if it will survive to Rails 1.2 and beyond, etc, etc, caveat, disclaimer, etc. I do think developers could benefit if Rails explicitly supported the property … just like knowing who called a function, there are always good reasons to do it, and the Ruby philosophy is to give developers the power to choose what’s good for them.

I’m using the property in a tabbed navigation partial, to determine which is the current page. A classic example of where the temlpate property is useful, since the property is used in a generic way, not as part of some complex “if” loop (viz “if template is leftColumn, make the background pink” etc.)

  1. css_classes = ["templateA", "templateB", "templateC"].map {
  2.   |template| @first_render==template ? "on" : "off"
  3. }

  1. <div class="nav">
  2.   <a class="<%= css_classes[0] %>" href="/controllerX/actionA">The A Thang</a>
  3.   <a class="<%= css_classes[1] %>" href="/controllerX/actionB">The B Thang</a>
  4.   <a class="<%= css_classes[2] %>" href="/controllerX/actionC">The C Thang</a>
  5. </div>

(It could be refactored to use some tag helpers and an associative array mapping actions to css classes.)

How Rails Handles Use of Reserved Keywords, Wow!

I didn’t ever expect to get excited about how a framework handles keywords, but Rails just impressed me big-time. When I tried to create a model named “Activity”, Rails told me it was reserved and then came back with a list of thesaurus terms that might be used instead!. That’s not just opinionated software, it’s downright friendly! Most frameworks don’t bother checking this sort of thing at all, let alone providing helpful “What To Do Now” instructions.

  1. > ruby script/generate model activity
  2.  
  3.   The name 'Activity' is reserved by Ruby on Rails.
  4.   Please choose an alternative and run this generator again.
  5.  
  6.   Suggestions:  
  7.  
  8. Sense 1
  9. activity -- (any specific activity; "they avoided all recreational activity")
  10.       => act, human action, human activity -- (something that people do or cause to happen)
  11.  
  12.  
  13. Sense 2
  14. action, activity, activeness -- (the state of being active; "his sphere of activity"; "he is out of action")
  15.        => state -- (the way something is with respect to its main attributes; "the current state of knowledge"; "his state of health"; "in a weak financial state")
  16.  
  17.  
  18. Sense 3
  19. bodily process, body process, bodily function, activity -- (an organic process that takes place in the body; "respiratory activity")
  20.        => organic process, biological process -- (a process occurring in living organisms)
  21.  
  22.  
  23. Sense 4
  24. activity -- ((chemistry) the capacity of a substance to take part in a chemical reaction; "catalytic activity")
  25.        => capability, capacity -- (the susceptibility of something to a particular treatment; "the capability of a metal to be fused")
  26.  
  27.  
  28. Sense 5
  29. natural process, natural action, action, activity -- (a process existing in or produced by nature (rather than by the intent of human beings); "the action of natural forces"; "volcanic activity")
  30.        => process -- (a sustained phenomenon or one marked by gradual changes through a series of states; "events now in process"; "the process of calcification begins later for boys than for girls")
  31.  
  32.  
  33. Sense 6
  34. activeness, activity -- (the trait of being active; moving or acting rapidly and energetically; "the level of activity declines with age")
  35.        => trait -- (a distinguishing feature of your personal nature)

It’s interesting…the standard HCI advice for showing an error is to not only explain what’s wrong, but how to fix it. “Tell the user what happened, explain the consequences if it’s not obvious, outline how to fix it, explain what to do if they can’t fix it.” Yet, how often do software tools, like compilers, give you anything more than a terse error message. Sure, it’s important to be concise in some cases, but you could always offer an option, like –verbose, to actually provide a few insights.

Rails, Selfishness, and Opinionated Patterns

When people talk about their favourite benefit of Rails over framework X in language Y, they’ll usually mention ActiveRecord, Ajax support, etc. But at a deeper level, the thing that really stands out is that Rails is opinonated software. This is where Rails derives its power and agility.

It’s pretty well-understood in software that consistency trumps piecemeal optimisation. Consistency comes from opionated software, piecemeal optimisation comes from design-by-committee. By piecemeal optimisation, I’m talking about something that’s full of good little chunks of local functionality, but falls apart when you use it because everything works differently.

Stated in another way, opinionated software is task-driven, whereas design-by-committee tends to be technology-driven. When DHH says the sky is now red, he’s selfishly refactoring things in such a way that he can work more effectively. I was about to slap quotes around “selfishly”, but the truth is, it really is a selfish act. And therein lies the benefit – it’s often better for me, as an end-developer, to work with a framework that one man (being DHH) has produced for himself than to work with one produced by a committee of agents with a variety of potentially conflicting interests. This, of course, assumes that DHH is talented and that he works on similar things as me.

Caveat: The above is a very crude approximation of the truth which ignores the contributions of many talented core developers, contributors, and feedbackers. And of course, I don’t mean selfish in a malicious sense, I mean it in an “enlightened selfishness” sense.

What’s particularly interesting about opinionated software is that it mirrors the subjectivity of patterns that has been discussed for years, by Richard Gabriel, Jim Coplien, and others (see Patterns of Software). While patterns are sometimes thought of as the endpoint of rational, wiki-ideal-like, trial-and-error, they are actually highly subjective. Indeed subjectivity is a key distinguishing factor between pattern languages and pattern collections.

To appreciate the importance of a pattern language, it is necessary to comprehend the subjective basis of pattern languages. Far from being the objective and exhaustive catalogue of ideas they may initially seem, patterns are based heavily on an underlying set of values. They explain how forces are identified and resolved according to certain principles; in doing so, they are encapsulating a particular approach. Alexander identified, valued, and discarded patterns in a process which embodied his own architectural philosophy (Kerth, 1997).

Roll on Rest On Ruby On Rails

It’s good to hear Rails 1.2 is all about embracing REST. Working with Ajax has helped me really appreciate the benefits of REST, and it all fits in with the Rails mindset quite nicely. Right now, we have URLs like

/user/update/5

whereas REST would have it as

/user/5

and then POST in the details – the nature of the method (“POST”) tells you it’s an update. Alternative we could send in a message with DELETE method if we wanted to delete user 5 instead. (In practice, there will be a slight hack to do this, involving a hidden field, because most browsers can only upload POST and GET).

By piggybacking on the commonly accepted conventions of REST, there will be even more “convention over configuration” embodied in Rails. And even greater division between RESTians and SOAPians. If nothing else, it’s a smart way to attract people who get REST and repel those who push SOAP ;-). (Software Engineering Radio recently quipped SOA is now just a pure acronym (just like KFC, AOL, Ajax) … it’s not simple, object-oriented, or ?accessible.)

Flexible Templating/Layouts With Rails

Rails’ layout mechanism follows the standard pattern embodied in Sun’s J2EE patterns and familiar to users of Struts and other frameworks. The page template outputs the main content, and it’s automatically wrapped by a generic template (as opposed to the earlier SSI-style paradigm, where each page manually includes headers, footers, etc.). There’s always a complication with this pattern, which is that the page author usually wants to influence some aspects of the wrapping as well.

For instance, you’re writing books.rhtml for an E-Commerce site. You’re obviously going to set the main content – a list of books – but you also want to influence the wrapping, such as: change the page title (in the doc head), highlight “books” in the side menu, etc.

With many frameworks, you have to jump in to an XML file to influence the wrapping, which often means no-one bothers, and usability – not to mention search engine optimisation – suffers as a result…all pages end up with the same title and meta-information, and you’re probably downloading extra Javascript because you’ve made it the same for all pages.

Rails makes it quite easy, and being Rails, there’s no funny config files to mess with…it’s all code. The layout template is parsed after the main content, so the main content can set any variables, and the layout template can make use of them.

For example, standard-layout.rhtml says:

  1. <html>
  2.  
  3. <head>
  4.     <title><%= @page_title || "My App" %></title>
  5. </head>

Then, each page gets to choose its own title, e.g. books.rhtml:

  1. <% @page_title="Book Bargains" %>
  2.  
  3. <!-- Main content for books -->
  4. <h1>Lots of Books Today!</h1>
  5. ....

It’s easy to make Javascript (and CSS) flexible. The main layout loops through each specified script:

  1. <% (@scripts||&#91;&#93;).each do | script | %>
  2.     <script type="text/javascript" src="/javascripts/<%= script %>.js"></script>
  3. <% end %>

(Some people might prefer Rails’ script_include tag instead of manual script tags.)

Then, the page can optionally indicate the Javascripts it needs

  1. <% @page_title="Book Bargains" %>
  2. <% @scripts = &#91;"dojo", "util", "books"&#93; %>
  3.  
  4. <!-- Main content -->

Bitten By Rails Pluralization/Inflection

If you’ve heard DHH or other Rails enthusiasts introducing Rails, they’ll often point to the neat inflection module, which maps back and forth between plurals and singular forms, among other things. Even if not the most killer thing about Rails, it’s an excellent introductory topic as it quickly conveys the humane nature of the Rails API as well as the principle of Convention Over Configuration.

It’s also the kind of thing where you think “These exception cases probably won’t happen to me…how likely am I to be coding Octopi, Sheep, or Oxen” (unless you work in the underwater farm sector). Actually, it turns out exceptions do happen more than I at first expected and are generally handled in a clean way. But how about exceptions that can’t be handled?

Where the inflector isn’t smart enough, I’ve seen the advice (e.g. in PragRails) to override ActiveRecord.set_table_name. e.g.

  1. class Sheep < ActiveRecord::Base
  2.    set_table_name "sheep"
  3. end
  4. [/ruby]
  5.  
  6. But today I had a problem. My class, "Fave", was leading to an error message about class "Fafe" not found, while loading the fixture. After some bemused grepping, I realised that it was due to this pluralisation issue. Seems that the fixture loader performs some singularization - here's an extract from fixtures.rb:
  7.  
  8. [ruby]
  9.     @class_name = class_name ||
  10.                   (ActiveRecord::Base.pluralize_table_names @table_name.singularize.camelize : @table_name.camelize)
  11. [/ruby]
  12.  
  13. **Overriding set_table_name doesn't solve this problem**. Which is surprising given the usual advice, so maybe I missed something!!! The solution that worked for me was to <a href="http://wiki.rubyonrails.com/rails/pages/Turn">configure the Inflection module</a>. I now have this in my config/environment.rb:
  14.  
  15. [ruby]
  16. Inflector.inflections do |inflection| inflection.irregular "fave", "faves"
  17. end

Now that Rails’ notion of grammar has been redefined, the set_table_name can be removed as it’s redundant.

Side note: Since people are often intrigued by the pluralization algorithm but probably not enough to hunt down the code, here it is – taken from active_support/inflections.rb (which confirms why “Faves” became “Fafe”):

  1. inflect.plural(/$/, 's')
  2.   inflect.plural(/s$/i, 's')
  3.   inflect.plural(/(ax|test)is$/i, '1es')
  4.   inflect.plural(/(octop|vir)us$/i, '1i')
  5.   inflect.plural(/(alias|status)$/i, '1es')
  6.   inflect.plural(/(bu)s$/i, '1ses')
  7.   inflect.plural(/(buffal|tomat)o$/i, '1oes')
  8.   inflect.plural(/([ti])um$/i, '1a')
  9.   inflect.plural(/sis$/i, 'ses')
  10.   inflect.plural(/(?:([^f])fe|([lr])f)$/i, '12ves')
  11.   inflect.plural(/(hive)$/i, '1s')
  12.   inflect.plural(/([^aeiouy]|qu)y$/i, '1ies')
  13.   inflect.plural(/([^aeiouy]|qu)ies$/i, '1y')
  14.   inflect.plural(/(x|ch|ss|sh)$/i, '1es')
  15.   inflect.plural(/(matr|vert|ind)ix|ex$/i, '1ices')
  16.   inflect.plural(/([m|l])ouse$/i, '1ice')
  17.   inflect.plural(/^(ox)$/i, '1en')
  18.   inflect.plural(/(quiz)$/i, '1zes')
  19.  
  20.   inflect.singular(/s$/i, '')
  21.   inflect.singular(/(n)ews$/i, '1ews')
  22.   inflect.singular(/([ti])a$/i, '1um')
  23.   inflect.singular(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, '12sis')
  24.   inflect.singular(/(^analy)ses$/i, '1sis')
  25.   inflect.singular(/([^f])ves$/i, '1fe')
  26.   inflect.singular(/(hive)s$/i, '1')
  27.   inflect.singular(/(tive)s$/i, '1')
  28.   inflect.singular(/([lr])ves$/i, '1f')
  29.   inflect.singular(/([^aeiouy]|qu)ies$/i, '1y')
  30.   inflect.singular(/(s)eries$/i, '1eries')
  31.   inflect.singular(/(m)ovies$/i, '1ovie')
  32.   inflect.singular(/(x|ch|ss|sh)es$/i, '1')
  33.   inflect.singular(/([m|l])ice$/i, '1ouse')
  34.   inflect.singular(/(bus)es$/i, '1')
  35.   inflect.singular(/(o)es$/i, '1')
  36.   inflect.singular(/(shoe)s$/i, '1')
  37.   inflect.singular(/(cris|ax|test)es$/i, '1is')
  38.   inflect.singular(/([octop|vir])i$/i, '1us')
  39.   inflect.singular(/(alias|status)es$/i, '1')
  40.   inflect.singular(/^(ox)en/i, '1')
  41.   inflect.singular(/(vert|ind)ices$/i, '1ex')
  42.   inflect.singular(/(matr)ices$/i, '1ix')
  43.   inflect.singular(/(quiz)zes$/i, '1')
  44.  
  45.   inflect.irregular('person', 'people')
  46.   inflect.irregular('man', 'men')
  47.   inflect.irregular('child', 'children')
  48.   inflect.irregular('sex', 'sexes')
  49.   inflect.irregular('move', 'moves')
  50.  
  51.   inflect.uncountable(%w(equipment information rice money species series fish sheep))

Must Have 5 Years “Ruby on Rails” Experience

If there’s one certainty in this industry, it’s the ongoing hilarity of job ads. David Heinemeier Hansson found a job ad that mentions “Rails” in the same breath as “enterprise” and “whitepaper”, and “struts” in the same breath as “latest and greatest”:

Should have ideally 5 years experience with all of the latest and greatest tools out there. The more the better. J2EE, struts, Ruby on Rails, Websphere, etc. Should have experience constructing Enterprise wide Java solutions from whitepapers.

“Why yes, I just pushed the Rational Rose Activationate button and Rose generated the Enterprise wide Java solution for me, along with the whitepaper. I didn’t even have to tell it anything first. Does that count?”

David’s reaction:

Rails has truly broken through the awareness barrier when its included as part of a job application that’s the antithesis of everything Rails is about. Clueless recruiters of the world, I salute you!

The “Must have 5 years Java” requirement, at a time when Java was barely out of the Sun Labs, have become legendary. There can be no finer way to attract dishonest candidates than to construct an infinitely high hurdle. I’ll donate a Pragmatic Rails text to whoever spots the first serious (as in seriously misguided) Rails equivalent.