davideisinger.com

My personal website
Log | Files | Refs | README

index.md (4031B)


      1 ---
      2 title: "Practical Uses of Ruby Blocks"
      3 date: 2010-10-25T00:00:00+00:00
      4 draft: false
      5 canonical_url: https://www.viget.com/articles/practical-uses-of-ruby-blocks/
      6 ---
      7 
      8 Blocks are one of Ruby's defining features, and though we use them all
      9 the time, a lot of developers are much more comfortable calling methods
     10 that take blocks than writing them. Which is a shame, really, as
     11 learning to use blocks in a tasteful manner is one of the best ways to
     12 up your Ruby game. Here are a few examples extracted from a recent
     13 project to give you a few ideas.
     14 
     15 ## `if_present?`
     16 
     17 Often times, I'll want to assign a result to a variable and then execute
     18 a block of code if that variable has a value. Here's the most
     19 straightforward implementation:
     20 
     21 ```ruby
     22 user = User.find_by_login(login)
     23 
     24 if user 
     25   # ...
     26 end 
     27 ```
     28 
     29 Some people like to inline the assignment and conditional, but this
     30 makes me ([and Ben](https://www.viget.com/extend/a-confusing-rubyism/))
     31 stabby:
     32 
     33 ```ruby
     34 if user = User.find_by_login(login)
     35   # ...
     36 end 
     37 ```
     38 
     39 To keep things concise *and* understandable, let's write a method on
     40 `Object` that takes a block:
     41 
     42 ```ruby
     43 class Object
     44   def if_present?
     45     yield self if present?
     46   end
     47 end 
     48 ```
     49 
     50 This way, we can just say:
     51 
     52 ```ruby
     53 User.find_by_login(login).if_present? do |user|
     54   # ...
     55 end 
     56 ```
     57 
     58 We use Rails' [present?](http://apidock.com/rails/Object/present%3F)
     59 method rather than an explicit `nil?` check to ignore empty collections
     60 and strings.
     61 
     62 ## `if_multiple_pages?`
     63 
     64 Methods that take blocks are a great way to wrap up complex conditional
     65 logic. I often have to generate pagination and previous/next links for
     66 JavaScript-powered scrollers, which involves calculating the number of
     67 pages and then, if there are multiple pages, displaying the links.
     68 Here's a helper that calculates the number of pages and then passes the
     69 page count into the provided block:
     70 
     71 ```ruby
     72 def if_multiple_pages?(collection, per_page = 10)
     73   pages = (collection.size / (per_page || 10).to_f).ceil
     74   yield pages if pages > 1
     75 end
     76 ```
     77 
     78 Use it like so:
     79 
     80 ```erb
     81 <% if_multiple_pages? Article.published do |pages| %>
     82   <ol>
     83     <% 1.upto(pages) do |page| %>
     84       <li><%= link_to page, "#" %></li>
     85     <% end %>
     86   </ol>
     87 <% end %> 
     88 ```
     89 
     90 ## `list_items_for`
     91 
     92 As you saw above, Rails helpers that take blocks can help create more
     93 elegant view code. Things get tricky when you want your helpers to
     94 output markup, though. Here's a helper I made to create list items for a
     95 collection with "first" and "last" classes on the appropriate elements:
     96 
     97 ```ruby
     98 def list_items_for(collection, opts = {}, &block)
     99   opts.reverse_merge!(:first_class => "first", :last_class => "last")
    100 
    101   concat(collection.map { |item|
    102     html_class = [
    103       opts[:class],
    104       (opts[:first_class] if item == collection.first),
    105       (opts[:last_class] if item == collection.last)
    106     ]
    107     
    108     content_tag :li,
    109       capture(item, &block),
    110       :class => html_class.compact * " " 
    111   }.join)
    112 end 
    113 ```
    114 
    115 Here it is in use:
    116 
    117 ```erb
    118 <% list_items_for Article.published.most_recent(4) do |article| %>
    119   <%= link_to article.title, article %>
    120 <% end %> 
    121 ```
    122 
    123 Which outputs the following:
    124 
    125 ```html
    126 <li class="first">
    127   <a href="/articles/4">Article #4</a>
    128 </li>
    129 <li>
    130   <a href="/articles/3">Article #3</a>
    131 </li>
    132 <li>
    133   <a href="/articles/2">Article #2</a>
    134 </li>
    135 <li class="last">
    136   <a href="/articles/1">Article #1</a>
    137 </li> 
    138 ```
    139 
    140 Rather than yield, `list_items_for` uses
    141 [concat](http://apidock.com/rails/ActionView/Helpers/TextHelper/concat)
    142 and
    143 [capture](http://apidock.com/rails/ActionView/Helpers/CaptureHelper/capture)
    144 in order to get the generated markup where it needs to be.
    145 
    146 Opportunities to use blocks in your code are everywhere once you start
    147 to look for them, whether in simple cases, like the ones outlined above,
    148 or more complex ones, like Justin's [block/exception tail call
    149 optimization technique](https://gist.github.com/645951). If you've got
    150 any good uses of blocks in your own work, put them in a
    151 [gist](https://gist.github.com/) and link them up in the comments.