copy-edit viget posts
This commit is contained in:
@@ -2,11 +2,10 @@
|
||||
title: "Practical Uses of Ruby Blocks"
|
||||
date: 2010-10-25T00:00:00+00:00
|
||||
draft: false
|
||||
needs_review: true
|
||||
canonical_url: https://www.viget.com/articles/practical-uses-of-ruby-blocks/
|
||||
---
|
||||
|
||||
Blocks are one of Ruby\'s defining features, and though we use them all
|
||||
Blocks are one of Ruby's defining features, and though we use them all
|
||||
the time, a lot of developers are much more comfortable calling methods
|
||||
that take blocks than writing them. Which is a shame, really, as
|
||||
learning to use blocks in a tasteful manner is one of the best ways to
|
||||
@@ -19,22 +18,42 @@ Often times, I'll want to assign a result to a variable and then execute
|
||||
a block of code if that variable has a value. Here's the most
|
||||
straightforward implementation:
|
||||
|
||||
user = User.find_by_login(login) if user ... end
|
||||
```ruby
|
||||
user = User.find_by_login(login)
|
||||
|
||||
if user
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
||||
Some people like to inline the assignment and conditional, but this
|
||||
makes me ([and Ben](https://www.viget.com/extend/a-confusing-rubyism/))
|
||||
stabby:
|
||||
|
||||
if user = User.find_by_login(login) ... end
|
||||
```ruby
|
||||
if user = User.find_by_login(login)
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
||||
To keep things concise *and* understandable, let's write a method on
|
||||
`Object` that takes a block:
|
||||
|
||||
class Object def if_present? yield self if present? end end
|
||||
```ruby
|
||||
class Object
|
||||
def if_present?
|
||||
yield self if present?
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This way, we can just say:
|
||||
|
||||
User.find_by_login(login).if_present? do |user| ... end
|
||||
```ruby
|
||||
User.find_by_login(login).if_present? do |user|
|
||||
# ...
|
||||
end
|
||||
```
|
||||
|
||||
We use Rails' [present?](http://apidock.com/rails/Object/present%3F)
|
||||
method rather than an explicit `nil?` check to ignore empty collections
|
||||
@@ -49,11 +68,24 @@ pages and then, if there are multiple pages, displaying the links.
|
||||
Here's a helper that calculates the number of pages and then passes the
|
||||
page count into the provided block:
|
||||
|
||||
def if_multiple_pages?(collection, per_page = 10) pages = (collection.size / (per_page || 10).to_f).ceil yield pages if pages > 1 end
|
||||
```ruby
|
||||
def if_multiple_pages?(collection, per_page = 10)
|
||||
pages = (collection.size / (per_page || 10).to_f).ceil
|
||||
yield pages if pages > 1
|
||||
end
|
||||
```
|
||||
|
||||
Use it like so:
|
||||
|
||||
<% if_multiple_pages? Article.published do |pages| %> <ol> <% 1.upto(pages) do |page| %> <li><%= link_to page, "#" %></li> <% end %> </ol> <% end %>
|
||||
```erb
|
||||
<% if_multiple_pages? Article.published do |pages| %>
|
||||
<ol>
|
||||
<% 1.upto(pages) do |page| %>
|
||||
<li><%= link_to page, "#" %></li>
|
||||
<% end %>
|
||||
</ol>
|
||||
<% end %>
|
||||
```
|
||||
|
||||
## `list_items_for`
|
||||
|
||||
@@ -62,15 +94,48 @@ elegant view code. Things get tricky when you want your helpers to
|
||||
output markup, though. Here's a helper I made to create list items for a
|
||||
collection with "first" and "last" classes on the appropriate elements:
|
||||
|
||||
def list_items_for(collection, opts = {}, &block) opts.reverse_merge!(:first_class => "first", :last_class => "last") concat(collection.map { |item| html_class = [ opts[:class], (opts[:first_class] if item == collection.first), (opts[:last_class] if item == collection.last) ] content_tag :li, capture(item, &block), :class => html_class.compact * " " }.join) end
|
||||
```ruby
|
||||
def list_items_for(collection, opts = {}, &block)
|
||||
opts.reverse_merge!(:first_class => "first", :last_class => "last")
|
||||
|
||||
concat(collection.map { |item|
|
||||
html_class = [
|
||||
opts[:class],
|
||||
(opts[:first_class] if item == collection.first),
|
||||
(opts[:last_class] if item == collection.last)
|
||||
]
|
||||
|
||||
content_tag :li,
|
||||
capture(item, &block),
|
||||
:class => html_class.compact * " "
|
||||
}.join)
|
||||
end
|
||||
```
|
||||
|
||||
Here it is in use:
|
||||
|
||||
<% list_items_for Article.published.most_recent(4) do |article| %> <%= link_to article.title, article %> <% end %>
|
||||
```erb
|
||||
<% list_items_for Article.published.most_recent(4) do |article| %>
|
||||
<%= link_to article.title, article %>
|
||||
<% end %>
|
||||
```
|
||||
|
||||
Which outputs the following:
|
||||
|
||||
<li class="first"><a href="/articles/4">Article #4</a></li> <li><a href="/articles/3">Article #3</a></li> <li><a href="/articles/2">Article #2</a></li> <li class="last"><a href="/articles/1">Article #1</a></li>
|
||||
```html
|
||||
<li class="first">
|
||||
<a href="/articles/4">Article #4</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/articles/3">Article #3</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/articles/2">Article #2</a>
|
||||
</li>
|
||||
<li class="last">
|
||||
<a href="/articles/1">Article #1</a>
|
||||
</li>
|
||||
```
|
||||
|
||||
Rather than yield, `list_items_for` uses
|
||||
[concat](http://apidock.com/rails/ActionView/Helpers/TextHelper/concat)
|
||||
@@ -80,7 +145,7 @@ in order to get the generated markup where it needs to be.
|
||||
|
||||
Opportunities to use blocks in your code are everywhere once you start
|
||||
to look for them, whether in simple cases, like the ones outlined above,
|
||||
or more complex ones, like Justin\'s [block/exception tail call
|
||||
or more complex ones, like Justin's [block/exception tail call
|
||||
optimization technique](https://gist.github.com/645951). If you've got
|
||||
any good uses of blocks in your own work, put them in a
|
||||
[gist](https://gist.github.com/) and link them up in the comments.
|
||||
|
||||
Reference in New Issue
Block a user