Helpers are for small snippets of code

Manfred Stienstra, 04 Jul 2007, 15:49 in ruby on rails and practices, last updated 04 Jul 2007, 20:45 (edit).

Recently we contracted a local company to help out with a Rails project. Their primary job was to implement payments. During an audit or their work I found the following helper:

module CoursesHelper
  include ActiveMerchant::Billing 
  def issuers
    @gateway = IdealGateway.new(
         :merchant            => IDEAL_MERCHANT_ACCOUNT,
         :sub_id              => IDEAL_SUB_ID,
         :password            => IDEAL_PRIVATE_KEY_PASS,
         :private_cert        => IDEAL_PRIVATE_CERT,
         :language            => IDEAL_LANGUAGE,
         :private_key         => IDEAL_PRIVATE_KEY,
         :authentication_type => IDEAL_AUTHENTICATION_TYPE
    )
    
    list = Array.new()
    begin
      response = @gateway.issuers
      if response.success?
        list=Array.new()
        list.push({"issuerName"=>"Kies uw bank...", "issuerID"=>0})
        list=list + (response.params["list"])
      end
    rescue
      list = [{:issuerName=>"Kies uw bank...", :issuerID=>""},
      {:issuerName=>"ofline Simulator", :issuerID=>"00"}]
    end
    return list.map {|i| [ i["issuerName"], i["issuerID"] ] }

  end
  
end

Helpers are for keeping simple code out of your view. They should never include complicated logic. A helper should be so simple that you’d almost dare to deploy without testing.

Also, because it’s a good idea to keep coupling between classes to a minimum, the payment gateway should only be called from classes directly related to payment processing.

Finally, you don’t want such a large number of constants in your code. There are better ways to store the gateway configuration.

The contracter was asked to solve these issues. The gateway initialization was refactored to only appear once and the gateway object was assigned to a constant named IDEAL_GATEWAY. The code to find all issuers was moved to the Payment model.

class Payment < ActiveRecord::Base
  def self.ideal_issuers
    response = IDEAL_GATEWAY.issuers
    response.params["list"]
  end
end
module CoursesHelper
  
  def issuers
    list=Array.new()
    # 'Kies uw bank' means 'Choose your bank'
    list.push({"issuerName"=>"Kies uw bank...", "issuerID"=>0}) 
    list=list + Payment.ideal_issuers
    return list.map {|i| [i["issuerName"], i["issuerID"]] }
  end
  
end

Unfortunately, the issuers helper still looks more like Python than Ruby. I would have written it like this:

module CoursesHelper
  def issuers
    [['Kies uw bank...', 0]] + Payment.ideal_issuers.map \
      { |i|[i['issuerName'], i['issuerID']] }
  end  
end

I don’t think you should include instructions on how to use a drop-down as one of the options inside it. Furthermore, a default value makes no sense in this case. People always have to choose a bank, otherwise they can’t continue with the payment process.

After some refactoring I was left with the code below. I’ve also moved it to the helpers module for the payments controller, because that’s where the form is rendered.

module PaymentsHelper
  def issuers
    Payment.ideal_issuers.map { |i| [i['issuerName'], i['issuerID']] }
  end
end

Comments

  1. Chris Obdam about 2 hours later: (delete)

    More important question is: is this iDeal code available for the public? :-) Or did somebody put it in ActiveMerchant recently?

  2. Manfred Stienstra about 3 hours later: (delete)

    The guys at Agile Dovadi submitted iDeal patches to ActiveMerchant recently, but they haven't been accepted yet.

  3. Michael Schuerig about 3 hours later: (delete)

    Unfortunately, you don't say where to put complicated presentational logic if it does exist. You don't want to have it in a view and it simply doesn't belong in a model.

    I have no objections against complicated helpers, but I insist on having tests for them. Presentational logic can be nicely encapsulated in classes with helper functions providing only a thin procedural layer on top. I found it particularly helpful to use the Builder design pattern. An alternative I'd like to explore further is to use explicit Presenter objects as has been advocated by various people, Jay Fields for one.

  4. Manfred Stienstra about 3 hours later: (delete)

    Ah, right, that's a good point.

    I know some caboose people swear by Presenter objects, some people create plugins, some people write libraries and but it in lib/ and some people even create gems (ie. RedCloth). I don't really care how you call it, you will somehow have to create a layer between models and the view.

    In our case there was no complicated presentation logic because it wasn't presentation logic at all and it could be easily integrated with the Payment model.

  5. Thijs van der Vossen about 5 hours later: (delete)

    I would go so far as to say that if you think you have complicated presentation logic, it's probably not presentation logic.

  6. Manfred Stienstra about 5 hours later: (delete)

    It's fine line. For instance when you have code where you parse the body tekst of a post and extract embedded Quicktime movies to add them as download links at the bottom. Is that presentational logic, or is that a parser?

  7. Julik 1 day later: (delete)

    Array.new() is legendary I think

  8. Soemirno 2 days later: (delete)

Add your comment

In order to fight spam on this blog, posting comments from a browser without javascript is currently not supported.