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.