Tuesday, December 26, 2006

Attacking Ruby on Rails

[Note added 12/28/2006: Caution - this posting was a holidays fogged, sugar-high induced feeble attempt at satire and humor that apparently rubbed many people the wrong way. Don't take it too seriously......]


The Swiss Army Knife


The Swiss Army Knife is a cool tool. It slices. It dices. It has scissors. A long knife blade. A corkscrew. A short knife blade. A file. A saw. Pliers and a screwdriver. If I'm stuck out in the wilderness with just one tool, I want one of these, or a Leatherman, or some similar tool. But I don't use a Swiss Army knife to gut a deer or a fish - it's too big and not something I want coated in blood and entrails. I don't use its screwdriver or pliers when I'm working on a car - it's too small and doesn't fit in tight spots. I don't use its saw to cut wood for the fireplace.

Using the right tool for the right job is a pretty simple concept. So why do so many people forget this when discussing programming languages and software frameworks? They're just tools, and can be selected in the same way. Are people really so stupid as to think that Java, or .NET, or PHP, or Python, or PHP, or LISP, or (insert your favorite language here) is the best tool for every job? I don't think so. So what's really going on when otherwise smart people go ballistic and become zealous warriors for their favorite technology when the topic of Ruby or Rails comes up? Here are some characers I've encountered that shed some light on the subject:

The One Trick Pony

"I've created dozens of kick-ass web sites in PHP. I can do anything in PHP that you can do in Rails. There's more lines of code in PHP than in Rails. There's more PHP job postings than for Rails. No way am I learning anything new, dude. It would cut into my partying and napping time..."

Propellerheads
"Ruby is just a degenerate form of LISP. Rails is just an over-hyped Lambda expression."


Python is demonstratably better than Ruby
We have infinite frameworks to choose from
Our superior intellects will make you weep
Take your upstart toy and be gone
The Wise Wizard

"Bow down to my Enterpriseyness! How dare you think you can apply your puny little framework to Serious Business Stuff! You foolish mortals know nothing of the dangers you will encounter. Inflexible DBA's! WSDL, BPEL and Enterprise Buses! Evil database API's! Message Queues! Enterprise Application Integration and Business Process Orchestration! Technology Stacks containing complexities which your feeble minds can't begin to comprehend! Now take your stupid little dog and go back home before someone gets hurt."

The Corporate Framework Guru:
Keep Your Filthy Problem Domains Away From Me


"We have a well-established seniority structure here. The new programmers work on customer-facing code: printed output, reports, user-interfaces. The intermediate programmers work on the business logic that the customers care about - they build classes and business rules that make the system work. The senior programmers work on the fun things that the customer doesn't care about - presentation layers, message processing, persistence engines, process orchestration rules engines. The smarter you are, the better you insulate yourself from boring customer problems. I don't want a fancy framework coming in here and messing up this my nice ivory tower..."

The Drone

"I got my Visual Basic certification just 10 years ago. 3 years ago the company made me go to a training class to learn VB.Net. Now I have a jerk of a new manager that wants me to use C#. My head hurts just thinking about it. No way am I going to experiment with Ruby or Rails. The company won't even let me install it on my machine at work, and I'm too busy to learn anything at home...."

The CIO/CTO

"Open source - isn't that something only commies use? Who provides your support contract? What does Gartner have to say about it? I can't possibly tell the CEO this is a good idea! Blah! Blah blah! BLAH!!! Now get out of my office - I have lots of magazine reading to catch up on and a golfing --er I mean a technology conference trip I have to schedule."

The Chicken


"I'll prototype it in Rails, then build the real app in X."

(This is how I got sucked into using Rails full time. Prototyping is a dangerous gateway drug...)

The Best Tool for the Job

If I was thrown out into the wilderness and forced to pick one toolset without knowing what kind of problem would come along I'd want the .NET Swiss Army Knife. If I was integrating with a rich corporate infrastructure rife riddled with legacy applications, pre-established database schemas, message queues, with some WS- sauce thrown on top, I might want the Java Leatherman. (OK, I'm lying. Actually I'd want to quit, run away, and start my own company with a fresh slate, but that's a different story....)

But if I'm working on a new web-based application, backed by an application database, Rails is the best tool for me, whether the job is large or small.

Thursday, December 21, 2006

Rails Web Service Gotcha: Use Separate Server for Client Testing

Today's painful lesson: If you are developing a web service, and also writing the "client" code that will utilize the web service, don't develop and test both on the same server instance.

In our case, we are exposing some controller code (call it the "server" controller) via a web service, and have a separate controller (call it the "client" controller) that will invoke the service. In the production world, these controllers will be running on separate web servers.

During development, it seemed perfectly reasonable to test both controllers on a single machine. The client code successfully invokes the server code via the web service, but also times out in a bizarre fashion. This occurs under Mongrel and Webrick, and occurs whether the call is made via SOAP, XML-RPC, or REST.

The solution: When on a development machine, start up two Mongrel or Webrick instances on separate ports (say 3000 and 3001). Point your browser at the client controller on port 3000, and be sure the client controller talks to the server controller on port 3001. This requires a bit of environment-specific code, but solves the problem.

Thursday, December 14, 2006

More Ruby / Rails trend indicators

After learning of the TIOBE Programming Community index mentioned in this post a few days ago, I've kept my eyes open for other trend indicators showing trends related to Ruby and Rails. Here are a few more:
  • Google trends, showing a trend history for the search term "Ruby on Rails".
  • A book sale graph comparing trends between various languages.
  • Indeed.com shows a nice graph of the Trend of Ruby on Rails jobs postings.
  • Monster.com currently shows 135 postings for Rails jobs, 335 postings for Ruby jobs. By way of comparison, Delphi shows 329, Python shows 906, and C#, Java, PHP, and Perl all show 1000 or more.
  • Dice.com (filterd for the U.S.A.) shows 181 postings for Rails jobs, 286 for Ruby. By way of comparison, Delphi shows 171, Python shows 853, C# shows 6,045, .Net shows 8,347, Java shows 15,122, and J2EE shows 7,674.
Today, my own observation of shelf space allocated to any given programming language at the local mega bookstore showed the following:
  • 6 shelves of books on .NET languages (C#, ASP.NET, VB).
  • 3 shelves of books on Java and J2EE.
  • 1 shelf of PHP books.
  • 1/2 shelf of Ruby/Rails books (a nice increase from the 2-3 books available earlier this year).
  • 1/2 shelf of Python books.
These are all crude indicators, but in combination provide a good view of the emerging Ruby/Rails development community. I'll revisit these numbers in the future to monitor the trend.

Tuesday, December 12, 2006

The Software Complexity Triangle

On recent projects, while drinking the koolaid of delivering simpler, more usable software, I have noticed a Software Complexity Triangle similar to the well known Project Management Triangle. The points on the Software Complexity Triangle are Simplicity, Power, and Design:


On this diagram, Simplicity represents the ease of using a system, both for end users as well as for operational support or back-office staff. Power represents the overall capability and depth of functionality of the system. Design represents the expertise of your software development team, particularly the depth and breadth of their technical skills as well as their knowledge of the problem domain in which they are working.

It is possible to fix in place or optimize for one or two points of the triangle, but the third point must compensate accordingly and cannot be held constant:
  • Most development teams can create unpowerful software that is simple to use.
  • Most development teams can create powerful software that is difficult to use.
  • It takes a highly skilled team to create software that is both powerful and simple to use.
The last item is of key interest. As an example, on our primary project it is critical that our software be simple to use, yet the operations it must perform are unavoidably complex. Our design team has spent many long hours searching for the right balance between power and simplicity on various portions of the system. Often, the end result of our requirement and design discussions results in something like this:

Usually (after recovering for an evening or a weekend) we are able to grind through the issue and eventually create a good solution. But it takes considerable time and effort. Other implications when developing a simple+powerful system are:
  • Planning: Be prepared to spend a lot more time on requirements, designing, prototyping, and reviewing until the combination is right.
  • Hiring: Your team must contain a great depth of domain knowledge - it will not be sufficient to attack the problem with technical wizardry only. Working by rote against a typical listing of functional requirements, use cases, etc. will not deliver a solution of any great value. A true understanding of the real business problems and opportunities to which the system will be applied is critical.
  • Back Office software: "Back end" processing software may need a higher degree of sophistication than the typical mishmash of scripted processing routines.
  • User Interface: It takes an open mind and considerable time and attention to detail to look at your system with the eyes of a new user.
It many cases it is inappropriate to force simplicity and power into the same system (or portion of a system). A reduction in complexity, or less emphasis on simplicity, may be appropriate. Keep in mind the alternate paths this potentially puts you on:
  • Less simplicity results in more user training and documentation. This is not acceptable if your audience is the general public, but may be appropriate if user staff is relatively small.
  • Less simplicity may cause the need for a "Man Behind the Curtain" to make the system work. This might be OK for small systems, but typically does not scale well.
  • Less power: Be certain that your requirements for high end functionality are really necessary - it is often surprising and rewarding to examine requirements through the lens of ROI, schedule impact, and alternative solutions.

Friday, December 8, 2006

Ruby moves up to #11 on the TIOBE Programming Community Index

Ruby has moved to position number 11 on the TIOBE Programming Community Index. I'm not sure what all this means, as I have not studied the methods used to create the index, but it's pretty cool regardless. Ruby was in position #20 a year ago.

The next languages above Ruby on the list are #10 Javascript, #9 Delphi (an old favorite of mine), and #8 is C#. It will be interesting to see how the rankings go the next few months.

Thursday, December 7, 2006

Ruby on Rails Deprecations Part 3 of 3: ActionView

This final installment in posts about deprecated features of Ruby on Rails covers Views. See Part 1 for Controllers, and Part 2 for ActiveRecord.

Deprecated View features
  • content_for('name_of_content_block') - use yield :name_of_content_block
  • :human_size - use :number_to_human_size
  • link_image_to - use image_tag within a link_to method
  • :post as a link modifier - use :method => "post" instead
  • render_partial - use render :partial
  • render_partial_collection - use render :partial, :collection

Wednesday, December 6, 2006

Ruby on Rails Deprecations Part 2: ActiveRecord

My search of deprecations for ActiveRecord returned a mercifully short list. Here they are:

Deprecated Associations
  • :dependent => true - use :dependent => :destroy
  • :exclusively_dependent - use :dependent => :delete_all
  • push_with_attributes - if associations require attributes, use has_many :through
Deprecated Methods
  • count by conditions or joins - use count(column_name, options)
  • find_all - use find(:all, ...)
  • find_first - use find(:first, ...)
  • human_attribute_name - use .humanize
  • Object-level transactions

Tuesday, December 5, 2006

Ruby on Rails Deprecations Part 1: ActionController

Rails provides reasonable facilities for notifying you when deprecated code is encountered. However, I have been unable to find a centralized listing of deprecated features so I can avoid them prior to coding. So, I've examined the Rails 1.2 Release Candidate 1 code to put together such a list. This article contains a list of deprecated Controller items. Subsequent listings will cover Models and Views.

Items are listed alphabetically, followed (if applicable) by the appropriate replacement. If you are aware of omissions or see an error, leave a comment and I'll update the list.

Deprecated: General

  • Components are deprecated.
  • All dependency loaders in module Dependencies now belong to ActiveSupport instead of ActiveController (:depend_on, :dependencies_on, :model, :observer, :service)

Deprecated Controller Instance Variables

  • @cookies, @flash, @headers, @params, @request, @response, @session - use reader/writer methods

Deprecated Controller Methods

  • expire_matched_fragments - use expire_fragment
  • keep_flash - use flash.keep
  • parse_query_parameters - use parse_form_encoded_parameters
  • parse_request_parameters - use parse_form_encoded_parameters
  • redirect_to_path - use redirect_to(path)
  • redirect_to_url - use redirect_to(url)
  • render('#{options}') - use render :file => #{options}
  • url_for(:#{options}) - use url_for with a named route directly

Deprecated Assertions

  • assert_assigned_equal - use assert_equal(expected, @response.template.assigns[key.to_s])
  • assert_cookie_equal - use assert(@response.cookies.key?(key))
  • assert_flash_empty - use assert(!@response.has_flash_with_contents?)
  • assert_flash_equal - use assert_equal(expected, @response.flash[key])
  • assert_flash_exists - use assert(@response.has_flash?)
  • assert_flash_has - use assert(@response.has_flash_object?(key))
  • assert_flash_has_no - use assert(!@response.has_flash_object?(key))
  • assert_flash_not_empty - use assert(@response.has_flash_with_contents?)
  • assert_flash_not_exists - use assert(!@response.has_flash?)
  • assert_invalid_column_on_record - use assert(record.errors.invalid?(column))
  • assert_invalid_record - use assert(!assigns(key).valid?)
  • assert_no_cookie - use assert(!@response.cookies.key?(key))
  • assert_redirect - use assert_response(:redirect)
  • assert_redirect_url - use assert_equal(url, @response.redirect_url)
  • assert_redirect_url_match - use assert(@response.redirect_url_match?(pattern))
  • assert_rendered_file - use assert_template
  • assert_session_equal - use assert_equal(expected, @response[key])
  • assert_session_has - use use assert(@response.has_session_object?(key))
  • assert_session_has_no - use assert(!@response.has_session_object?(key))
  • assert_success - use assert_response(:success)
  • assert_template_equal - use assert_equal(expected, @response.template.assigns[key.to_s])
  • assert_template_has - use assert(@response.has_template_object?(key))
  • assert_template_has_no - use assert(!@response.has_template_object?(key))
  • assert_template_xpath_match - use assert_tag
  • assert_valid_record - use assert(assigns(key).valid?)
  • assert_valid_column_on_record - use assert(!record.errors.invalid?(column))

Harvest Outfitters: Rails Site Recently Depoyed

Harvest Outfitters is a company in which I have a business interest. The company supplies kerosene cooking and lighting appliances and accessories for emergency preparedness. The site was originally a J2EE system that has recently been rewritten in Rails. It is running on Mongrel - so far we are very satisfied with Mongrel as a server. Take a look....

Monday, December 4, 2006

Justify Your Choice of Ruby on Rails: Articles and Links

If you have a project of any significance for which you are using or considering using Ruby on Rails, you will have to justify your decision to several brutal audiences. Your customers, investors, those that may acquire your business, your coworkers, managers, team, etc. have a whole boatload of concerns about technologies that are not regarded as mainstream. If you're not using something widely regarded as safe (J2EE or .Net), you'd better be prepared to dance.

Performance and Scalability

The top two concerns are usually performance and scalability: "Ruby is slow" or "Interpreted languages are slow" seem to be the most common. Here are some excellent articles on the subject (the overall summary being: Rails provides a great advantage in getting your product to market, there are reasonable and inexpensive ways of addressing any performance bottlenecks, and scalability is not a problem):

It's Boring to Scale With Ruby on Rails
Scalability Examples: Hardware Requirements at Basecamp and Robot Co-Op (43Things)
Outsourcing the Performance-Intensive Functions
Making Things Faster

Large Systems Using Rails

There are a lot more than listed here, but these are some of the largest:

Amazon Uses Rails on Amazon Unspun
Basecamp has over 1 million users
43Things has over 700,000 users
Odeo is a Rails App
Blinksale is a Rails App
List of Other Rails Deployments

Philosophy Behind Rails

Many of the arguments against Rails are rooted in a misunderstanding of philosophy, purpose, and design decisions behind the framework. The following articles provide clarity in this area:

Ruby on Rails: An Interview with DHH
IndicThreads.com interview with DHH
The Reg sits down with DHH
Choose a Single Layer of Cleverness

Are You Afraid?

Fear-Driven Technology Choices
Is Rails Ready for Prime Time?
Are We Approaching a Tipping Point for Rails?
Will Rails Become Mainstream?
Does it Matter If It Does?
How to Introduce Ruby on Rails in Your Company
Paul Graham: Beating the Averages (It's about Lisp as a strategic advantage. Just read "Rails" where it says "Lisp" and you'll get the idea).
TIOBE Programming Community Index One indicator of the rate of Ruby popularity (as of this writing #12 and moving up fast).

Other Articles

Some of these articles are dated and are not up to date with respect to capabilities of the current version of Rails, but nevertheless provide good material.

Enterprise Rails
Evaluating Ruby
RoR in the Enterprise Toolbox
Rails / J2EE Comparison from IBM
Rails Bidding: Put Your Money Where Your Mouth Is
Rails Perspective from the guy at the bank

Know the Enemy

Get to know what the other side is saying. There is a lot of writing out there (some rational and well reasoned, some misinformed, and some from people that are almost certainly insane). Search for 'rails sucks' or 'ruby sucks' and you'll have hours of entertaining reading. You need to understand what Rails strengths are, what its weaknesses are, and what FUD is out there in order to be able to make (and defend) a wise decision.

Thursday, November 30, 2006

In Praise of Automated Tests: A Built In Break

In the good old days of slow computers, slow compilers, and tens or hundreds of thousands of lines of code that had to be built before you could test, compiling provided a nice break in your work cycle:

"Why are you just sitting there? Get back to work."
Programmer's automatic reply: "I'm compiling...."

Faster compilers, faster computers, and scripting languages all conspired to remove this perk of the business. Thankfully, automated testing has brought it back:

"Why are you just sitting there?"
New reply: "I'm testing."

Who can argue against testing?

Wednesday, November 29, 2006

Rails Security Checklist

Security is a dull topic, but will become exciting if you screw it up. To avoid such excitement, here is a checklist for reviewing security in models, controllers, and views. If you see holes, add a comment and I'll update the list.

Security checklist for models:
  • Use attr_accessible (or attr_protected if you must) to explicitly identify attributes that are accessible by .create and .update_attributes. Just because you don't expose an attribute on an edit form doesn't mean that someone won't try to post a value to it. I prefer attr_accessible over attr_protected as it fails on the side of safety when new fields are added to a model - you have to explicitly expose new fields.
  • Make sure queries are using the Rails bind variable facility for parameters, not string concatenation or the handy Ruby's #{...} syntax.
  • Use validations to prevent bad input.
Security checklist for controllers:
  • Make non-action controller methods private (if possible).
  • If non-action controller methods must be public, identify them with hide_action to prevent unwanted execution.
  • Make sure before_filters are in place if necessary for your authorization infrastructure.
  • Move queries from your controller to your model, and see the model checklist above.
  • Check for params[:id] usage - are you sure you can trust it? Check for proper ownership of the record.
  • Check for usage of hidden fields - a user can send anything to you through them, so treat them with suspicious just as params[:id] should be suspect.
  • Use filter_parameter_logging to prevent entry of sensitive unencrypted data (passwords, SSN's, credit card numbers, etc.) in your server logs.
  • Forget about your view code for a minute, and think about how to protect your controller from posts a malicious user could make to any of your exposed methods. All parameters (whether or not exposed on a form, and whether or not invisible) are suspect to length overruns, bypassing of any browser based validation, attacks with malformed data, etc.
Security checklist for views:
  • Make sure all data displayed is escaped with the helper method h(string).
  • Eliminate comments in your views that you don't wish the entire world to see.
What else? (In particular, considerations for REST web services and AJAX need to be added).

Tuesday, November 28, 2006

Ruby on Rails Anti-Pattern?: Primary Key Visibility, Using in Code

Using an autoincrement integer field as the primary key / object id lets the Rails table relationship magic flow, and is a common design pattern in other O/R frameworks. Be careful about using this value for other purposes, however. It is typical to consider using the ID for such things as:
  • As a customer number, part number, invoice number, etc. visible to users or printed on documents.
  • Explicitly referenced in your source code to drive business logic.
Why is exposing or referencing the ID a potential problem? It may bite you in several ways:
  • If using ID's to drive business logic, your data is no longer easily portable between databases. This is particularly a problem when workinging on a project with multiple developers each running on their own local development database. It is possible to keep configuration record id's related to business logic in sync., but takes some effort. Throw in test databases, test fixtures, production systems, demo systems, etc. and you are signed up for even more synchronization fun.
  • There are surprising and annoying non-technical reasons that may cause the need to change formatting and layout of customer numbers, invoice numbers, part numbers, etc. It is very unpleasant to have those as part of your key structure if you have to restructure them. (A particularly ugly example: systems using Social Security Number (SSN) as a primary key, foreign key, etc. are a nightmare to rework when eliminating/hiding/encrypting SSN for privacy reasons.)
  • Sometime during the lifecycle of your application, it is possible that you will move your production data to a different database instance or platform. While you do have to maintain foreign key integrity among ID's while performing such a task, you will at least not have to worry about also synchronization with the outside world to maintain the integrity of the customer numbers, invoice numbers, or part numbers.
What to do instead? Consider incurring the wrath of the database normalization gods:
  • Use a separate, non-primary key field for identifiers visible by your users. This is a bit of work as you have to roll another autoincrement field, driven by a separate table, but is worth considering. Having such a field decouples your database structure (primary and foreign keys, table relationsihps) from the whims of how users use an identifier as a purchase order number, invoice number, customer number, part number, etc.
  • Use a mapping field of some sort for objects that must be explicitly identified in code to drive business rules or other behavior. This is easy to do, but does incur some run-time overhead as you now reference my_object.some_other_object.map_field instead of my_object.some_other_object_id in your code.
  • In the case of configurable selections against which you may have to code logic, don't use a table (and accompanying model) just to have an database driven drop down selection box. If there is no logic or other data associated with the selections, simply store the actual selection value in your record, thereby eliminating the object ID issue altogether.
Related: See c2.com for a great catalog of anti-patterns.

Monday, November 27, 2006

Put Away that Hammer: Use Ruby-fu to avoid unnecessary inheritance in Rails

Overusing inheritance is a well-known pitfall of object oriented languages. Ruby provides insanely great toys to allow you to extend classes (or even objects) without resorting to ugly inheritance hierarchies that are not relevant to your real-world domain model.

While working on Rails projects I have learned to break old habits:
  • Don't inherit from a class simply to extend it.
  • Don't write a wrapper class to extend an existing class.
Instead, do the following:
  • Extend a project class using "include" to include a module.
  • Extend Ruby's classes (careful!) the same way.
  • Or - extend an existing class by declaring it and adding additional methods.
  • In some scenarios, extend an object rather than a class.
Extremely useful articles in greater depth are at Luke Redpath and in Code Snippets. Ruby modules are documented in the Ruby Core API.

Thursday, November 23, 2006

Running with Scissors: Are you still coding without automated tests?

Do you run with scissors? Play football without a helmet? Drive while drunk? Do you still create software without scripted tests? For quite some time, I was a test driven development hypocrite. I had read the books, read the magazine articles, dabbled with various testing tools, and believed test driven development was the way to go. But for various reasons, I never quite had the gumption to push test driven development into our organization (or even into my own work habits).

Switching to Ruby on Rails finally kicked me into shape. Creating and running scripted tests within Rails is almost too easy not to do - the framework draws you into testing with it's excellent capabilities for Unit Tests, Functional Tests, and Integration Tests. I was initially hooked by using unit tests to do initial model testing (reaction: "cool.... this is much nicer than monkey testing various boundary behaviors"), then got the full testing religion after doing some significant code refactoring on code that was fully covered by tests ("Carrumba! I'll never write an application without fully automated tests again!").

One of the common complaints and FUD mentioned about Ruby and Rails is non-existent or weak support for debugging (although reportly Radrails, and possibly other tools, does have a decent level of debugging capabilities). Interestingly enough, I have found this to be an almost irrelevant issue, as my debugging is almost always done through the tests, not by stepping through code. I don't always follow the exact sequence of "write a test, see it fail, write some code to make it pass", but do make sure that my code is covered by tests, and do use the tests to exercise boundary conditions, failure cases, and so on.

Two additional tools we have found to be particularly useful for testing Rails code:
  • rcov: code coverage for ruby - provides coverage analysis with excellent web reporting of the results.
  • Watir - Web Applicatoin Testing in Ruby - The Rails tests can give your application and database servers a good beating. But they do run on the application server, only exercising a subset of your hardware and software. When you are ready to test your entire infrastructure from a browser (exercising your ISP, front-end web servers, SSL, etc.) , consider Watir. It's not really a load testing tool, but it does let you create browser-based tests scripted in Ruby, is quick and easy to install and use, and is handy if don't read (or are not ready for) incurring the full effort of a full blown load testing solution.
In our company, we are convinced that the tradeoff of creating more code up front (often the test code is larger than the code being tested) easily pays for itself in a shortened development schedule. More importantly, we are also convinced that over the lifespan of our application, having a fully automated test suite will give us a huge advantage in long-term agility, will hugely reduce our maintenance costs, and ultimately will have a significant impact on the profitability and valuation of our business.

If you're not using Rails, there is almost certainly a tool available for your programming language. There are innumerable references on this subject, a good place to start looking is here.

Wednesday, November 22, 2006

A link for Technorati

Nothing to see here, move along....
Technorati Profile

Tuesday, November 21, 2006

Using Rails in_place_editor and in_place_editor_field

In-place form editing is the first Recipe in the Rails Recipes book, and for good reason. Our application has a screen on which the user enters a dollar amount, from which several other amounts are calculated and displayed for review. A user typically cycles through this process 2 or 3 times before being ready to save their results.

The in_place_editor_field (or in_place_editor) provides a clean solution for this need. The user can click the amount to edit, click ok to submit it (while remaining on the page), and review the calculated results.

I coded the view using in_place_editor_field (which uses parameters identical to text_field - send it your object and column to be edited, and you're in business). In our case, the model object is named coverage, and the field to be edited is annual_amount, so the view contains:

<%= in_place_editor_field('coverage', 'annual_amount') %>

The user can now click the amount, edit it in place, and click OK to submit the results.

To process the results (since we wanted to do some calculations, rather than just saving the entry), we are responsible for providing a controller method (named appropriately as it is automatically called when the user submits their edit) as follows:

def set_coverage_annual_amount
#store posted amount
#(available in params[:value] )
#and/or use the results in calculations
end

The params[:value] that was automatically submitted to the method, so I did my calculations based on the submitted value. A more typical use is to store the submission, but in my case that was deferred until final form submission.

Finally, one the results are submitted and processed, the controller method wants a corresponding view to render its results. In this case, an rjs template was the way to go, so I created the view set_coverage_annual_amount.rjs with just one line of code:

page.replace_html 'amounts_group',
:partial => 'subscriber_amounts'

The subscriber_amounts partial form was already available, displayed all of the recalculated fields, and was just as easy to re-render as opposed to updating each individual field in the rjs template (which would have worked just as well). What did not work was attempting do do the partial render directly within the controller's set_coverage_annual_amount method - this resulted in the edited amount field being replaced with the entire 'amounts_group' div contents.

If you need similar functionality on any piece of data, in_place_editor works in a similar fashion. You display a piece of data identifiable via the DOM (such as by using the span tag with an ID - for this example the ID is 'myamount'). Then follow up with in_place_editor in the same view to mark the item with the necessary AJAX goodness:
<%= in_place_editor('myamount',   
{:url => url_for(
:action => "set_coverage_annual_amount")}) %>
Note quite as pretty as in_place_editor_field, as you have to explicitly identify the destination the edit will be posted to, but the capability is still nice to have when you need it.

See the Rails wiki, Shoo.gr, and Shane's Brain Extension for other articles (in addition to the Rails API reference and the Rails Recipes book) that I found particularly helpful on this topic.

Gotcha: Ruby Class Variable Scope is Global

While working on the authorization portion of an application, I was using a class variable to cache lookups of access rights throughout the duration of a users session. For various obscure reasons that are not relevant, I did not want to cache the rights on a session variable, so was using a class variable for this purpose. This technique worked nicely for a single web user and during functional testing, but failed miserably with two or more users.

As it turns out (as tested on Webrick and Mongrel), class variables are shared between web sessions (this holds true for controller classes and for models). Depending upon your prior language and platform background, this may not be news to you, but it was to me, as I beleived class variables were isolated by user session. To make matters worse, under both Mongrel and Webrick on a Windows machine, the class variables ARE isolated by user session. The problem did not manifest itself until the application was deployed to Linux at our hosting site.

Lessons learned:
  • Be careful about assumptions you bring with you from other languages and platforms.
  • Test for simultaneous multiple users - just running your unit, functional, and integration tests for a single user is not sufficient.
  • While the portability of Ruby and Rails code is magnificent, there is at least one subtle difference (probably more) between behavior on platforms.
  • I now have have one more reason for needing to buy a Mac for software development.
The good news is that this behavior opens the possibility of selectively sharing cached data between users on a single machine, although this topic needs exploration as there are possibly better methods for doing this.

See RubyCenteral here and here for good overviews of class variables. See RailsTips.org for an article explaining the difference between class variables and class instance variables.

Monday, November 20, 2006

Returning multiple results from a Ruby method

While it is preferable to have a method call return a single value, sometimes it is necessary to return multiple results. Returning an array or hash works for this purpose, array results can be received into individual variables.

For example, consider a method that returns a boolean success/failure indicator, along with an integer representing the number of items having that status. The following examples show use of an array, and use of a hash, to return the results:

1. Return the results as an array, receive the values in a list of variables.
Return the results (in this case true, and 23):

class MyClass
def some_method
[true, 23]
end
end

Receive the results in separate variables:

my_object = MyClass.new
status, counter = my_object.some_method

2. Return the results as a hash:
Return the results in a hash (in this case with keys of :status and :counter):

class MyClass
def some_method
{:status => true, :counter => 23}
end
end

Use the results:

my_object = MyClass.new
results = my_object.some_method

#the results are now in the hash
puts results[:status]
puts results[:counter]

Sunday, November 19, 2006

Do you grok Ruby on Rails?

During the last 9 or 10 months I have learned a great deal about programming with Ruby on Rails - enough to say that I am confident and comfrotable using it. But I have not mastered Ruby or Rails - I still learn something new almost every day. This blog documents things learned while proceeding beyond the basics, mixed with occasional thoughts about software development in general.

With that said, an explanation of the word grok may be in order:
  • If you have mastered Perl, you grok punctuation.
  • If you have mastered Java (in particular J2EE), you grok XML.
  • If you have been working as an Enterprise Astronaut, you might grok WSDL, BPEL, UML, EAI, CYA, FUD, and a whole boatload of enterprise-speak.
  • If you have mastered .NET, you grok Visual Studio.
  • If you have mastered PHP, you most likely grok spaghetti......
If that's not enough, see wikipedia's entry for grok.