Adventures with Ruby

Odd Ruby Methods

View Comments

I recently encountered some methods I didn’t know about.

~

The tilde method (~) is used by Sequel and you can use it like this:

class Post
  def ~
    11
  end
end

post = Post.new
~post # => 11

That’s right: you call this method by placing the tilde before the object.

In Sequel it is used to perform NOT queries, by defining the tilde on Symbol:

Post.where(~:deleted_at => nil)
# => SELECT * FROM posts WHERE deleted_at IS NOT NULL

-@

The minus-at (-@) method works just about the same as the tilde method. It is the method called when a minus-sign is placed before the object. In effect, this means that Fixnum is implemented somewhere along the lines of this:

class Fixnum
  def -@
    self * -1
  end
end

num = 10
negative = -num # => -10

I found this one browsing through the source of the ActiveSupport::Duration class (you know, the one you get when you do 1.day). I guess it makes sense.

Be careful with chaining methods though:

ruby-1.9.1-p378 > num = 5
 => 5
ruby-1.9.1-p378 > -num.to_s
NoMethodError: undefined method `-@' for "5":String
	from (irb):5
	from /Users/iain/.rvm/rubies/ruby-1.9.1-p378/bin/irb:17:in `
'

This also applies to the tilde method.

So?

I don’t know. Enrich your DSLs and APIs, if it makes any sense to do it.

Written by Iain Hecker

March 30th, 2010 at 9:37 am

Posted in iain.nl

Monkey Patch of the Month: group_by

View Comments

Photo courtesy of killthebird

A while back, I talked about new additions to ActiveSupport. And now, I have a confession to make: I like monkey patches! At least, as long as they’re short and self-explanatory.

I got a few of them lying around, so I am going to post one every month. I also welcome your favorite monkey patch, which you can email me.

So, the first one: group_by. This method groups arrays of objects by the result of the block provided and puts the result into a hash.

class Array
  # Turns an array into a hash, using the results of the block as keys for the
  # hash.
  #
  #   [1, 2, 3, 4].group_by(&:odd?)
  #   # => {true=>[1, 3], false=>[2, 4]}
  #
  #   ["One", "Two", "three"].group_by {|i| i[0,1].upcase }
  #   # => {"T"=>["Two", "three"], "O"=>["One"]}
  def group_by
    hash = Hash.new { |hash, key| hash[key] = [] }
    each { |item| hash[yield(item)] << item }
    hash
  end
end

No piece of code is complete without tests, so this is it:

class ArrayExtGroupingTests < Test::Unit::TestCase

  def test_group_by
    assert_equal {true=>[1, 3], false=>[2, 4]}, [1, 2, 3, 4].group_by(&:odd?)
    assert_equal {"T"=>["Two", "three"], "O"=>["One"]}, ["One", "Two", "three"].group_by {|i| i[0,1].upcase }
  end

end

Update

Silly me, this one is already in Ruby itself. Anyway, this is how it works under the cover…

Written by Iain Hecker

March 19th, 2010 at 4:10 pm

Posted in iain.nl

Going crazy with to_proc

View Comments

You all know Symbol#to_proc, right? It allows you to write this:

# Without Symbol#to_proc
[1, 2, 3].map { |it| it.to_s }
[3, 4, 5].inject { |memo, it| memo * it }

# With Symbol#to_proc
[1, 2, 3].map(&:to_s)
[3, 4, 5].inject(&:*)

It has been in Rails as long as I can remember, and is in Ruby 1.8.7 and 1.9.x. I love it to death and I use it everywhere I can.

It is actually quite simple, and you can implement it yourself:

class Symbol
  def to_proc
    Proc.new { |obj, *args| obj.send(self, *args) }
  end
end

It works because when you prepend an ampersand (&) to any Ruby object, it calls #to_proc to get a proc to use as block for the method.

What I always regretted though was not being to pass any arguments, so I hacked and monkeypatched a bit, and got:

class Symbol

  def with(*args, &block)
    @proc_arguments = { :args => args, :block => block }
    self
  end

  def to_proc
    @proc_arguments ||= {}
    args = @proc_arguments[:args] || []
    block = @proc_arguments[:block]
    @proc_arguments = nil
    Proc.new { |obj, *other| obj.send(self, *(other + args), &block) }
  end

end

So you can now write:

some_dates.map(&:strftime.with("%d-%M-%Y"))

Not that this is any shorter than just creating the darn block in the first place. But hey, it’s a good exercise in metaprogramming and show of more of Ruby’s awesome flexibility.

After this I remembered something similar that annoyed me before. It’s that Rails helper methods are just a bag of methods available to, because they are mixed in your template. So if you have an array of numbers that you want to format as currency, you’d have to do:

<%= @prices.map { |price| number_to_currency(price) }.to_sentence %>

What if I could apply some to_proc-love to that too? All these helper methods cannot be added to strings, fixnums, and the likes; that would clutter way to much. Rather, it might by a nice idea to use procs that understands helper methods. Here is what I created:

module ProcProxyHelper

  def it(position = 1)
    ProcProxy.new(self, position)
  end

  class ProcProxy

    instance_methods.each { |m| undef_method(m) unless m.to_s =~ /^__|respond_to\?|method_missing/ }

    def initialize(object, position = 1)
      @object, @position = object, position
    end

    def to_proc
      raise "Please specify a method to be called on the object" unless @delegation
      Proc.new { |*values@object.__send__(*@delegation[:args].dup.insert(@position, *values), &@delegation[:block]) }
    end

    def method_missing(*args, &block)
      @delegation = { :args => args, :block => block }
      self
    end

  end

end

I used a clean blank class (in Ruby 1.9, you’d want to inherit it from BasicObject), in which I will provide the proper proc-object. I play around with the argument list a bit, handling multiple arguments and blocks too. You can now use this syntax:

<%= @prices.map(&it.number_to_currency).to_sentence %>

That is a lot sexier if you as me. And you can use it in any object, not just inside views. And lets add some extra arguments and some Enumerator-love too:

class SomeClass
  include ProcProxyHelper

  def initialize(name, list)
    @name, @list = name, list
  end

  def apply(value, index, seperator)
    "#{@name}, #{index} #{separator} #{value}"
  end

  def applied_list
    @list.map.with_index(&it.apply(":"))
  end

end

In case you are wondering, the position you can specify is to tell where the arguments need to go. Position 0 is the method name, so you shouldn’t use that, but any other value is okay. An example might be that you cant to wrap an array of texts into span-tags:

<%= some_texts.map(&it(2).content_tag(:span, :class => "foo")).to_sentence %>

So there you have it. I’m probably solving a problem that doesn’t exist. It is however a nice example of the awesome power of Ruby. I hope you’ve enjoyed this little demonstration of the possible uses of to_proc.

Written by Iain Hecker

February 5th, 2010 at 10:13 pm

Posted in iain.nl

3 times ActiveSupport 3

View Comments

Rails 3 is coming. All the big changes are spoken of elsewhere, so I’m going to mention some small changes. Here are 3 random new methods added to ActiveSupport:

presence

First up is Object#presence which is a shortcut for Object#present? && Object. It is a bit of a sanitizer. Empty strings and other blank values will return nil and any other value will return itself. Use this one and your code might be a tad cleaner.

"".presence # => nil
"foo".presence #=> "foo"

# without presence:
if params[:foo].present? && (foo = params[:foo])
  # ..
end

# with presence:
if foo = params[:foo].presence
  # ...
end

# The example Rails gives:
state   = params[:state]   if params[:state].present?
country = params[:country] if params[:country].present?
region  = state || country || 'US'
# ...becomes:
region = params[:state].presence || params[:country].presence || 'US'

I like this way of cleaning up you’re code. I guess it’s Rubyesque to feel the need to tidy and shorten your code like this.

uniq_by

Another funny one is Array.uniq_by (and it sister-with-a-bang-method). It works as select, but returns only the first element from the array that complies with the block you gave it. Here are some examples to illustrate that:

1, 2, 3, 4 ].uniq_by(&:odd?) # => [ 1, 2 ]

posts = %W"foo bar foo".map.with_index do |title, i|
  Post.create(:title => title, :index => i)
end
posts.uniq_by(&:title)
# => [ Post("foo", 0), Post("bar", 1) ] ( and not Post("foo", 2) )

some_array.uniq_by(&:object_id) # same as some_array.uniq

exclude?

And the final one for today is exclude? which is the opposite of include?. Nobody likes the exclamation mark before predicate methods.

# yuck:
!some_array.include?(some_value)
# better:
some_array.exclude?(some_value)

And it also works on strings:

# even more yuck:
!"The quick fox".include?("quick") # => false
# better:
"The quick fox".exclude?("quick") # => false

The full release notes of Rails 3 can be read here.

Written by Iain Hecker

February 3rd, 2010 at 10:42 am

Posted in iain.nl

http_accept_language released as a gem

View Comments

I released an old Rails plugin as gem today. Slowly but surely, all my plugins will be converted to gems.

This time it’s an old one: http_accept_language

  • Splits the http-header into languages specified by the user
  • Returns empty array if header is illformed.
  • Corrects case to xx-XX
  • Sorted by priority given, as much as possible.
  • Gives you the most important language
  • Gives compatible languages

For more information, read the README on GitHub.

Written by Iain Hecker

January 5th, 2010 at 12:27 pm

Posted in Uncategorized