Odd Ruby Methods
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.
Monkey Patch of the Month: group_by
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…
Going crazy with to_proc
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.
3 times ActiveSupport 3
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.
http_accept_language released as a gem
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.
