~4 minutes

ActiveSupport: thanks, try, tap

The more time I spend with the Rails source, the more I appreciate it. I’ve always appreciated it for what it could do, but now I’m appreciating it directly, for what it is. I find it to be quite elegant, pragmatic, aesthetically pleasing, and educational. Inside the black box, it’s really quite nice.

So…thank you to all of the people that continue to make it better and to DHH and 37Signals for leading the charge. It’s a really great framework to use and study.

Hash#to_param

The first source fragment for today is a method responsible for URL generation of query strings from a Hash…something which I’m confident I’ve rolled my own at least once. It’s important to realize that many of the core objects have methods like to_param and to_query and that they all work in concert to handle the arbitrary structure we might throw at it, but we are just examining this one. Check out to_param.rb and to_query.rb for more type specific implementations.

(from core_ext/object/to_param.rb)

class Hash
  # Returns a string representation of the receiver suitable for use as a URL
  # query string:
  #
  #   {name: 'David', nationality: 'Danish'}.to_param
  #   # => "name=David&nationality=Danish"
  #
  # The string pairs "key=value" that conform the query string
  # are sorted lexicographically in ascending order.
  #
  # This method is also aliased as +to_query+.
  def to_param(namespace = nil)
    collect do |key, value|
      value.to_query(namespace ? "#{namespace}[#{key}]" : key)
    end.sort! * '&'
  end
end

Calling collect with a block on a Hash gives easy access to key value pairs. There are many ways to do that, but it’s neat to learn another way. It is also interesting that the block also has a postfix operation tacked onto it. Take a look at end.sort! * '&'. We can reason pretty easily about what that does (sort in place and join the array with ampersands).

That said, I don’t think I’ve ever used array * str before and I rarely postfix a multiline block. I think I have been using far more temporary variables than I really need to…

See the Array docs for more details.

Object#try

I know that I have littered my code with existence checks…things like:

@person.name if @person && @person.respond_to?(:name)

Thankfully, there’s a better way baked into every object…try!

(from core_ext/object/try.rb)

class Object
  # Invokes the public method whose name goes as first argument just like
  # +public_send+ does, except that if the receiver does not respond to it the
  # call returns +nil+ rather than raising an exception.
  def try(*a, &b)
    if a.empty? && block_given?
      yield self
    else
      public_send(*a, &b) if respond_to?(a.first)
    end
  end
end

So for my egregious code above, I can use the cleaner:

@person.try(:name)

Much better!

String#indent

This next code fragment has a method I have never seen before. How cool is that?

(from string/indent.rb)

class String
  def indent!(amount, indent_string=nil, indent_empty_lines=false)
    indent_string = indent_string || self[/^[ \t]/] || ' '
    re = indent_empty_lines ? /^/ : /^(?!$)/
    gsub!(re, indent_string * amount)
  end
  # tons of docs removed for brevity
  def indent(amount, indent_string=nil, indent_empty_lines=false)
    dup.tap {|_| _.indent!(amount, indent_string, indent_empty_lines)}
  end
end

Ok, so the actual presence of String#indent is rather helpful and it does pretty much exactly what you think it does…but hold up…what is tap?

Object#tap

From the docs:

Yields x to the block, and then returns x. The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.

This clever bit of code seems to come from MenTaLguY where you’ll also find other cool uses for it described.

Like, simple debugging without breaking stuff:

foo = [1,2,3,4]
foo.tap{|_| puts "inspecting #{_.inspect}"}.each do |x|
 # ...
end

In the case of indent, since it taps into a destructive method (indent!) it changes the string while remaining chainable.

That’s all for now, have fun reading the source!

Tagged with: programming

My first novel is coming soon-ish!

Check out Singular