A better “try()” for Ruby, why not do the Groovy way?

The new method in Ruby 1.9 is making some people happy and creative too.
The only problem with that in my opinion is that every one is fixed about using method chaining with the name “try” with a symbol as a parameter to escape from the:

@person.name unless @person.nil?

or

@person ? @person.name : nil

and for some strange reason they think it is natural to write:

@person.try(:name)

when I see the sentence above, the first thing I want to “try” is:

@person.try(:to_swim)

Ok, the last comment was not cool, but I thought it would be funny :D

So, what is this post about?

In Groovy there is a built’in language construct to solve this situation in a very cool way:

person?.name

The method call after the “?” is only called if the previous expression was not nil.

I could not think in a way to implement any thing like that in ruby, but I think I came out with a satisfactory solution.
What do you think about flagging your method calls with “I know this can be null, but ignore it, it is a normal situation”?
No, I’m not telling you to write all this sentence in your code :D
the flag I’m using now is the “_” character.

I’m monkey patching ruby like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class NilClass
   alias :old_method_missing :method_missing
   def method_missing(methodname,*args)
	  old_method_missing methodname, *args unless methodname.to_s =~ /.*_$/
   end
end
class Object
   alias :old_method_missing :method_missing
   def method_missing(methodname,*args)
      methodname = (methodname.to_s.gsub(/(.*)_$/,'\1')).to_sym
	  if respond_to? methodname
	     send methodname,*args
	  else
	     old_method_missing methodname, *args 
	  end
   end
end

and you can use it like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#test classes
class Named
	attr_accessor :name
	def initialize(name)
		@name = name
	end
end
class Person < Named
	attr_accessor :name, :company
	def initialize(name,company=nil)
		super(name)
		@company = company
	end
end
#Testing starts here
def run_test
	puts @person.name_
	puts @person.company_.name_
end
@person = Person.new('Urubatan',Named.new('The Best'))
run_test
@person = nil
run_test


With this little method missing hack, we just put an “_” in the end of the method name we want to call and if the target object is nil the method will not be called and we will not get a NoMethodError.
Better yet, we can even pass arguments to the method we want to call :D

This way, we can use this solution only in situations we know it is safe.
What do you think about this?
I prefer the groovy solution, but I kine this one better than the “try” method :D

<updated>
Added one more link to the list in the beggining of the post (thanks coderr)
</updated>

If you enjoyed this post, make sure you subscribe to my RSS feed!

18 comments so far

  1. […] Bom, desta vez não vou traduzir o meu post, então deem uma olhadinha no meu blog em ingles: A better “try()” for Ruby, why not do the Groovy way? […]

  2. coderrr March 5, 2008 11:44 pm

    you have seen this solution, no?

    http://coderrr.wordpress.com/2007/09/15/the-ternary-destroyer/

    it’s probly the most similar to the groovy way.

    and I agree the .try() method is pretty ugly.

  3. Bruno March 6, 2008 2:03 pm

    “The method call after the “?” is only called if the previous expression was not nil. I could not think in a way to implement any thing like that in ruby, but I think I came out with a satisfactory solution.”

    I don’t see the “try(:method)” as a “clean” solution, too. But, unfortunately, “:?” isn’t a valid ruby symbol. So, I guess, we can’t define a “?” method. What about this solution?

    class MyProxy
    def initialize(object)
    @object = object
    end

    def method_missing(methodname, *args)
    if @object.respond_to? methodname
    @object.send(methodname,*args)
    end
    end
    end

    class Object
    def e?
    return MyProxy.new(self)
    end
    end

    x = nil
    x.e?.name
    x = Struct.new(:name)
    x.e?.name

    (Yeah, I know, there are better ways to implement a proxy object in ruby. And there are more methods that need to be overwritten, like “object_id” and “class”. And that “e?” isn’t the better method name of all times. But this is a simple proof of concept =D)

  4. Urubatan March 6, 2008 2:40 pm

    coderrr and bruno, I have seen this solution before, and I think it is safer than mine (because it does not play much with method_missing)
    I just think that @person.e?.company.e?.manager.e?.name
    is a little stranger to write than just placing a suffix in the method names …

    but I agree that the implementation is a lot more elegant than mine :D

  5. […] A better “try()” for Ruby, why not do the Groovy way? (tags: ruby programming) […]

  6. Mark March 7, 2008 10:18 pm

    This is the ugliest version I’ve seen yet.

  7. Arya March 8, 2008 1:02 am

    Interesting approach, but it removes the readability that we rubyists love. The underscore character has no meaning to the reader and to someone who is reading someone else’s code, there would either have to be comments for each use of it or ask the original programmer to figure out why there is an underscore.

    Maybe a more readable solution but very similar to what you did is to have it be “try_method_name” but that uses the “try” word that you don’t like.

    Anyway, good work, I think it can potentially be better and usable.

  8. Renan March 8, 2008 5:30 pm

    Urubatan, off-topic:
    what do you use(plugin) to put code(jsp, ruby, java) in your blog ?
    Thank you!

  9. Matt Todd March 9, 2008 2:45 am

    Could just see if methodname[0..-2] exists, effectively allowing:

    person?.name

    The test could be written:

    respond_to? :”#{methodname[0..-2]}” if methodname.split(”).last == ‘?’

    Something like that… Come now, we can use the ‘?’ in our method names! Makes much more sense than ‘_’.

  10. Matt Todd March 9, 2008 2:47 am

    By the by, your captcha image isn’t showing up… :(

  11. Rajesh Duggal March 11, 2008 3:37 pm

    I’ve been doing…
    @person && @person.name

    It’s a little bit less typing than
    @person.name unless @person.nil?

    It gets really useful when you want to go deeper into an object..

    e.g. id = @person && @person.friend && @person.friend.youngest && @person.friend.youngest.user && @person.friend.youngest.user.id

    ( Careful now.. http://en.wikipedia.org/wiki/Law_of_Demeter )

    Cheers,
    Rajesh Duggal

  12. Urubatan March 12, 2008 12:06 pm

    I think the captcha is already working :D
    but if SK2 is working you’ll not see it again :D
    Matt, I agree that the “?” termination would be more clear, but there is already a convention in ruby programming saying that methods terminated in “?” return a boolean value …

    Renan, I use the WP-Syntax plugin

    Arya, try_method_name is a good approach too, and it looks like calling a method (different of try(:method_name) ), the “_” having no meaning is a matter of convention, line the “?” at the end of boolean methods :D

    Mark, I agree this is ugly, but I think try(:method_name) is uglier because it is not clear that you are calling a method (IMHO) :D

  13. […] 루비에서도 이 문법을 차용해오자는 의견도 있다. 구현은 method_missing을 이용해 처리해줄 수 있을 것 같다. 재미있는 […]

  14. Me parece uma cópia do operador ? do Io …

    http://www.iolanguage.com/scm/git/checkout/Io/docs/IoGuide.html

  15. […] Monkey-patching proposals have abounded in the Ruby community recently - including, but not limited to Object#andand, Object#_? and SafeNil and Object#method_. […]

  16. Rajesh Duggal April 30, 2008 6:22 pm
  17. Arno Nyhm May 18, 2008 3:22 pm

    whats about a ? in as the first letter?

    puts @person.?company.?name

  18. Urubatan May 18, 2008 10:49 pm

    Arno, thinking about the other comments in this thread, I change my position, and I agree that playing with method_missing is not a good solution …
    Even because Rails applications, and Ruby applications in general do not have a very complex object hierarchy.
    By complex I mean, usually you do not go very deep in the object model to show an information.
    I think this post was inspired by the java programmer inside my head :D

Leave a comment

Please be polite and on topic. Your e-mail will never be published.