--- http_interactions: - request: method: get uri: https://rubytapas.dpdcart.com/subscriber/login body: encoding: US-ASCII string: '' headers: Accept-Encoding: - gzip,deflate,identity Accept: - "*/*" User-Agent: - Mechanize/2.7.3 Ruby/2.1.2p95 (http://github.com/sparklemotion/mechanize/) Accept-Charset: - ISO-8859-1,utf-8;q=0.7,*;q=0.7 Accept-Language: - en-us,en;q=0.5 Host: - rubytapas.dpdcart.com Connection: - keep-alive Keep-Alive: - 300 response: status: code: 200 message: OK headers: Date: - Fri, 26 Dec 2014 07:13:06 GMT Server: - Apache Set-Cookie: - symfony=8uf97v120upgq6kb3n3m825621; path=/; HttpOnly Expires: - Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: - no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: - no-cache Googlebot: - noindex Robots: - noindex, nofollow Vary: - Accept-Encoding,User-Agent Content-Encoding: - gzip P3p: - CP="NOI CURa OUR NOR UNI" Content-Length: - '1682' Connection: - close Content-Type: - text/html; charset=utf-8 body: encoding: ASCII-8BIT string: !binary |- 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 HhFYEPh+m/0HztdzEmcPAAA= http_version: recorded_at: Fri, 26 Dec 2014 07:13:06 GMT - request: method: post uri: https://rubytapas.dpdcart.com/subscriber/login?__dpd_cart=db458bc3-a4d9-42d3-a679-59cea27092e4 body: encoding: UTF-8 string: username=joan%40example.com&password=password&redirect= headers: Accept-Encoding: - gzip,deflate,identity Accept: - "*/*" User-Agent: - Mechanize/2.7.3 Ruby/2.1.2p95 (http://github.com/sparklemotion/mechanize/) Accept-Charset: - ISO-8859-1,utf-8;q=0.7,*;q=0.7 Accept-Language: - en-us,en;q=0.5 Cookie: - symfony=8uf97v120upgq6kb3n3m825621 Host: - rubytapas.dpdcart.com Referer: - &1 !ruby/object:URI::HTTPS scheme: https user: password: host: rubytapas.dpdcart.com port: 443 path: "/subscriber/login" query: opaque: registry: fragment: parser: Content-Type: - application/x-www-form-urlencoded Content-Length: - '66' Connection: - keep-alive Keep-Alive: - 300 response: status: code: 302 message: Found headers: Date: - Fri, 26 Dec 2014 07:13:07 GMT Server: - Apache Expires: - Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: - no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: - no-cache Location: - https://rubytapas.dpdcart.com/subscriber/content Set-Cookie: - persist_login=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/ Vary: - Accept-Encoding,User-Agent Content-Encoding: - gzip P3p: - CP="NOI CURa OUR NOR UNI" Content-Length: - '116' Connection: - close Content-Type: - text/html body: encoding: ASCII-8BIT string: !binary |- H4sIAAAAAAAAAy3NSw7DIAwA0atE7Btn3QJ3MeAKpPCJbSr19lHT7Gf0bNa6 e5sJk7eVFJesOh50zPJxhunNJNkssTelps5sr8m7+zXyBOAZvooDZU0jRWRd Y68gM0jkEojh/gx4C38DLvAEF4qnnXcAAAA= http_version: recorded_at: Fri, 26 Dec 2014 07:13:07 GMT - request: method: get uri: https://rubytapas.dpdcart.com/subscriber/content body: encoding: US-ASCII string: '' headers: Accept-Encoding: - gzip,deflate,identity Accept: - "*/*" User-Agent: - Mechanize/2.7.3 Ruby/2.1.2p95 (http://github.com/sparklemotion/mechanize/) Accept-Charset: - ISO-8859-1,utf-8;q=0.7,*;q=0.7 Accept-Language: - en-us,en;q=0.5 Cookie: - symfony=8uf97v120upgq6kb3n3m825621 Host: - rubytapas.dpdcart.com Referer: - *1 Connection: - keep-alive Keep-Alive: - 300 response: status: code: 200 message: OK headers: Date: - Fri, 26 Dec 2014 07:13:17 GMT Server: - Apache Expires: - Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: - no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: - no-cache Googlebot: - noindex Robots: - noindex, nofollow Vary: - Accept-Encoding,User-Agent Content-Encoding: - gzip P3p: - CP="NOI CURa OUR NOR UNI" Content-Length: - '27108' Connection: - close Content-Type: - text/html; charset=utf-8 body: encoding: ASCII-8BIT string: !binary |- H4sIAAAAAAAAA9W9aZcbR5It+Ll5jv6DC3yvKM1JZGLNjVqGq5TVoshislpd Zz5UOyIcgCsDEVAsCUI9/d/Hrpl7RACZVCGpF3Fi9F4XyUzAY3FzW69d++bL l29ffPjHu1dqma8i9e7vz3+6eqF6/ZOTX8YvTk5efnip/vPHD29+UsPjgfqQ 6jizuU1iHZ2cvPq5p3rLPF9fnpxsNpvjzfg4SRcnH96ffMRaQ3zZ/bWf1755 HOZh77tH3/AFP66iOPv2nmWGFxcX8m3+rNHhd4+U+mZlcq3w6b75rbC33/Ze JHFu4rz/Ybs2PRXIv77t5eZjfoJvP1XBUqeZyb8t8nn/vKdOeJ3c5pH57rqY ZUFq17gx5VZS/696X8y2H/RaZ9+cyOfoG/SdyMY3KjXRt70s30YmWxqT99Qy NfNveycnKX0px5eOw3UY6DQ/DpLVic7o0tkJf+E4yDJ/fbmsyumu3c3+qm+1 /LSnsjSQd5LRS9G/6o/HiyRZREavbSbL0s9OIjvLTn79rTDp9mR4PD0eun8c r2x8/GvW++6bE1kQV/zXF/XvqScvCl/6X1/NizjA2/nqa/Xf/BZOTtTShkbl S8Nv28aFUdkyWa9tvFCzIs+TOFN2rm6t2eBH+TJNisVS6Zh+muqV4WXs/Cv6 bZhsjjMTzdWX3yr3zzxZ+0vh+k+O/UX6/iJPvj7GHXz19VP+1P/Q//4P/33v cfHAeO315+UdWJnQatrEIDUm7snmzpJwq/5bzXRws6D7jcNL9fj1qyn+31O6 RGhvH6+0je98hP+rf+JIHdvcrPqQgH5oSvHCz+PQfOyv0yQsgp3f0apBEiUp LTjg/7CgPlL6UtPLvzX4262l82PC2kfH49PTiwt8lN4hb1Ifx8Skajmqfay6 xf2P7T3L8PXZc3pa9+rlv/oH+qvk9z6dAaPT/iLVoaXD8tVwMPjfiv/nYhCa xZFfhv4y1RfaTL/+gwU3ZnZj82oxWfxI0WqD/81/YOUjNU+T1Vd+vSOVJ1+5 q3z9NT+XSN3+84zGZ69fnlW/v1wmt4ZWd/883vunvOr9RQaDs/HL0e5FkpTe 3qUarj+qLIlsqB5PBufDZ69372X3/asv7WqdpLmOc3xMpBXSScrt/4hOu7vK D6wyZkleWyJOWAg/8Y33CX06u/vxIxUn8ySKkg1/Uc7Wiejkb/jk2PDbnvsW 1DUdBv4RDkT933wo8L3ed7SCXS12NV04mpliORlEQfj7fHYcREkR0t7H+XFs cnpdCala/PPkIphOhiMdnIbDgRmNR6EJBsH5fHR2EZ6ej2dmNhkOpmMzOV7H i57bsG97A9w930sQkV6m2yGNmva++0s8y9ZPvzmh39C9uj92bhnPCHVa/mrv O2vFeylb1NeRXcSXKqCXYdKn9D39R792BsRZwMqKQM3P9W89let0AbX8z1mk 45ved6V1Uq+f/e2bE033taZNof/Km9ZFvnSKjd6z/2mULJKC9udHQ1tJy650 qv9v+YM01hoGKlmR+aP75Xt6QqpqbiPz5Lt38hdcrPb7csGf+E+5FX4hLN74 W/1t72kf3hR6OcvRvWaY5GtUW672GLG+tT33w3/7pojkOf/t38g+f1feuhPG J/7ic5tmePRkZeQ+6cNuhRO/xL1XmxsT9rEaiTLu9w8v27P5Orj8lC+ApeiB S7H3zsE6oc9k+T9HTl79LePKli6t3AdIFOCNfNt7FoakBJX9UMSGjquO8v2f kZzvPuXubfoD97D7TLPs0/eYwrK6+3P7OTO4o/fX1+4Ws/0f33uXO0tHembI ASwXvPyjjSv3bEaCubNn9ZPglufPkISk2+qgLMffjU7P1L+bNDaRers2MXkl +VKRXlRvbBSZlKRyXH58vbNWqf4+JKHeqo1ROjXqV9KgZLVnW5WtTWB1pBaF yXJS4GZeW/eY/24zdqtwehM6IHP+V5bQmcmT/sz0yfc0OsNySXKj/vJbkeRP P5BKUXQ+A5NlcLfkhmlf5dfkOqisWNN5C5Ikki/qKFJ6RidWFfwVfFqROtEk aBv6ZI5PzukO6Eiu6y7cOoILBCWmQp3rY3U1V9ukUCb+Ndnyzd6QvcCNu5fB v82MUSQgpdo6UldqnZIPFvDv//J4fPEUdxRliSJFR7dxY4MbhfujlZb0UnDX x188+uLRFV0dPzDkBych+UV4a+QXbjJ6FPqVzuXRsWSm/iuhHfwvdXx8zBry rgy4u+yvkyzvwxqWolD9V56Zk1J80xN84XuStNPp6DEUI/naI/Wa/qKe5bkO litaNWNtub/al/0+va6Q/ON+v7rYN9lax9/9q0uR6ifVqd6QGZRzw9+qFrlr 2dzBcL+v/6N2bmo/OPCInKp3OifjFas3Og+WJCKHnIv972BDn2U4KEtNjheJ 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 txCIZb6Kvvv/ABC2p1vOYgIA http_version: recorded_at: Fri, 26 Dec 2014 07:13:17 GMT - request: method: get uri: https://joan%40example.com:password@rubytapas.dpdcart.com/feed body: encoding: US-ASCII string: '' headers: Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 Accept: - "*/*" User-Agent: - Ruby Host: - rubytapas.dpdcart.com response: status: code: 200 message: OK headers: Date: - Fri, 26 Dec 2014 07:13:18 GMT Server: - Apache Set-Cookie: - symfony=er9ek1m8pjgob7bhgf9c0dl6t0; path=/; HttpOnly Expires: - Thu, 19 Nov 1981 08:52:00 GMT Cache-Control: - no-store, no-cache, must-revalidate, post-check=0, pre-check=0 Pragma: - no-cache Vary: - Accept-Encoding,User-Agent P3p: - CP="NOI CURa OUR NOR UNI" Connection: - close Content-Type: - application/rss+xml; charset=UTF-8 body: encoding: UTF-8 string: "\n\n \n RubyTapas\n https://rubytapas.dpdcart.com/subscriber/content\n \ \n Thu, 25 Dec 2014 09:00:00 -0500\n \ contact@shiprise.net (Avdi Grimm)\n en\n \ Copyright 2014 RubyTapas\n getdpd.com\n \ RubyTapas: Small plates of gourmet code.\n \ \n \n \ https://getdpd.com/uploads/ruby-tapas.png\n Ruby Tapas\n https://rubytapas.dpdcart.com/subscriber/content\n \ 849\n 849\n \n \n \ <![CDATA[267 Kernel Open with Rob Miller]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=652\n \ \n

Today we are joined by special guest chef Rob Miller. Rob is the author of the soon-to-be-released book "Text Processing with Ruby", a super cool book all about using Ruby as a power tool for ripping through plain text data. If you enjoy the kind of content you see on RubyTapas, I predict you'll also get a kick out of his book.

\r\n\r\n

In this episode, Rob shows us that Ruby's `open` method is a bit like Clark Kent: on the surface, it looks like a simple, mild-mannered method for opening files. But scratch the surface, and it has some awesome super-powers. Enjoy.

\r\n\r\n

Show Notes:

\r\n\r\n\r\n\r\n
\r\n

If you've used Ruby for any length of time, you're probably familiar with the open method of the File class.

\r\n\r\n

Pass it the name of a file and, optionally, the mode in which you want to open the file — it defaults to read-only — and it will return for you a File object that you can use to read from and write to that particular file.

\r\n\r\n

Pass it a block, and it will yield that file object to the block, cleaning up after you once the block is finished.

\r\n\r\n

Here, we're opening our system's dictionary and reading the first word from it; in this case, the slightly uninteresting "A".

\r\n\r\n

open as a shortcut

\r\n\r\n
\r\nopen("/usr/share/dict/words/",
        "r") do |file|\r\n    file.gets\r\nend
\r\n\r\n

File.open works well, and is widely used. But to save our precious fingers the effort of typing, Ruby's Kernel module provides a method, also called open, that achieves the same result. Since the Kernel module is mixed in to all objects, it's available as method anywhere you care to use it.

\r\n\r\n

This concision would probably be reason enough to use open rather than the lengthier File.open. But it turns out that Kernel's open method isn't a mere alias for that of the File class. It has some tricks of its own, too.

\r\n\r\n

open with processes

\r\n\r\n
\r\nopen("|pbpaste", "r")
        do |clipboard|\r\n    clipboard.read\r\nend
\r\n\r\n

Here, we use the open method to open not a file, but a process. open knows to do this, rather than attempt to read a file, because the first character of the path that we pass to it is a pipe symbol.

\r\n\r\n

Behind the scenes, open is doing all sorts of plumbing for us; it's spawning a subprocess, and connecting the standard output stream of this subprocess to a pipe. This pipe is then yielded to the block passed into open, allowing us to use it to read from the process's output.

\r\n\r\n

In this case, the process that we're spawning is Mac OS X's pbpaste command, which allows us to read from the clipboard. This block, then, returns the text that's on the system clipboard.

\r\n\r\n
\r\nopen("|
        ps ax | grep ruby") do |ps|\r\n    ps.gets.split.first\r\nend
\r\n\r\n

But we could use open in this way to read from any command that produces output. We can even pass in pipeline chains, in which case our pipe will be connected to the standard output stream of the final process in the pipeline. In this example, we're using ps to search for Ruby processes running on our system. We're then taking the first line of output from ps, splitting on whitespace, and outputting the first field; this will print the process ID of the first Ruby process running on our system.

\r\n\r\n

Writing not reading

\r\n\r\n
\r\nopen("|pbcopy",
        "w") do |clipboard|\r\n    clipboard.write("hello, world")\r\nend
\r\n\r\n

Of course, just like with files, we're not limited only to reading from the process's standard output stream. We can also write to its standard input stream, too.

\r\n\r\n

In this example we use the pbcopy command, which copies text to the system clipboard. Since we opened the command in write mode, by passing the "w" flag to open, we're able to use the write method to write to the process's standard input stream, and in doing so copy text to the clipboard.

\r\n\r\n

Paging output

\r\n\r\n

One way that this behaviour of open can be put to practical use is to redirect our own standard output stream to another process. In doing so, we form a pipeline, just like we might in our shells on the command line.

\r\n\r\n

If you've ever used the version control software Git, you might have noticed that, if you run a command that produces a lot of output, such as git log, it will helpfully scroll the output for you. This ensures that you stay at the top of the output, but can scroll down further if you choose.

\r\n\r\n

Let's look at how open can help us achieve the same behaviour in our scripts.

\r\n\r\n
\r\nstdout
        = STDOUT.clone\r\nless = open("|less", "w")\r\nSTDOUT.reopen(less)
\r\n\r\n

Here, we clone the existing standard output stream so that we can restore it later. Then, we use open to spawn the less utility in a subprocess; it's less that will be taking care of the actual scrolling functionality.

\r\n\r\n
\r\n500.times
        do |n|\r\n    puts "#{n}: hello world"\r\nend
\r\n\r\n

Next, we output a lot of text; in this case, 500 fairly repetitive lines.

\r\n\r\n
\r\nSTDOUT.reopen(stdout)\r\nless.close
\r\n\r\n

Finally, at the end of our script, we revert standard output to its original location, and close the pipeline we connected to less, thus ending the subprocess.

\r\n\r\n

If we run this script, it should behave as we'd hoped. We stay at the top of the output, able to see the first line, but we can scroll down further if we wish. Perfect.

\r\n\r\n

open-uri

\r\n\r\n

Its ability to open both files and processes might have convinced you to use Kernel's open method, but it has a further trick up its sleeve.

\r\n\r\n
\r\nrequire
        "openuri"\r\n\r\nopen("http://www.rubytapas.com/") do
        |page|\r\n    page.base_uri\r\n    page.content_type\r\n    page.gets\r\nend
\r\n\r\n

By requiring the openuri library, which is part of the Ruby standard library, open gains yet another feature: the ability to open URLs. Just as open will spawn a subprocess if the path you pass to it starts with a pipe symbol, after including the openuri library it will request the path as a URL if it looks like one.

\r\n\r\n

This functionality wraps the native Net::HTTP, Net::HTTPS, and Net::FTP libraries, and in a simple script is certainly the easiest way to fetch such URLs.

\r\n\r\n

Here we fetch the RubyTapas homepage. The returned IO object is similar to the ones we saw when opening files and processes, but has a few methods of its own; here we check the URI that we fetched and the content type of the response. But it's otherwise a regular IO stream, and we can use methods like gets, each_line, and so on to process the response body.

\r\n\r\n

Summing up

\r\n\r\n

At first glance, open might seem like a simple shortcut for opening files. But it's actually a general purpose way to create many different types of IO object. Ruby's abstracted IO class makes reading from and writing to these disparate forms of input and output feel similar, so it seems only right that there should be a simple, overarching interface for creating them. Use it! And, as Avdi would say: happy hacking!

\n
\n

Attached Files

\n ]]>
\n \ dpd-9fdfec123347ab2437e3c5ef897511784739a3a9\n \ Thu, 25 Dec 2014 09:00:00 -0500\n \n In this guest episode, Rob Miller shows off some super powers of Ruby's `Kernel#open`.\n \ \n \
\n \n <![CDATA[266 Pattern Matching]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=651\n \ \n

Pattern Matching

\r\n\r\n

As we have seen in episode 261, Ruby has some fairly sophisticated data destructuring capabilities, at least for a dynamic object-oriented language. In that episode, we saw how we could take apart and bind the different parts of a dependency specification in a single statement. The parenthesized expression on the left of the assignment mimics the shape of the data, and Ruby takes apart the data structure and assigns the matching parts of it accordingly.

\r\n\r\n
\r\ndep
        = {"hello" => ["hello.c", "foo.o", "bar.o"]}\r\n\r\na
        = *dep                        # => [["hello", ["hello.c",
        "foo.o", "bar.o"]]]\r\n\r\n((target, (first_preq, *rest_preqs)))
        = *dep\r\n\r\ntarget                          # => "hello"\r\nfirst_preq
        \                     # => "hello.c"\r\nrest_preqs                      #
        => ["foo.o", "bar.o"]\r\n
\r\n\r\n

We have also seen, in various episodes, that Ruby has special tools for matching arbitrary objects against a pattern. The "case equality" operator, or "threequals", lets us apply all kinds of tests to an object. We can test its class, whether it matches a regular expression, whether it is within a given range, and so on.

\r\n\r\n
\r\nobj = 23\r\n\r\nInteger === obj                 #
        => true\r\n/foo/   === obj                 # => false\r\n(0...100) ===
        obj               # => true\r\n
\r\n\r\n

If you have used any functional programming languages with pattern-matching capabilities, such as Haskell or Elixir, you know that they combine the features of matching and destructuring assignment such that they can both be performed at once. Once you've used a language in the pattern-matching family, you might miss having it in Ruby. I know I do, so I thought it might be fun today to look at how we might add this capability to Ruby.

\r\n\r\n

We will start by defining a placeholder object. A placeholder is a simple creature: it has a context and a name. It also has a custom equivalence operator. This operator behaves a bit peculiarly: it takes the value it is supposed to be matching against, and instead assigns it as the value of this placeholder's name inside the context—which it assumes is a hash-like object. Then it simply returs true, regardless of what the value was. In effect, this is a wildcard object like we saw in episode #215, except it also "captures" values as a side effect.

\r\n\r\n

Next we'll define a MatchContext. It descends from BasicObject, so as to have a minimal set of methods defined. It has an internal hash, called bindings. This hash is specialized: when someone asks it for a key it doesn't have, it will return a placeholder for that key instead.

\r\n\r\n

The class also has a method_missing which simply takes any message sent to the object, and looks up the method's name as a key in the bindings hash.

\r\n\r\n
\r\nPlaceholder
        = Struct.new(:bindings, :name) do\r\n  def ==(other)\r\n    bindings[name]
        = other\r\n    true\r\n  end\r\nend\r\n\r\nclass MatchContext < BasicObject\r\n
        \ def initialize\r\n    @bindings = ::Hash.new { |hash, key| ::Placeholder.new(hash,
        key) }\r\n  end\r\n\r\n  def method_missing(name, *)\r\n    @bindings[name]\r\n
        \ end\r\nend\r\n
\r\n\r\n

Let's play around with these classes a little bit. We'll create a new MatchContext. Then we'll send it some random messages. Each time, it returns a placeholder named for that message.

\r\n\r\n

If we try to match one of these placeholders to an arbitrary value, it succeeds. We are able to do this with the case-equality operator even though we didn't explicitly define it, because case-equality delegates to the double-equals equivalence operator by default.

\r\n\r\n

If we then send the same message as before, we no longer get a placeholder. Instead, we get the value that was "captured" by performing an equality test.

\r\n\r\n
\r\nrequire
        "./pmatch"\r\n\r\nm = MatchContext.new\r\n\r\nm.foo                           #
        => #<struct Placeholder bindings={}, name=:foo>\r\nm.bar                           #
        => #<struct Placeholder bindings={}, name=:bar>\r\n\r\nm.foo ===
        23                    # => true\r\nm.foo                           # =>
        23\r\n
\r\n\r\n

Let's put these classes to work to do some very basic pattern matching. We'll define an old favorite, a Point struct. We'll instantiate a Point object. Then we'll use our MatchContext and do a pattern match against a Point with placeholder values.

\r\n\r\n

The result of the case equality test is true. And when we examine the placeholders, we can see that they are now bound to the X and Y values of the Point we matched on. In other words, we have successfully checked that that an object is a Point and bound its x and y values to variables, all in one go. Well, OK, not actually to variables, per se, but to something close enough.

\r\n\r\n
\r\nrequire
        "./pmatch"\r\n\r\nPoint = Struct.new(:x, :y)\r\n\r\np = Point.new(5,
        10)\r\nm = MatchContext.new\r\n\r\nPoint.new(m.x, m.y) === p       # =>
        true\r\n\r\nm.x                             # => 5\r\nm.y                             #
        => 10\r\n
\r\n\r\n

How did this happen? Well, Struct derived objects implement threequals in terms of equivalence. And by default, the equivalence test for a Struct is whether the two objects are the same type and whether their attributes are also equivalent. So comparing one Point to another implicitly delegates to the equivalence operators for the x and y attributes.

\r\n\r\n

So far, our placeholders match anything at all. But we'd like the option to be a little more discerning in our matches. For instance, we'd like to be able to assert that the X and Y values of a Point must be integers (and not nil) for the match to succeed.

\r\n\r\n

To make this possible, we need to flesh out the Placeholder class a little bit. We add a new attribute called guards, which defaults to an empty array. And we overload a method for adding new guards, somewhat arbitrarily choosing the right-shift operator for this purpose. In this method we add the right operand to the guards list, and return self to make further chaining possible.

\r\n\r\n

We then add a guard clause to the definition of the equivalence operator. It will perform case-equality matches of all of the guards against the supplied value, and return false if any of those matches fail.

\r\n\r\n
\r\nPlaceholder
        = Struct.new(:bindings, :name) do\r\n  def ==(other)\r\n    return false unless
        guards.all?{ |g| g === other }\r\n    bindings[name] = other\r\n    true\r\n
        \ end\r\n\r\n  def guards\r\n    @guards ||= []\r\n  end\r\n\r\n  def >>(guard)\r\n
        \   guards << guard\r\n    self\r\n  end\r\nend\r\n\r\nclass MatchContext
        < BasicObject\r\n  def initialize\r\n    @bindings = ::Hash.new { |hash,
        key| ::Placeholder.new(hash, key) }\r\n  end\r\n\r\n  def method_missing(name,
        *)\r\n    @bindings[name]\r\n  end\r\nend\r\n
\r\n\r\n

Now when we match using placeholders, we can annotate the placeholders with extra patterns that the corresponding value must match in order to succeed. In this case, we specify that both attributes of a point must be integers in order to match. This succeeds with the point we've been using. But when we change one of the coordinates to nil, the match no longer returns true.

\r\n\r\n
\r\nrequire
        "./pmatch2"\r\n\r\nPoint = Struct.new(:x, :y)\r\n\r\np = Point.new(5,
        10)\r\nm = MatchContext.new\r\n\r\nPoint.new(m.x >> Integer, m.y >>
        Integer) === p       # => true\r\n\r\nm.x                             #
        => 5\r\nm.y                             # => 10\r\n\r\np = Point.new(5,
        nil)\r\nm = MatchContext.new\r\n\r\nPoint.new(m.x >> Integer, m.y >>
        Integer) === p # => false\r\n
\r\n\r\n

Now let's use our placeholders on something slightly more practical. Let's say we have a method, get_account_balance. This method may fail, which we'll simulate in this example by giving it an explicit fail argument. If it succeeds, it returns an account balance as a string. If it fails, it returns an array of two elements: first, a symbol indicating that this is an error. And second, a string explaining the problem. Using different return types to indicate success or failure is a common style in pattern-matching programming languages.

\r\n\r\n

We then open a case statement, with the return value of a call to get_account_balance as the object to switch on. For the first case, we specify a placeholder that is constrained to be a String. In that branch, we print out the account balance. For the next case, we specify a pattern which will match an error return. For the second element in the array, we use another placeholder to capture the error explanation.

\r\n\r\n

If we execute this code, we can see that the case statement matches the success return value, and binds the account balance in the process. If we change the method call to force a failure and run the code again, it matches the error case this time. This time it binds the error info to a pseudo-variable that can be used in the error handling code.

\r\n\r\n

As with the struct, this works because Ruby arrays implement case-equality as an alias for equivalence, and determine equivalence by going over the array members one by one and asking them if they are equivalent to their counterpart in the other array.

\r\n\r\n
\r\nrequire "./pmatch2"\r\n\r\ndef
        get_account_balance(fail: false)\r\n  if fail\r\n    [:error, "I literally
        can't even"]\r\n  else\r\n    "$1234.56"\r\n  end\r\nend\r\n\r\nm
        = MatchContext.new\r\n\r\ncase get_account_balance(fail: true)\r\nwhen m.balance
        >> String\r\n  puts "Balance: #{m.balance}"\r\nwhen [:error,
        m.info]\r\n  puts "Error: #{m.info}"\r\nend\r\n\r\n# >> Error:
        I literally can't even\r\n
\r\n\r\n

This example really shows off the power of pattern-matching: whereas in normal ruby code we would have had to separately extract the values we were interested in after a case match, here we are able to do a case match and assign variables for later use all at the same time.

\r\n\r\n

And that's plenty for today. Happy hacking!

\n \
\n

Attached Files

\n ]]>
\n \ dpd-2ceb021ea3b938568e99170fc69ec8b4f0753173\n \ Mon, 22 Dec 2014 09:00:00 -0500\n \n Ever wanted to have fancy pattern-matching in Ruby like that found in languages like Erlang or Elixir? Well, today we'll construct the ability to do just that!\n \ \n \
\n \n <![CDATA[265 Method Introspection with Noah Gibbs]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=650\n \ \n

Noah Gibbs is a Ruby and Rails developer. He's the author of "Rebuilding Rails", a neat little book that helps you understand how Ruby on Rails works by rebuilding pieces of it from scratch. Today Noah steps into the RubyTapas kitchen in order to show us how to discover all kinds of useful information about the methods that are available in a Ruby program. It turns out we can find out quite a bit, just by asking the methods about themselves. I'll let Noah explain. Enjoy!

\r\n\r\n

Show Notes:

\r\n\r\n\r\n\r\n
\r\n

In Ruby, you can call .methods() on an object to see what other methods you can call on it. For instance:

\r\n\r\n
\r\n7.methods\r\n
\r\n\r\n

Remember that everything in Ruby is an object, so the number 7 is too. That means you can call .methods() on it.

\r\n\r\n

That output is kind of hard to read. It's in no particular order. So I like to sort them:

\r\n\r\n
\r\n7.methods.sort\r\n
\r\n\r\n

Sort does the right thing, so that looks better.

\r\n\r\n

This list also doesn't tell you, "what *interesting* methods does this have?" There's a fun old trick for that:

\r\n\r\n
\r\n7.methods.sort - Object.methods\r\n
\r\n\r\n

You can try to do this with any parent class -- not just Object -- but the obvious approach doesn't work quite right:

\r\n\r\n
\r\n7.methods.sort
        - Fixnum.methods\r\n
\r\n\r\n

Seven *is* a Fixnum, so you'd expect that to be empty. But .methods() gets called on the Fixnum object itself -- the class Fixnum, not a specific instance of Fixnum. We want the instance methods that an instance of Fixnum would have.

\r\n\r\n
\r\n7.methods.sort
        - Fixnum.instance_methods\r\n
\r\n\r\n

That's better. And we can do it with any parent class we want.

\r\n\r\n
\r\n7.methods.sort -
        Integer.instance_methods\r\n
\r\n\r\n

Now that's interesting. Apparently Ruby doesn't define things like multiplication or to_f for the Integer class. You wouldn't directly make an instance of class Integer, but that's still a little quirk that we might care about some day -- for instance, if you inherit one of your own objects from class Integer.

\r\n\r\n

So what else can we do with this?

\r\n\r\n

You can ask, "what was that method name again?"

\r\n\r\n

Here's one I use often. I forget the names of some of the Array and Hash methods, and this trick can find them for me:

\r\n\r\n
\r\n[].methods.sort
        - Object.methods\r\n
\r\n\r\n

Remember that you have to use an actual array object -- an instance -- for the first argument. You won't get a useful answer if you say this:

\r\n\r\n
\r\nArray.methods.sort - Object.methods\r\n
\r\n\r\n

because that's checking the Array class object. Not what you want!

\r\n\r\n

Ruby allows you to define a class across many files, so sometimes it's hard to know what methods there are, or where they're defined. It's nice to be able to get the list of methods for an object. But how can we find out more about a specific method? And especially, how can we find out who defined it?

\r\n\r\n

Imagine that somebody has monkeypatched a class. You know some libraries that do this.

\r\n\r\n

I'll add a very simple file that adds a sum method to your arrays and everything else enumerable. You can find it in the files for this episode. I'll require it here as an example of a mysterious third-party library that monkeypatches standard Ruby classes. If you're following along, put this into the same directory you're working in.

\r\n\r\n
\r\n# enumerable_monkeypatch.rb\r\nmodule Enumerable\r\n
        \ def sum\r\n    inject(&:+)                                # => 6,
        1, 2.0, nil\r\n  end\r\nend\r\n
\r\n\r\n

Now, let's make sure it works.

\r\n\r\n
\r\nrequire "./enumerable_monkeypatch"\r\n(1..99).sum\r\n
\r\n\r\n

Now if we look for this method on Array, we'll find it:

\r\n\r\n
\r\n[].methods.include?(:sum)\r\n
\r\n\r\n

But where did it come from? Ruby can tell us a lot about a method. So first let's grab that method:

\r\n\r\n
\r\n[].method(:sum)\r\n
\r\n\r\n

What can we do with a method?

\r\n\r\n
\r\n[].method(:sum).methods.sort
        - Object.methods\r\n
\r\n\r\n

Hm. "Owner" looks promising. Let's try that.

\r\n\r\n
\r\n[].method(:sum).owner\r\n
\r\n\r\n

That told us that it's on Enumerable. But maybe we can do even better.

\r\n\r\n
\r\n[].method(:sum).source_location\r\n
\r\n\r\n

Oh, hey! There's our source file and line number. And now you can find out where any method was defined. Like the chopsticks say, now you can debug anything!

\r\n\r\n

Oh -- one final bit of trivia before we're done. What if we try it on a function that isn't defined in Ruby? "Plus" on Fixnums is a C method, not a method in a Ruby source file.

\r\n\r\n
\r\n7.method(:+)\r\n
\r\n\r\n

Ruby says, "not telling." And now I'm done telling for this episode. I'm sure you'll enjoy the next one soon!

\n
\n

Attached Files

\n ]]>
\n \ dpd-32c626265a607aeae554925c3ad7aa0f84bb229d\n \ Thu, 18 Dec 2014 09:00:00 -0500\n \n Noah Gibbs joins us this week, to show us how we can find out all about ruby methods just by asking them.\n \n \
\n \n <![CDATA[264 Destructuring]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=649\n \ \n

Destructuring

\r\n\r\n

We've talked a bit about "destructuring" in the past, in episodes about "splatting" such as #80. Today I want to demonstrate an advanced example of destructuring.

\r\n\r\n

Just to quickly review, in a Ruby context, destructuring assignment is the process of assigning multiple variables at one time, with each bound to a different portion of a data structure. A very simple example is assigning both items in a two-element array to variables at the same time. By using the splat operator, we explicitly tell Ruby to "break up" the array and distribute it across the variables on the left side of the assignment. When we then inspect their values, we can see that a has taken on the first array entry, and b has taken on the second.

\r\n\r\n
\r\narr
        = [2, 3]\r\n(a, b) = *arr\r\na # => 2\r\nb # => 3\r\n
\r\n\r\n

As we've seen in other episodes such as #81, some of the syntax we've used here can be omitted in this simple case of splatting an array. But for the purpose of today's example I wanted to start out by illustrating the full, canonical syntax for destructuring.

\r\n\r\n

Let us now turn our attention to a slightly more complex data structure. In the Rake build tool, dependencies are represented in the form of single-entry hashes. For instance, here's an example of the dependencies for a C program. The final executable, called hello, depends on a file called hello.c. It also depends on some supporting object files called foo.o and bar.o. If any of these files are updated, the executable needs to be recompiled in order to be current.

\r\n\r\n
\r\ndep
        = {"hello" => ["hello.c", "foo.o", "bar.o"]}\r\n
\r\n\r\n

This dependency follows a common convention for build tools: the first file in the dependency list is "special". It is the primary source file for the hello executable, whereas the others are supporting libraries that need to also be linked in at compile time.

\r\n\r\n

In order to break out the component parts of this dependency, we could assign one piece at a time. In order to get the dependency target, we grab the first entry in the Hash. Individual hash entries are represented as two-element arrays, so we then take the first element in the resulting array.

\r\n\r\n

The first prerequisite, then, is the first element of the last element of the first hash entry. And the rest of the prerequisites are the second through last elements of the last element of the first hash entry.

\r\n\r\n
\r\ndep
        = {"hello" => ["hello.c", "foo.o", "bar.o"]}\r\n\r\ndep.first
        \                         # => ["hello", ["hello.c",
        "foo.o", "bar.o"]]\r\ntarget = dep.first.first        #
        => "hello"\r\nfirst_preq = dep.first.last.first # => "hello.c"\r\nrest_preqs
        = dep.first.last[1..-1] # => ["foo.o", "bar.o"]\r\n
\r\n\r\n

This code reads about as well as it sounds to describe it. Which is to say, it's complete gobbledygook. Let's try a different approach.

\r\n\r\n

A Hash can be splatted into an Array. The result is an array of two-element arrays - in this case, just one two-element array. And we know from episode #84 that we can destructure nested arrays so long as we mimic the expected structure using parentheses on the left side of the assignment. So we can build up a destructuring assignment which has named slots for the dependency target, the first prerequisite, and all the remaining prerequisites. For the rest of the prerequisites, we make use of the fact that a variable number of elements can be "slurped" into a single variable by preceding it with a star.

\r\n\r\n

Then, on the right side of the assignment, we splat out the dependency into an array, ready to be destructured.

\r\n\r\n

When we examine the resulting variable assignments, we can see that we successfully captured the target, primary prerequisite, and remaining prerequisites. We did it all in a single assignment. And we were able to do it using a parenthesized form that visually echoes the "shape" of the data we are destructuring.

\r\n\r\n
\r\ndep
        = {"hello" => ["hello.c", "foo.o", "bar.o"]}\r\n\r\na
        = *dep                        # => [["hello", ["hello.c",
        "foo.o", "bar.o"]]]\r\n\r\n((target, (first_preq, *rest_preqs)))
        = *dep\r\n\r\ntarget                          # => "hello"\r\nfirst_preq
        \                     # => "hello.c"\r\nrest_preqs                      #
        => ["foo.o", "bar.o"]\r\n
\r\n\r\n

And that's it for today. Happy hacking!

\n
\n

Attached Files

\n ]]>
\n \ dpd-982511f0b569a2021b2eb63700e6e69a6accbc83\n \ Mon, 15 Dec 2014 09:00:00 -0500\n \n Today's special is a demonstration of how we can use Ruby's built-in \"destructuring\" capabilities to simplify complex assignments.\n \n \
\n \n <![CDATA[263 Immutable Enumerators with Tom Stuart]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=648\n \ \n

In today's special guest episode, Tom Stuart shows us the benefits of using enumerators as immutable collections.

\r\n\r\n

I think the first time Tom Stuart blew my mind it was with his article "programming with nothing", which showed how to derive an entire programming system---including numbers and control flow operators---using nothing but Ruby procs taking single arguments. He then went on to write the book "Understanding Computation", which I highly recommend.

\r\n\r\n

Today he's out to blow minds once again, with a fascinating exploration of how we can use Ruby's enumerators to expose and operate on immutable collections. Enjoy!

\r\n\r\n

Show Notes

\r\n\r\n\r\n\r\n
\r\n

Immutability is the default in some functional programming languages. But in Ruby most objects are mutable, and that’s by design, because mutability is baked into the way most of us think about object-oriented programming.

\r\n\r\n

Sometimes it’s useful for values to be immutable. Knowing that an object won’t change can make our code easier to reason about, and shared mutable state can be hard to get right in general, even in single-threaded programs.

\r\n\r\n

When our objects are mutable, we always need to be aware of the possibility of them changing. To give one example: when we pass an argument into a method, it can be changed without us realising. Here the string 'cherry' is being modified in-place by the #print_pie method:

\r\n\r\n
\r\ndef
        print_pie(filling)\r\n  puts filling << ' pie'\r\nend\r\n\r\nfruit
        = 'cherry'\r\nprint_pie(fruit)\r\nfruit # => "cherry pie"
\r\n\r\n

If we want to be sure a value won’t get mutated, we can clone it and pass the clone around instead. Now the original value remains unchanged:

\r\n\r\n
\r\nfruit
        = 'cherry'\r\ncloned_fruit = fruit.clone\r\nprint_pie(cloned_fruit)\r\nfruit
        # => "cherry"
\r\n\r\n

Or we can freeze our value, and then we’ll get an exception if anything tries to modify it:

\r\n\r\n
\r\nfruit
        = 'cherry'\r\nfrozen_fruit = fruit.freeze\r\nprint_pie(frozen_fruit)\r\nfruit
        # =>\r\n# ~> -:2:in `print_pie': can't modify frozen String
        (RuntimeError)\r\n# ~>  from -:7:in `<main>'
\r\n\r\n

In this episode we’re going to try a different way of making objects immutable in Ruby — specifically, a way of making immutable collections.

\r\n\r\n

The simplest way to make an immutable collection is to call #freeze on an existing array. As we just saw, this will disable all the methods that allow the array to be changed:

\r\n\r\n
\r\nfruits = %w(apple banana cherry damson
        elderberry)\r\nfruits.freeze\r\nfruits << 'fig' # =>\r\n#
        ~> -:3:in `<main>': can't modify frozen Array (RuntimeError)
\r\n\r\n

But there’s another way. We can create a more naturally immutable collection by using an enumerator, because out of the box an enumerator only supports reading from a collection, not modifying it, so we don’t need to disable anything to get the behaviour we want.

\r\n\r\n

One option is to expose an existing mutable collection through an enumerator by using the #to_enum method. The resulting enumerator provides an interface that lets us iterate over the underlying collection but doesn’t give us a way to modify it:

\r\n\r\n
\r\nfruits
        = %w(apple banana cherry damson elderberry).to_enum\r\nfruits.entries # =>
        ["apple", "banana", "cherry", "damson",
        "elderberry"]\r\nfruits << 'fig' # =>\r\n# ~>
        -:3:in `<main>': undefined method `<<' for #<Enumerator>
        (NoMethodError)
\r\n\r\n

Alternatively, we can make an enumerator from scratch by generating its contents with a block. Here we yield a series of strings inside the block, and those strings become the contents of the collection:

\r\n\r\n
\r\nfruits
        = Enumerator.new do |yielder|\r\n  yielder.yield 'apple'\r\n  yielder.yield
        'banana'\r\n  yielder.yield 'cherry'\r\n  yielder.yield 'damson'\r\n
        \ yielder.yield 'elderberry'\r\nend\r\n\r\nfruits.entries # =>
        ["apple", "banana", "cherry", "damson",
        "elderberry"]
\r\n\r\n

It’s more obvious why this collection must be immutable. There’s not even any underlying data structure to modify; its contents are being generated on the fly by an unchanging block of Ruby code.

\r\n\r\n
\r\nfruits << 'fig'\r\n# ~>
        -:10:in `<main>': undefined method `<<' for #<Enumerator:
        #<Enumerator::Generator>:each> (NoMethodError)
\r\n\r\n

So we can base an enumerator on an existing array or create it from scratch with a block. Either way, the structure of the collection can’t be modified through the enumerator, but we can still implement operations that create a new collection by adding, removing, reordering or otherwise changing the contents of this one.

\r\n\r\n

The Enumerator class includes the Enumerable module, so we can do all the usual Enumerable stuff with an immutable collection, like mapping a block over it, filtering it and so on.

\r\n\r\n
\r\nfruits.map(&:upcase)
        # => ["APPLE", "BANANA", "CHERRY", "DAMSON",
        "ELDERBERRY"]\r\nfruits.select { |fruit| fruit.length == 6 } # =>
        ["banana", "cherry", "damson"]
\r\n\r\n

But note that those operations return a new mutable array, not another immutable collection represented as an enumerator:

\r\n\r\n
\r\nfruits.class
        # => Enumerator\r\nfruits.map(&:upcase).class # => Array\r\nfruits.select
        { |fruit| fruit.length == 6 }.class # => Array
\r\n\r\n

More generally, we can make a modified copy of an immutable collection by writing a new enumerator that iterates over it and yields different values. Here’s how to add an element:

\r\n\r\n
\r\nmore_fruits = Enumerator.new do |yielder|\r\n
        \ fruits.each do |fruit|\r\n    yielder.yield fruit\r\n  end\r\n\r\n  yielder.yield
        'fig'\r\nend
\r\n\r\n

This new enumerator iterates over the old one, yielding every value it finds, and afterwards yields an extra value. As a result, it behaves like the old collection with an extra element appended:

\r\n\r\n
\r\nmore_fruits.entries
        # => ["apple", "banana", "cherry", "damson",
        "elderberry"]\r\nfruits.include?('fig') # => false\r\nmore_fruits.include?('fig')
        # => true
\r\n\r\n

We can use a similar technique to remove an element:

\r\n\r\n
\r\nfewer_fruits
        = Enumerator.new do |yielder|\r\n  fruits.each do |fruit|\r\n    yielder.yield
        fruit unless fruit == 'cherry'\r\n  end\r\nend
\r\n\r\n

This enumerator iterates over the old one and yields all of its elements except one. So, it behaves like the old collection with an element removed:

\r\n\r\n
\r\nfewer_fruits.entries
        # => ["apple", "banana", "damson", "elderberry"]\r\nfruits.include?('cherry')
        # => true\r\nfewer_fruits.include?('cherry') # => false
\r\n\r\n

Enumerators are lazy, so this technique also works on infinite collections. For example, here’s an infinite collection of even numbers:

\r\n\r\n
\r\neven_numbers
        = Enumerator.new do |yielder|\r\n  n = 2\r\n\r\n  loop do\r\n    yielder.yield
        n\r\n    n += 2\r\n  end\r\nend
\r\n\r\n

We can take as many elements as we like from this collection, as long as we don’t try to take all of them. The enumerator will generate more elements as they’re needed:

\r\n\r\n
\r\neven_numbers.next
        # => 2\r\neven_numbers.next # => 4\r\neven_numbers.next # => 6\r\neven_numbers.next
        # => 8\r\neven_numbers.next # => 10\r\n\r\neven_numbers.take 10 # =>
        [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
\r\n\r\n

By making a new enumerator that wraps this collection and yields different values, we can make a modified copy. Here’s a version that includes the unlucky number thirteen at the appropriate position:

\r\n\r\n
\r\neven_or_unlucky_numbers = Enumerator.new
        do |yielder|\r\n  even_numbers.each do |n|\r\n    yielder.yield n\r\n    yielder.yield
        13 if n == 12\r\n  end\r\nend\r\n\r\neven_or_unlucky_numbers.next # =>
        2\r\neven_or_unlucky_numbers.next # => 4\r\neven_or_unlucky_numbers.next
        # => 6\r\neven_or_unlucky_numbers.next # => 8\r\neven_or_unlucky_numbers.next
        # => 10\r\n\r\neven_or_unlucky_numbers.take 10 # => [2, 4, 6, 8, 10,
        12, 13, 14, 16, 18]
\r\n\r\n

Another advantage of using enumerators instead of calling #clone or #freeze is that those methods are shallow: they protect the structure of the collection at the top level, but not any of the objects inside it.

\r\n\r\n

So cloning an array doesn’t prevent modification of the string objects it contains, and those objects are shared between the original collection and its clone. Here we can see that the original fruits array ends up containing the string 'pineapple' even though it looks like we’re only modifying the clone:

\r\n\r\n
\r\nfruits
        = %w(apple banana cherry damson elderberry)\r\ncloned_fruits = fruits.clone\r\ncloned_fruits.first.prepend
        'pine'\r\nfruits # => ["pineapple", "banana",
        "cherry", "damson", "elderberry"]
\r\n\r\n

Similarly, freezing the array doesn’t make the strings themselves frozen, so there’s nothing to stop us modifying them in-place:

\r\n\r\n
\r\nfruits = %w(apple
        banana cherry damson elderberry)\r\nfrozen_fruits = fruits.freeze\r\nfrozen_fruits.first.prepend
        'pine'\r\nfruits # => ["pineapple", "banana",
        "cherry", "damson", "elderberry"]
\r\n\r\n

But an enumerator backed by code instead of a concrete data structure is regenerated every time we iterate over it, so even if its generated contents get mutated, we can arrange for the block to provide fresh copies next time we look. In this case, the string literals inside the block will create new string objects every time they’re evaluated, so even if we pull one out and mutate it, the mutated copy won’t show up next time we iterate over the collection:

\r\n\r\n
\r\nfruits
        = Enumerator.new do |yielder|\r\n  yielder.yield 'apple'\r\n  yielder.yield
        'banana'\r\n  yielder.yield 'cherry'\r\n  yielder.yield 'damson'\r\n
        \ yielder.yield 'elderberry'\r\nend\r\n\r\nfruits.first.prepend 'pine'\r\nfruits.entries
        # => ["apple", "banana", "cherry", "damson",
        "elderberry"]
\r\n\r\n

And that’s how to use enumerators to build immutable collections. Thanks for having me as your guest chef today, and happy hacking!

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-43f1e1e1bc7ab24bb5a29fd540af32133a313ed3\n \ Thu, 11 Dec 2014 09:00:00 -0500\n \n In today's special guest episode, Tom Stuart shows us the benefits of using enumerators as immutable collections.\n \n \
\n \n <![CDATA[262 Advanced Next]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=647\n \ \n

Advanced Next

\r\n\r\n

In episode #260 we introduced the next block control keyword. We saw how it can be used to skip forward when iterating over lists.

\r\n\r\n

As we ended that episode, we were looking at what happens when we use next in a mapping context. We mapped over a list of strings. When we skipped empty strings using next, this produced nil values in the resulting output array.

\r\n\r\n
\r\nobjects
        = ["house", "mouse", "", "mush", "",\r\n
        \          "little old lady whispering 'hush'"]\r\n\r\nresult
        = objects.map do |o|\r\n  next if o.empty?\r\n  "Goodnight, #{o}"\r\nend\r\n\r\nresult\r\n#
        => ["Goodnight, house",\r\n#     "Goodnight, mouse",\r\n#
        \    nil,\r\n#     "Goodnight, mush",\r\n#     nil,\r\n#     "Goodnight,
        little old lady whispering 'hush'"]\r\n
\r\n\r\n

But what if we instead want to substitute a special string whenever the input is blank? Do we have to switch over to using an if/else statement?

\r\n\r\n

As a matter of fact, we can accomplish this goal with next as well. Let's say we just want to return the string "Goodnight, Moon" wherever the input data is empty. By supplying an argument to next, we accomplish exactly that.

\r\n\r\n
\r\nobjects = ["house", "mouse",
        "", "mush", "",\r\n           "little old
        lady whispering 'hush'"]\r\n\r\nresult = objects.map do |o|\r\n
        \ next "Goodnight, Moon" if o.empty?\r\n  "Goodnight, #{o}"\r\nend\r\n\r\nresult.compact\r\n#
        => ["Goodnight, house",\r\n#     "Goodnight, mouse",\r\n#
        \    "Goodnight, Moon",\r\n#     "Goodnight, mush",\r\n#
        \    "Goodnight, Moon",\r\n#     "Goodnight, little old lady
        whispering 'hush'"]\r\n
\r\n\r\n

What we see here is that the argument to next becomes the return value of the current invocation of a block.

\r\n\r\n

Let's look at a more concrete example of using next with an argument. We have a list of filenames, which we want to winnow down using #select. It's a long list, and it's inefficient to iterate over it multiple times. So we want to get all of our rules for inclusion or exclusion in one place. To do that, we use a series of next statements.

\r\n\r\n

The first statement checks that the filename represents an actual file, rather than a directory or a pipe or some other special entity. Since there is no point performing more checks if it's not a regular file, we skip the remaining checks using next with a false argument. This will cause the block to return false, telling #select that the current filename should not be included in the results.

\r\n\r\n

Next we check that the file is readable by the current user, and skip forwards if it is not.

\r\n\r\n

The next check is a little different. It identifies a file with a name that doesn't match the same naming pattern that all the other files have. We happen to know that we want to include that specific file, so we invoke next with a true argument. This skips all remaining tests and tells #select to go ahead and include this entry in the results.

\r\n\r\n

Next is a test intended to exclude zero-length files. And then there is a final test that includes files matching a particular naming scheme.

\r\n\r\n
\r\nDir["../**/*.mp4"].select { |f|\r\n
        \ next false unless File.file?(f)\r\n  next false unless File.readable?(f)\r\n
        \ next true if f =~ /078b-java-dregs\\.mp4/\r\n  next false if File.size(f)
        == 0\r\n  next true if File.basename(f) =~ /^\\d\\d\\d-/\r\n}\r\n
\r\n\r\n

This isn't the only way we could have written this code. We also could have structured it as a single chained boolean expression. But I find that boolean expressions tend to become harder to read the bigger they are, especially when they involve a lot of negation. I like how each line in this block is a self-contained rule which could be safely removed by deleting a single line.

\r\n\r\n

I also like the fact that if we wanted to, we could step through these rules one by one in a debugger. That's not always the case with chained boolean expressions.

\r\n\r\n

So far we've been discussing iteration. It's easy to talk about keywords like next and break and redo as if they are loop control operations. Partly because that is the context that they are commonly found in, and partly because it's easier to understand them by analogy to other languages that have dedicated loop-control operations.

\r\n\r\n

It's important to understand, however, that in Ruby these keywords aren't really loop control operations. They are something more generalized: they are block-control operators. They work anywhere that blocks are passed and invoked, regardless if there is anything like iteration going on.

\r\n\r\n

To hopefully make this principle clear, let's write a little method which accepts a block. It yields to the block twice, logging before and after each yield.

\r\n\r\n

There is no looping or iterating over a list going on here, just two yields. Let's call this method, passing it a block. The block will log, invoke next, and then log again.

\r\n\r\n

Finally, we log the moment the method returns.

\r\n\r\n

When we execute this code, we can see that the block is, in fact, executed twice, just as intended. Each time, it invokes next, which causes control to be passed back to the yieldtwice method before the block can do anything else. The last line of the block is never reached. Again we can see how this behaves like an early return, except for a block instead of a method.

\r\n\r\n
\r\ndef
        yieldtwice\r\n  puts "Before first yield"\r\n  yield\r\n  puts "Before
        second yield"\r\n  yield\r\n  puts "After last yield"\r\nend\r\n\r\nyieldtwice
        do\r\n  puts "About to invoke next"\r\n  next\r\n  puts "Can
        never get here"\r\nend\r\nputs "After method call"\r\n\r\n#
        >> Before first yield\r\n# >> About to invoke next\r\n# >>
        Before second yield\r\n# >> About to invoke next\r\n# >> After
        last yield\r\n# >> After method call\r\n
\r\n\r\n

Now let's take one last look at the difference between next and break. Instead of invoking next in the block, we'll invoke break.

\r\n\r\n
\r\ndef yieldtwice\r\n
        \ puts "Before first yield"\r\n  yield\r\n  puts "Before second
        yield"\r\n  yield\r\n  puts "After last yield"\r\nend\r\n\r\nyieldtwice
        do\r\n  puts "About to invoke break"\r\n  break\r\n  puts "Can
        never get here"\r\nend\r\nputs "After method call"\r\n\r\n#
        >> Before first yield\r\n# >> About to invoke break\r\n# >>
        After method call\r\n
\r\n\r\n

This time, we can see that execution only gets as far as the first break before the whole method exits. Unlike next, break doesn't just bring a yield to an early end. It cancels the execution of the whole method that triggered the yield. That is, it forces an early return of the call to yieldtwice.

\r\n\r\n

In working through these two examples, we can begin to see how break and next can effectively function as iteration control operators, because in Ruby iteration is always expressed using blocks. But in fact they don't know anything about loops or iteration; they are all about controlling the execution of blocks and the methods that yield to those blocks.

\r\n\r\n

And that's probably enough to digest in one day. Happy hacking!

\n
\n

Attached Files

\n ]]>
\n \ dpd-7123d1c772f19b2cc08359c6edbacafe620c07cd\n \ Mon, 08 Dec 2014 09:00:00 -0500\n \n Continuing our discussion of the `next` keyword, today we'll look at some advanced usages.\n \ \n \
\n \n <![CDATA[261 Next]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=644\n \ \n
\r\n

Next

\r\n\r\n

In the last episode, #258, I made passing mention of the Ruby next keyword. It occurred to me that not everyone might be familiar with this keyword. Just like redo, it's one of those features where you can spend years writing perfectly good code without knowing about it. But once you do know about it, you can use it to save some effort or make certain idioms more expressive. And even if you use next every day, in the episode after this I'll be covering some advanced uses that might just teach you something new.

\r\n\r\n

At its most basic, next works like the continue loop control keyword found in languages like Java or C. For instance, let's say we have a list of objects we need to say goodnight to. Unfortunately, some blank strings have snuck into the list. As a result, our output is messed up.

\r\n\r\n
\r\nobjects = ["house",
        "mouse", "", "mush", "",\r\n           "little
        old lady whispering 'hush'"]\r\n\r\nobjects.each do |o|\r\n  puts
        "Goodnight, #{o}"\r\nend\r\n\r\n# >> Goodnight, house\r\n#
        >> Goodnight, mouse\r\n# >> Goodnight,\r\n# >> Goodnight,
        mush\r\n# >> Goodnight,\r\n# >> Goodnight, little old lady whispering
        'hush'\r\n
\r\n\r\n

Let's assume that we can't simply remove the blank strings from the list. One way to deal with them would be to put a conditional statement around the puts line. It will only execute the puts if the string is non-empty.

\r\n\r\n
\r\nobjects = ["house",
        "mouse", "", "mush", "",\r\n           "little
        old lady whispering 'hush'"]\r\n\r\nobjects.each do |o|\r\n  unless
        o.empty?\r\n    puts "Goodnight, #{o}"\r\n  end\r\nend\r\n\r\n#
        >> Goodnight, house\r\n# >> Goodnight, mouse\r\n# >> Goodnight,
        mush\r\n# >> Goodnight, little old lady whispering 'hush'\r\n
\r\n\r\n

This works fine. But this style of code always bothers me a little bit. By pushing the actual "meat" of the block down inside a guarding conditional, we've given this junk-handling code greater prominence than I feel it deserves.

\r\n\r\n

If this were a method we were writing, I would prefer to write it like this, with a guard clause which returns early if the input is garbage. Once we are past the guard clause, we can mentally dispense with that case entirely. There is no lingering context to keep us thinking about it.

\r\n\r\n
\r\ndef say_goodnight_to(object)\r\n  return if object.empty?\r\n
        \ puts "Goodnight, #{object}"\r\nend\r\n
\r\n\r\n

Obviously, we can't use return inside an #each loop, because that would abort the iteration and force the whole method to return. Instead, we can use the next keyword to tell Ruby to skip forward to the next iteration.

\r\n\r\n
\r\nobjects
        = ["house", "mouse", "", "mush", "",\r\n
        \          "little old lady whispering 'hush'"]\r\n\r\nobjects.each
        do |o|\r\n  next if o.empty?\r\n  puts "Goodnight, #{o}"\r\nend\r\n\r\n#
        >> Goodnight, house\r\n# >> Goodnight, mouse\r\n# >> Goodnight,
        mush\r\n# >> Goodnight, little old lady whispering 'hush'\r\n
\r\n\r\n

When we run this version, it leaves out the empty entries in the list. In effect, next gives us a way to add guard clauses for blocks.

\r\n\r\n

You might recall that back in episodes #70 and #71 we introduced the break keyword. If you haven't used the break and next keywords much, you might be a little unclear in how they differ from each other. In order to clarify that difference, let's change our next to a break and run the code again.

\r\n\r\n
\r\nobjects
        = ["house", "mouse", "", "mush", "",\r\n
        \          "little old lady whispering 'hush'"]\r\n\r\nobjects.each
        do |o|\r\n  break if o.empty?\r\n  puts "Goodnight, #{o}"\r\nend\r\n\r\n#
        >> Goodnight, house\r\n# >> Goodnight, mouse\r\n
\r\n\r\n

This time, instead of just skipping the blank entries, the iteration over the array halted completely at the first blank string. That's the difference in a nutshell: next skips to the next iteration, whereas break breaks completely out of the method that the block has been passed into. In this case, that method is #each.

\r\n\r\n

Thus far, we've been coding purely in imperative terms. We're iterating over the elements of an array, printing some of them to standard out, and ignoring any return values.

\r\n\r\n

Let's switch that around now, and look at things from a functional perspective. We'll change the #each to a #map, and assign the result to a variable. For now, we'll get rid of the next. And we'll lose the puts and just return the constructed string from each block invocation.

\r\n\r\n
\r\nobjects
        = ["house", "mouse", "", "mush", "",\r\n
        \          "little old lady whispering 'hush'"]\r\n\r\nresult
        = objects.map do |o|\r\n  "Goodnight, #{o}"\r\nend\r\n\r\nresult\r\n#
        => ["Goodnight, house",\r\n#     "Goodnight, mouse",\r\n#
        \    "Goodnight, ",\r\n#     "Goodnight, mush",\r\n#     "Goodnight,
        ",\r\n#     "Goodnight, little old lady whispering 'hush'"]\r\n
\r\n\r\n

The result is an array of strings, including some junk strings. Let's see what happens when we reintroduce our guard clause using next.

\r\n\r\n
\r\nobjects
        = ["house", "mouse", "", "mush", "",\r\n
        \          "little old lady whispering 'hush'"]\r\n\r\nresult
        = objects.map do |o|\r\n  next if o.empty?\r\n  "Goodnight, #{o}"\r\nend\r\n\r\nresult\r\n#
        => ["Goodnight, house",\r\n#     "Goodnight, mouse",\r\n#
        \    nil,\r\n#     "Goodnight, mush",\r\n#     nil,\r\n#     "Goodnight,
        little old lady whispering 'hush'"]\r\n
\r\n\r\n

This time, where the input data had a blank string, we now have nil values. We now know that the return value of a block invocation which is skipped by next is nil.

\r\n\r\n

This is a handy behavior, since it means we can then use compact to filter out all the nil entries.

\r\n\r\n
\r\nobjects
        = ["house", "mouse", "", "mush", "",\r\n
        \          "little old lady whispering 'hush'"]\r\n\r\nresult
        = objects.map do |o|\r\n  next if o.empty?\r\n  "Goodnight, #{o}"\r\nend\r\n\r\nresult.compact\r\n#
        => ["Goodnight, house",\r\n#     "Goodnight, mouse",\r\n#
        \    "Goodnight, mush",\r\n#     "Goodnight, little old lady
        whispering 'hush'"]\r\n
\r\n\r\n

We are not done. I have a lot more to show you about the next keyword. But in the interests of introducing just one idea at a time, I'm going to end for now and pick up the subject again in the next episode. If everything we've seen so far was old hat to you, be patient: the next installment will dig a lot deeper. Until then, happy hacking!

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-760b3bfc75e1c9464ac270296e61840c31e1a246\n \ Thu, 04 Dec 2014 09:00:00 -0500\n \n Today's episode introduces the next keyword, and how we can use it to control the result of loop iterations.\n \ \n \
\n \n <![CDATA[260 Capture Groups with Nell Shamrell]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=642\n \ \n

When I think about regular expressions in Ruby, I think of Nell Shamrell. Nell has put a lot of study into regular expressions—how to write them, how to optimize them, and how they are implemented under the covers. She's given some great talks on this subject. I've put some links in the show notes.

\r\n

Today, she has agreed to step into the RubyTapas kitchen and give us an introduction on using regex capture groups. If you've ever looked at some of the more advanced regex tricks on this show and felt a little lost, this episode should fill in some of the blanks.

\r\n

Notes:

\r\n\r\n
\r\n

Today I'd like to talk to you about using regular expressions capture groups in Ruby. Capture groups are a way to capture certain parts of my regular expression's match so I can use them later in my regular expression or later code outside of the regex. Let's say I want to make a regex to scan a string looking for basic urls. Here's an example string.

\r\n
\r\n
        \    \"site www.rubytapas.com\" \r\n  
\r\n
\r\n

Next, I'm going to create a basic regular expression to find the url in that string. I want this regular expresion to match www followed by a literal dot. Notice that I had to escape the dot using a backslash.This tells the regular expression engine to treat this a literal dot not as a dot metacharacter which has different meaning. Followed by any word character appearing one or more times, followed by another dot, followed by any word character appearing one or more times.

\r\n
\r\n
    
        \"site www.rubytapas.com\" \r\n     /www.\\.\\w+\\.\\w+/
        \r\n  
\r\n
\r\n

Now, this is a somewhat contrived example and there very likely are more efficient regular expressions to match urls, but this one illustrates the points I want to make.

\r\n

So let's say I want to capture the domain name for use later outside the regular expression. In this string the domain name would be \"rubytapas\".

\r\n
\r\n
     # Domain
        name: rubytapas \r\n     \"site www.rubytapas.com\"
        \r\n     /www.\\.\\w+\\.\\w+/ \r\n
        \ 
\r\n
\r\n

Then I also want to capture the top level domain for use later in the program. In the case of this string, it would be \"com\".

\r\n
\r\n
    
        # Top Level domain: com # Domain name: rubytapas \r\n    
        \"site www.rubytapas.com\" \r\n     /www.\\.\\w+\\.\\w+/
        \r\n  
\r\n
\r\n

To capture that domain name, I'm going to enclose the section of the regex meant to match the domain name in parentheses.

\r\n
\r\n
    
        # Top Level domain: com # Domain name: rubytapas \r\n    
        \"site www.rubytapas.com\" \r\n     /www.\\(.\\w+)\\.\\w+/
        \r\n  
\r\n
\r\n

Next I'll do the same thing for the section that's meant to match the top level domain.

\r\n
\r\n
     # Top Level
        domain: com # Domain name: rubytapas \r\n    
        \"site www.rubytapas.com\" \r\n     /www.\\(.\\w+)\\.(\\w+)/
        \r\n  
\r\n
\r\n

So let's try running this in Ruby. I'm first going to assign my string to a variable, we'll just call it string.

\r\n
\r\n
    
        # Top Level domain: com # Domain name: rubytapas \r\n    string = \"site www.rubytapas.com\"\r\n\r\n    /www.\\(.\\w+)\\.(\\w+)/\r\n
        \ 
\r\n
\r\n

Then I'm going to assign my regex to a variable called regex.

\r\n
\r\n
        \    # Top Level domain: com # Domain
        name: rubytapas \r\n    string = \"site
        www.rubytapas.com\"\r\n    regex = /www.\\(.\\w+)\\.(\\w+)/\r\n
        \ 
\r\n
\r\n

I'm first going to match my regex against this string using the equals sign tilde operator. And I'm going to put my regex on one side of this and my string on the other side. This tells Ruby \"look in the string for an of it that matches this regex pattern.\"

\r\n
\r\n
     # Top Level
        domain: com # Domain name: rubytapas \r\n    string = \"site
        www.rubytapas.com\"\r\n    regex = /www.\\(.\\w+)\\.(\\w+)/\r\n\r\n
        \   regex =~ string\r\n  
\r\n
\r\n

And Ruby's going to return back \"5.\" That \"5\" means the part of the string that matches the regex begins on the fifth character of the string, the character at index 5.

\r\n
\r\n
    
        # Top Level domain: com # Domain name: rubytapas \r\n    string = \"site www.rubytapas.com\"\r\n    regex = /www.\\(.\\w+)\\.(\\w+)/\r\n\r\n    regex =~ string
        # => 5\r\n  
\r\n
\r\n

Now, knowing where my match began is useful, but Ruby offers a few different ways I can get more information about my match. First, let's say I want to see exactly what my match is. One way to do this in Ruby is to type $~. That returns an instance of Ruby's matchdata class for my match. We'll go a little more into matchdata in just a little bit. Notice that it contains the entire part of the string that matched my regex and the results of the two capture groups.

\r\n
$~ # => #
\r\n

Now I personally find the $~ to by cryptic and not very readable. Fortunately, Ruby has another way to see what my last match was. And that is through using Regexp - impossible to pronounce but important to know, it's the regular expressions class in Ruby - and I'm going to call last_match on that class. And I get back that same matchdata object for our last match. You can see it also shows the results from my capture groups - those subexpressions within my larger regular expression.

\r\n
Regexp.last_match # => #
\r\n

Now what about when I want to look at those capture groups individually and maybe use them later in my code? I can view the first capture group by typing in $1, in that case it returns \"rubytapas\".

\r\n
$1 # => \"rubytapas\"
\r\n

Likewise, I can view the second capture group by typing in $2, which returns \"com\".

\r\n
$2 # => \"com\"
\r\n

Notice that my first capture group is referenced by one, not by zero. If I were to type in zero, I would get back the name of the program that ran the match.

\r\n

Along with looking at these capture groups, I can also use them later in the program. Let's try interpolating these two capture groups into a string. In my string I'm going to type in \"Domain name: \", then interpolate my first capture group, followed by \"Top Level Domain: \" then I'll interpolate my second capture group. And this interpolates those two capture groups into my string.

\r\n
\"Domain name: #{$1} Top Level Domain: #{$2}\"
\r\n

And this interpolates those two capture groups into my string.

\r\n
\"Domain name: #{$1} Top Level Domain: #{$2}\" # => \"Domain name: rubytapas Top Level Domain: com\"
\r\n

Now using numbers like this does work, but again it's somewhat cryptic and a little hard to read. A perhaps clearer way to handle capture groups is through Ruby's matchdata class. Working with capture groups is one of the places the matchdata class is most useful.

\r\n

So let's create a matchdata object using the match method. I'm going to assign it to a variable called \"my_match.\" And I'm going to call match on my regex and pass it in my string.

\r\n
my_match = regex.match(string)
\r\n

And I get back that instance of the matchdata class with the full string and the capture groups.

\r\n
my_match = regex.match(string) # => #
\r\n

I can then also access the results of my capture groups similar to how I would access the elements of an array. If I type in my_match[1], I'll get back the result of my first capture group.

\r\n
my_match[1] # => \"rubytapas\"
\r\n

Likewise, if I type in my_match[2], I'll get back the result of my second capture group.

\r\n
my_match[2] # => \"com\"
\r\n

Again, note that the first capture group begins at 1, not at 0 like an array. If I were to type in my_match[0], I would get back the entire string that matched the larger regular expression.

\r\n
my_match[0] # => \"www.rubytapas.com\"
\r\n

And that is an intro to using capture groups in your Ruby regular expressions. Happy hacking!

\n
\n \

Attached Files

\n ]]>
\n \ dpd-0fdb1f19ce6f6cbf4159579d517caf847304bdd3\n \ Mon, 01 Dec 2014 09:00:00 -0500\n \n Guest chef Nell Shamrell shares an intro to working with regex capture groups in this episode.\n \n \
\n \n <![CDATA[259 Redo]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=639\n \ \n

Let's say we have a list of files we want to download. Each file could be retrieved from any one of three different mirror sites. This is important to know, because the mirrors aren't always reliable. Sometimes they are overloaded with clients, or go down for maintenance. If we get an error response from one mirror, we can try the same request on a different one instead of giving up altogether.

\r\n

For this example, I've simulated the unreliability of the mirrors using WebMock. Every fourth request to any of the mirror hosts will result in a 502 \"Bad Gateway\" response.

\r\n
\r\n
FILES = %W[file1
        file2 file3 file4 file5 file6 file7 file8 file9]\r\n\r\nMIRRORS
        = %W[a.example.org b.example.org c.example.org]\r\n\r\nrequire
        \"net/http\"\r\nrequire \"webmock\"\r\n\r\ninclude
        WebMock::API\r\n\r\nrequest_count
        = 0\r\nerr = {status: 502}\r\nok  = {status: 200, body:
        \"OK!\"}\r\nstub_request(:get,
        /.*\\.example\\.org/)\r\n  .to_return(->(r){
        request_count += 1; request_count % 4 == 0 ? err : ok })\r\n
\r\n
\r\n

In order to download the files, we can loop over the list using #each. To begin with, we'll just use the first mirror and ignore the others. We'll build a URI by combining the mirror and the filename, and we'll log the URI. Then we'll make an HTTP request.

\r\n

We then make a decision based on the response status. If it's 200, all is well and we log a successful download. But if it's some other status code, we note the failure and terminate the loop with break.

\r\n

We run this code, and see that it makes four requests before ending with an error.

\r\n
\r\n
require \"./setup\"\r\n\r\nFILES.each do
        |file|\r\n  mirror = MIRRORS.first\r\n  uri
        = URI(\"http://#{mirror}/#{file}\")\r\n
        \ puts \"Requesting
        #{uri}\"\r\n  result = Net::HTTP.get_response(uri)\r\n  if
        result.code == \"200\"\r\n    puts \"Success!\"\r\n  else\r\n
        \   puts \"Error
        #{result.code}\"\r\n    break\r\n
        \ end\r\nend\r\n\r\n# >>
        Requesting http://a.example.org/file1\r\n#
        >> Success!\r\n#
        >> Requesting http://a.example.org/file2\r\n# >>
        Success!\r\n# >>
        Requesting http://a.example.org/file3\r\n#
        >> Success!\r\n#
        >> Requesting http://a.example.org/file4\r\n# >>
        Error 502\r\n
\r\n
\r\n

In order to make this code robust in the face of failures, we want it to switch mirrors when there is a network problem. But it's not enough to change mirrors and then go around the loop again, because this would mean skipping the file that failed entirely.

\r\n

A typical approach to a problem like this would be to add an inner loop which re-tried downloading the same filename with successive mirrors. But in Ruby, we can avoid the need to write a second loop.

\r\n

Instead, we add just two lines of code. First, when a request fails we shift the mirrors array by one, which has the effect of putting the next mirror at the head of the list. Then, we invoke the redo keyword.

\r\n

redo is a special block control flow operator in Ruby. It causes execution to be thrown back up to the beginning of the current block. But unlike the next keyword, the block does not advance to the next iteration in a sequence. Instead, it is restarted with the same block argument as the last time around.

\r\n

We can run the code and see the upshot of this behavior. After four requests, there is an error. But instead of terminating, the request is repeated with the same file but a new mirror. Then there are another four requests, followed by another mirror switch, and so on.

\r\n
\r\n
require \"./setup\"\r\n\r\nFILES.each do
        |file|\r\n  mirror = MIRRORS.first\r\n  uri
        = URI(\"http://#{mirror}/#{file}\")\r\n
        \ puts \"Requesting
        #{uri}\"\r\n  result = Net::HTTP.get_response(uri)\r\n  if
        result.code == \"200\"\r\n    puts \"Success!\"\r\n  else\r\n
        \   puts \"Error
        #{result.code}; switching mirrors\"\r\n
        \   MIRRORS.shift\r\n    redo\r\n
        \ end\r\nend\r\n\r\n# >>
        Requesting http://a.example.org/file1\r\n#
        >> Success!\r\n#
        >> Requesting http://a.example.org/file2\r\n# >>
        Success!\r\n# >>
        Requesting http://a.example.org/file3\r\n#
        >> Success!\r\n#
        >> Requesting http://a.example.org/file4\r\n# >>
        Error 502; switching mirrors\r\n#
        >> Requesting http://b.example.org/file4\r\n# >>
        Success!\r\n# >>
        Requesting http://b.example.org/file5\r\n#
        >> Success!\r\n#
        >> Requesting http://b.example.org/file6\r\n# >>
        Success!\r\n# >>
        Requesting http://b.example.org/file7\r\n#
        >> Error 502; switching mirrors\r\n# >>
        Requesting http://c.example.org/file7\r\n#
        >> Success!\r\n#
        >> Requesting http://c.example.org/file8\r\n# >>
        Success!\r\n# >>
        Requesting http://c.example.org/file9\r\n#
        >> Success!\r\n
\r\n
\r\n

redo is very similar to the retry keyword we discussed in episode #257. The difference is that retry is for exception rescue clauses, and redo is for blocks.

\r\n

Like with retry, we have to take care with redo that we always move the program state forward in some way before redoing. Otherwise, we risk getting stuck in an infinite loop. In today's example, we're ensuring this by shifting a mirror off of the mirror list before every redo. Eventually, with enough failures this will cause the code to run out of mirrors, a scenario we haven't handled yet.

\r\n

By using redo, we are able to \"try, try again\" at a given loop iteration, without adding the code complexity of an inner loop. And that's it for today. Happy hacking!

\n
\n \

Attached Files

\n ]]>
\n \ dpd-6e126140304623a9b1dd362d0173e573ce502ca7\n \ Thu, 27 Nov 2014 12:17:00 -0500\n \n In today's episode, we use a Ruby keyword to make a loop more flexible in the face of failures.\n \ \n \
\n \n <![CDATA[258 Bitwise Operations with Peter Cooper]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=636\n \ \n

Today I am very happy to welcome into the RubyTapas kitchen guest chef Peter Cooper. Peter publishes the indispensable Ruby Weekly News, my absolute favorite way to stay up to date on everything happening in the Ruby world. He also publishes many other great newsletters on various programming topics. There are links to all these resources in the show notes.

\r\n

Today, Peter is going to be sharing with us an introduction to using Ruby's bitwise operators for manipulating numbers at the binary level. This can be a tricky topic to teach and to comprehend, but Peter has come up with some terrific visuals that I think you'll find really make these ideas clear. Enjoy!

\r\n

Show notes:

\r\n\r\n
\r\n
 
\r\n

 

\r\n

Hi, this is Peter Cooper, editor of Ruby Weekly and numerous other email newsletters, you can find me on Twitter at @peterc. It’s an honor to be here with you today.

\r\n

If you’re here solely for object oriented design, refactoring, design philosophy, or anything like that, you might want to give this video a swerve!

\r\n

So binary. Here’s a quick refresher.

\r\n

Normally we represent numbers using the digits 0 through 9, the decimal system, we all know what is.

\r\n

In the binary system we can also represent numbers, but we have just two digits, 0 and 1. Any number can be represented using 0s and 1s, just as with decimal.

\r\n

Let’s say we want to represent 0. That’s just 0. Easy.

\r\n

And 1? 1. Easy.

\r\n

What about 2? We have no digit two, but the number 2 can be represented as 10. That’s not ten, but one-zero. Instead of having tens, hundreds, thousands, and so forth as each column, we have twos, fours, eights, sixteens, thirty-twoths and so on.

\r\n

Ruby let’s us work with binary representations and also convert between binary and decimal quite easily. Let’s say we want to know what 42 is in binary. We could work it out, but let’s get Ruby to do the work.

\r\n
42.to_s(2)
\r\n

What we do is tell Ruby to convert the decimal 42 to a string that represents the binary version.

\r\n

It’s 101010 .. nothing suspicious about the meaning of life there then..!

\r\n

We can also represent numbers directly in binary in Ruby as Avdi showed you in episode 1. We prefix with 0b, like so:

\r\n
0b101010
\r\n

Note that we can also convert back from a string representation of binary to decimal with to_s:

\r\n
“101010”.to_i(2)
        \  # => 42
\r\n

The argument of 2 is just telling to_i that the representation is in base 2, a synonym for “binary”. Without it, Ruby assumes base 10, decimal, and we’d get this:

\r\n
“101010”.to_i
        \  # => 101010
\r\n

Now, Ruby lets us perform special operations upon binary representations that we call “bitwise operations”. Bitwise operations just manipulate things at the level of each bit. What’s a bit, you say?

\r\n

If you take 101010, each digit there is a bit. Each bit is the smallest unique portion of a computer’s memory, whether that’s regular memory, a register, or whatever.

\r\n

Bit is a shortened version of “binary digit” by the way, and just to tie together some terminology, eight bits is now typically called a byte, although historically a byte has had no specific length and was simply the smallest addressable unit of memory upon a particular computer architecture.

\r\n

Let’s start with some Ruby specific methods that aren’t exactly bitwise operations but get us going in the right direction.

\r\n

Let’s take our 42, and then ask Ruby to tell us what individual bits of it are set to.

\r\n
100[0]
        # => 0\r\n100[1] # => 1\r\n100[2] # => 0
\r\n

Using the square brackets method on a number in Ruby lets us “address” individual bits of that number.

\r\n

Another thing we can do is see the bit “length” of a number. That is, the minimum number of bits that would be required to represent that number. For example:

\r\n
42.bit_length # => 6
\r\n

Whereas, 255, the largest number that can be represented in 8 bits is..

\r\n
255.bit_length
        # => 8
\r\n

Note that just one digit higher, 256, we need 9 bits:

\r\n
256.bit_length
        # => 9
\r\n

As an aside, this can be used as an interesting and quick way to determine if a number is a power of two or not. Take a number x and if x’s bit length is not the same as the bit length of x - 1, it must be a power of 2, as the number of bits needed to represent it has just increased by 1:

\r\n
x = 256\r\nputs “#{x} is a power of
        2!” if x.bit_length != (x-1).bit_length
\r\n

So, now on to the true, primary bitwise operations. You may have heard of them before, they’re called AND, OR, XOR, and NOT.

\r\n

The AND operation isn’t the same as the logical and operation you might use on an if statement. Instead, it’s an operation that takes two binary digits or even complete numbers and then compares each bit in each respective position, then only applies a 1 on the output if both respective bits on the input are 1 too. This is best shown visually using what’s called a truth table which shows all combinations of inputs and the outputs they result in.

\r\n

Or in code, we can demonstrate:

\r\n
(0b101 & 0b100).to_s(2)  # => “100”
\r\n

Notice that the AND operator is just a single ampersand, unlike the logical and which is a double ampersand &&.

\r\n

The OR operation is like the AND operation except the output bit is 1 if either of the input bits is a 1.

\r\n

Or in code..

\r\n
(0b101 | 0b110).to_s(2) # =>
        “101\"\r\n(0b101 | 0b010).to_s(2) # => “111”
\r\n

XOR is again a bit like OR but with the proviso that the output bit is only 1 if one and exclusively one of the input bits is a 1. So with OR, if both input bits are 1, the output is 1. But with XOR, the output would be 0 unless a single input bit is 1 and the other is 0.

\r\n
(0b111 ^ 0b111).to_s(2)
        # => “0\"
\r\n

NOT is, in theory, the easiest, but in practice is a bit of a pain in Ruby. In theory, if you take a string of bits and flip any 1s to 0s and 0s to 1s, you’re good. The truth table is ridiculously simple.

\r\n

The problem is that due to how numbers are represented internally in Ruby and other languages, flipping all of the bits has the interesting side effect of making them negative.

\r\n
(~0b101).to_s(2)  # =>
        “-110”
\r\n

However, a compounding problem here is that Ruby isn’t really returning the internal representation of the number using to_s, as we can analyse here:

\r\n
(~0b101)[0]  #
        => 0\r\n(~0b101)[1]  # => 1\r\n(~0b101)[2]  # => 0
\r\n

This demonstrates the NOT is actually working properly, but due to the way negative numbers are stored and represented, things get complicated when it comes to rendering decimal equivalents. This could be the topic for an entire other video, however, so we will pause there.

\r\n

Before I show a quick example of practical uses for these operators, I want to quickly touch on another operation that is commonly considered a bitwise operation, but isn’t the classical sense. It’s called shifting.

\r\n

Take “101” and let’s “shift” it to the left. We can do this in Ruby with two less than signs.

\r\n
((0b101) << 1).to_s(2)  # =>
        “1010”
\r\n

What’s happened is our 101 has shifted one position to the left and a 0 has been placed in the rightmost place. We can then shift is back again, by shifting to the right.

\r\n
((0b1010)
        >> 1).to_s(2)  # => “101”
\r\n

Due to how binary is built around powers of 2, this has the interesting side effect of doubling and halving numbers. Let’s try it on decimal:

\r\n
24 <<
        1 # => 48\r\n24 << 2 # => 96 (equivalent of 24 * 2 * 2)\r\n24
        << 3 # => 192 (equivalent of 24 * 2 * 2 * 2)\r\n37 >> 1 # =>
        18
\r\n

.. because we’re working with binary, we get no decimal places on the last one, we just lop off the odd bit.

\r\n

Shifting is commonly used at the machine code level to optimise multiplications since shifting bits is a lot quicker than performing true multiplication.

\r\n

If this intrigues you, you might also look up rotation, which is a bit like shifting, except instead of digits being lost off of either end, they get looped around to the other end of the value. Essentially the bits of a value get rotated rather than just shifted.

\r\n

So how are bitwise operations useful in Ruby or even programming in general? This is mostly an exercise for you, Google “uses for bit wise operations” and you’ll actually find a lot of stuff, but here’s a quick fly through some ideas.

\r\n

If you’re doing socket programming or interacting with low level C libraries, you’ll often encounter interesting ways data has been packed using binary. For example, in a single byte, we have 8 bits, but you could represent two 4 bit numbers within that.

\r\n

Let’s say we have the numbers 6 and 9 and we want to represent those separately within a single byte. We could place one number in the lower 4 bits of the byte, and the other in the higher. But how?

\r\n

Simply saying x = 9 gets us the number 9 into the lower 4 bits, so that was easy! The 6 will take more work.

\r\n

So all we do is shift 6 left by 4 bits and add it on!

\r\n

Now what about extracting both of them? One way is to use what’s called a bitmask. What you do is mark the area you want to extract using one number then AND it with the data to pull out only the marked part.

\r\n
0b11110000 & 105  #
        => 96
\r\n

Then shift that right 4 places:

\r\n
96 >>
        4 # => 6
\r\n

And similarly for lowest 4 bits:

\r\n
0b00001111
        & 105 # => 9
\r\n

This sort of stuff is very useful to know when working with things like colour values, IP addresses, file formats, or network packets at a low level.

\r\n

A similar technique is often used in C to store simple on/off flags compactly. Let’s say we want to represent the flags of a blog post.. things like is it private or not, is it published or not, was it deleted or not?

\r\n
PRIVATE = 1\r\nPUBLISHED = 2\r\nDELETED
        = 4\r\n\r\nflags = 0
\r\n

Now, to turn on and off bits, you’d just use OR for turning bits on:

\r\n
flags |= PRIVATE\r\nflags |=
        PUBLISHED
\r\n

And then AND for checking if bits are set, a non-zero value represents true:

\r\n
flags & PRIVATE # => 1\r\nflags
        & DELETED # => 0
\r\n

We could then use XOR to turn OFF a flag:

\r\n
flags
        ^= PRIVATE\r\nflags & PRIVATE # => 0\r\nflags & PUBLISHED # =>
        1
\r\n

Indeed, XOR actually would toggle the flag on and off if you kept using it.

\r\n

Now if you thought ActiveRecord’s enums were clever, imagine having this on them!

\r\n

OK, so this is becoming a feast rather than a tapas, but if you want to keep investigating, bitwise operations are also used in things like:

\r\n
    \r\n
  • compression
  • \r\n
  • checksums
  • \r\n
  • hashing
  • \r\n
  • graphics manipulation (think about doing these operations on colour values, such as overlaying two images on top of each other)
  • \r\n
  • cryptography (you can XOR values with a key value and toggle them back and forth)
  • \r\n
  • calculating valid network addresses for a subnet
  • \r\n
  • swapping two variables without an intermediary
  • \r\n
\r\n

And more. But that’s it, so follow me @peterc, subscribe to Ruby Weekly, and goodbye and goodnight!

\n
\n \

Attached Files

\n ]]>
\n \ dpd-b58875d2b5be4e3da33839cf0f891e54a0804b32\n \ Mon, 24 Nov 2014 09:00:00 -0500\n \n Guest chef Peter Cooper drops some binary science in today's episode!\n \ \n \
\n \n <![CDATA[257 Retry]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=633\n \ \n

In the idealized world of pure programs, operations either work the first time or they don't work at all. Sadly, as soon as we start connecting our software to external services this beautiful dream starts to shatter. Servers go down, networks get overloaded. And sometimes we wind up spending as much time writing code to handle an occasional 502 \"Bad Gateway\" error as we do on the rest of the code combined.

\r\n

Let's consider some code that wraps a fictional web service. We've used webmock to simulate an unreliable connection that causes exceptions to be raised the first two times we try to make a request.

\r\n
\r\n
require \"net/http\"\r\nrequire \"webmock\"\r\ninclude
        WebMock::API\r\n\r\nstub_request(:get, \"www.example.org\")\r\n
        \ .to_raise(\"Packets devoured by rodents\")\r\n
        \ .to_raise(\"Request saw its shadow\")\r\n
        \ .to_return(body: \"OK\")\r\n
\r\n
\r\n

As a result, when we call our wrapper method, it fails with an exception.

\r\n
\r\n
require \"./setup\"\r\n\r\ndef
        make_request\r\n  result = Net::HTTP.get(URI(\"http://www.example.org\"))\r\n
        \ puts \"Success:
        #{result}\"\r\nend\r\n\r\nmake_request\r\n\r\n# ~>
        StandardError\r\n# ~> Packets devoured by rodents\r\n#
        ~>\r\n#
        ~> /home/avdi/.gem/ruby/2.1.2/gems/webmock-1.13.0/lib/webmock/response.rb:68:in `raise_error_if_any'\r\n#
        ~> /home/avdi/.gem/ruby/2.1.2/gems/webmock-1.13.0/lib/webmock/http_lib_adapters/net_http.rb:173:in `build_net_http_response'\r\n# ~>
        /home/avdi/.gem/ruby/2.1.2/gems/webmock-1.13.0/lib/webmock/http_lib_adapters/net_http.rb:83:in `request'\r\n# ~>
        /home/avdi/.rubies/ruby-2.1.2/lib/ruby/2.1.0/net/http.rb:1280:in `request_get'\r\n#
        ~> /home/avdi/.rubies/ruby-2.1.2/lib/ruby/2.1.0/net/http.rb:480:in
        `block in get_response'\r\n# ~>
        /home/avdi/.gem/ruby/2.1.2/gems/webmock-1.13.0/lib/webmock/http_lib_adapters/net_http.rb:123:in `start_without_connect'\r\n# ~>
        /home/avdi/.gem/ruby/2.1.2/gems/webmock-1.13.0/lib/webmock/http_lib_adapters/net_http.rb:150:in `start'\r\n# ~>
        /home/avdi/.rubies/ruby-2.1.2/lib/ruby/2.1.0/net/http.rb:583:in `start'\r\n# ~>
        /home/avdi/.rubies/ruby-2.1.2/lib/ruby/2.1.0/net/http.rb:478:in `get_response'\r\n#
        ~> /home/avdi/Dropbox/rubytapas/257-retry/make_request.rb:12:in
        `make_request'\r\n# ~> xmptmp-in7623Pte.rb:3:in `<main>'\r\n
\r\n
\r\n

Knowing that we are dealing with a flaky service, we'd like to update the #make_request method to try again a few times before giving up. In most programming languages this would involve writing a loop. However, in Ruby we have another option.

\r\n

In the parameters to the #make_request method, we initialize a count of tries remaining. Then, we add a rescue clause to the method. inside the clause, we first log the error. Then we decrement the tries counter. Then we check to see if there are any tries remaining.

\r\n

If there are, we invoke the retry keyword. If, however, there are no tries left, we re-raise the current exception.

\r\n

Let's see what happens when we call this method. This time, we can see that the request failed twice, and succeeded the third time.

\r\n
\r\n
require \"./setup\"\r\n\r\ndef make_request(tries: 3)\r\n  result = Net::HTTP.get(URI(\"http://www.example.org\"))\r\n  puts \"Success: #{result}\"\r\nrescue => e\r\n  tries -= 1\r\n  puts \"Error: #{e}. #{tries} tries left.\"\r\n  if
        tries > 0\r\n    retry\r\n  else\r\n
        \   raise e\r\n  end\r\nend\r\n\r\nmake_request\r\n\r\n#
        >> Error: Packets devoured by rodents.
        2 tries left.\r\n# >> Error: Request saw its shadow. 1 tries left.\r\n# >>
        Success: OK\r\n
\r\n
\r\n

So what happened here? By invoking retry, we triggered a feature that is not found in many other languages. retry tells the Ruby VM to back execution up to the beginning of the nearest begin/rescue/end block and try again. Since we used the method-level rescue clause, the effective location of the nearest begin block is the beginning of the current method's code.

\r\n

There are a couple of points worth noting about this code. First off, it is very important when dealing with retry to remember to create and use a counter of some kind. Likewise, it is vital to remember to decrement the counter before retrying, and to check the state of the counter before retrying. Miss any of these points, and we end up with an infinite loop.

\r\n

Secondly, note how we made the counter a method parameter instead of instantiating it inside the method body. This isn't just to make it easy to override. Remember what we said earlier: retry starts again from the top of the nearest begin block or, in this case, the current method body. If we had instead made the counter a local variable inside the method body, it would be reinitialized to its original value with every retry. Again, we'd get an infinite loop.

\r\n
\r\n
def
        make_request\r\n  tries = 3 # don't
        do this\r\n  result = Net::HTTP.get(URI(\"http://www.example.org\"))\r\n
        \ puts \"Success:
        #{result}\"\r\nrescue
        => e\r\n  tries -= 1\r\n  puts \"Error: #{e}. #{tries} tries
        left.\"\r\n  if tries > 0\r\n
        \   retry\r\n  else\r\n
        \   raise\r\n  end\r\nend\r\n
\r\n
\r\n

So long as we are careful with our tries counter though, retry gives us an elegant and concise way to re-attempt failed operations.

\r\n

We've got a little more time, so let's pare this code down to the bare essentials before we wrap up. I wrote out an if/else statement for maximum clarity, but we can write this more concisely. Since, by definition, triggering the retry means that nothing after it will be executed, we can reduce the if statement to a statement modifier. We also don't technically need to supply an argument to raise, since it will implicitly re-reaise the current error if nothing else is given.

\r\n
\r\n
require \"./setup\"\r\n\r\ndef make_request(tries: 3)\r\n  result = Net::HTTP.get(URI(\"http://www.example.org\"))\r\n  puts \"Success: #{result}\"\r\nrescue => e\r\n  tries -= 1\r\n  puts \"Error: #{e}. #{tries} tries left.\"\r\n  retry
        if tries > 0\r\n  raise\r\nend\r\n
\r\n
\r\n

And that's enough for today. Happy hacking!

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-fcc05360f8e8dc9423985efeb7a7afb90c09271b\n \ Thu, 20 Nov 2014 09:00:00 -0500\n \n In this episode we'll see how Ruby lets us retry operations without resorting to a loop.\n \ \n \
\n \n <![CDATA[256 Workflow]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=632\n \ \n

At the center of a lot of application programming is workflow. Unlike algorithms or object modeling, workflow is all about lists of tasks that have to be performed one after another in order to achieve some business goal. Usually, the execution of later tasks depends on the outcome of earlier tasks in the list.

\r\n

For instance, consider this highly simplified workflow. First, log in. Then, make a purchase. Next, collect any special offers related to that purchase. Finally, get a receipt for the purchase.

\r\n

Unfortunately, any of the steps in this workflow can fail. As we can see if we run the code: currently it doesn't get past trying to make a purchase before terminating with an exception.

\r\n
\r\n
$n = 0\r\n\r\ndef
        login\r\n  puts \"Logging
        in\"\r\n  $n += 1\r\n  return :session123\r\nend\r\n\r\ndef
        make_purchase(session)\r\n  puts
        \"Making purchase\"\r\n  fail
        \"The API was rude to me\" if
        $n < 2\r\n  $n
        += 1\r\n  :purchase_record\r\nend\r\n\r\ndef get_special_offers(purchase_record)\r\n
        \ puts \"Getting special offers\"\r\n  fail \"Special offers
        server is down.\"\r\nend\r\n\r\ndef get_receipt(purchase_record)\r\n
        \ puts \"Getting receipt\"\r\n  fail
        \"I forgot what I was doing\" if
        $n < 3\r\n  fail
        \"I left it in my other pants\" if
        $n < 4\r\n  $n
        += 1\r\n  :receipt\r\nend\r\n\r\nsession
        = login\r\npurchase_rec = make_purchase(session)\r\noffers = get_special_offers(purchase_rec)\r\nreceipt
        = get_receipt(purchase_rec)\r\n\r\n#
        >> Logging in\r\n#
        >> Making purchase\r\n\r\n# ~>
        RuntimeError\r\n# ~> The API was rude to me\r\n#
        ~>\r\n#
        ~> xmptmp-in25515jcB.rb:11:in `make_purchase'\r\n# ~>
        xmptmp-in25515jcB.rb:30:in `<main>'\r\n
\r\n
\r\n

The errors that these particular steps usually encounter tend to be transient errors. That is, they are errors that only happen some of the time, due to network connectivity issues, server outages, or other temporary problems.

\r\n

There are a lot of potential ways we could change this code to make it more robust. Some possible approaches include building a state machine to represent our workflow. Or creating a general-purpose monadic abstraction for chaining together unreliable actions.

\r\n

These are legitimate strategies with some good arguments in their favor. But I thought it would be interesting to try and see how we might tackle this with as little change or added ceremony as possible, using just some carefully chosen Ruby features.

\r\n

In order to make the workflow more robust, we add a new helper method called attempt. It accepts an argument determining how many times a task will be retried, and a block which is the action to be attempted.

\r\n

We surround each step in the workflow with a call to attempt, specifying different numbers of re-try attempts based on our past experience with these actions and our tolerance for waiting before giving up.

\r\n

When we run the new code, it gets further. But unfortunately, it still doesn't quite succeed. The method for getting special offers is still failing.

\r\n

The thing is, getting special offers is strictly optional. This is reflected in the fact that we've only permitted it one attempt. We don't want to waste a lot of time trying to retrieve special offers.

\r\n
\r\n
$n = 0\r\n\r\ndef login\r\n
        \ puts \"Logging in\"\r\n  $n
        += 1\r\n  return :session123\r\nend\r\n\r\ndef
        make_purchase(session)\r\n  $n += 1\r\n  puts \"Making
        purchase\"\r\n  fail \"The
        API was rude to me\" if $n
        < 3\r\n  :purchase_record\r\nend\r\n\r\ndef
        get_special_offers(purchase_record)\r\n
        \ $n += 1\r\n  puts \"Getting
        special offers\"\r\n  fail \"Special offers server is down.\"\r\nend\r\n\r\ndef get_receipt(purchase_record)\r\n
        \ $n += 1\r\n  puts \"Getting
        receipt\"\r\n  fail \"I
        forgot what I was doing\" if $n < 4\r\n  fail
        \"I left it in my other pants\" if
        $n < 5\r\n  :receipt\r\nend\r\n\r\ndef
        attempt(times:
        1)\r\n  yield\r\nrescue
        => e\r\n  times -= 1\r\n  retry if times > 0\r\n  raise(e)\r\nend\r\n\r\nsession      = attempt(times:
        1)  {login}\r\npurchase_rec = attempt(times:
        3)  {make_purchase(session)}\r\noffers       = attempt(times:
        1)  {get_special_offers(purchase_rec)}\r\nreceipt      = attempt(times:
        10) {get_receipt(purchase_rec)}\r\n\r\n#
        >> Logging in\r\n#
        >> Making purchase\r\n# >>
        Making purchase\r\n# >> Getting special offers\r\n\r\n# ~>
        RuntimeError\r\n# ~> Special offers server is down.\r\n# ~>\r\n# ~>
        xmptmp-in25515NTF.rb:19:in `get_special_offers'\r\n#
        ~> xmptmp-in25515NTF.rb:40:in `block
        in <main>'\r\n# ~> xmptmp-in25515NTF.rb:31:in `attempt'\r\n# ~>
        xmptmp-in25515NTF.rb:40:in `<main>'\r\n
\r\n
\r\n

What we need is a way to alter the error policy for an individual step in the workflow. In order to do this, we add a new keyword argument on_error to the attempt method. We give it a default which is a lambda that simply re-raises the passed error.

\r\n

Then we replace the raise in the rescue stanza with a call to the on_error handler, using Ruby's shorthand lambda calling syntax.

\r\n

Back in our workflow, we change the special offers step. We give it a custom error handler, which simply ignores the passed error and does nothing. When we run the code again, it gets all the way to the end. We have successfully made the special offer step optional.

\r\n
\r\n
$n = 0\r\n\r\ndef
        login\r\n  puts \"Logging
        in\"\r\n  $n += 1\r\n  return :session123\r\nend\r\n\r\ndef
        make_purchase(session)\r\n  $n += 1\r\n  puts \"Making
        purchase\"\r\n  fail \"The
        API was rude to me\" if $n
        < 3\r\n  :purchase_record\r\nend\r\n\r\ndef
        get_special_offers(purchase_record)\r\n
        \ $n += 1\r\n  puts \"Getting
        special offers\"\r\n  fail \"Special offers server is down.\"\r\nend\r\n\r\ndef get_receipt(purchase_record)\r\n
        \ $n += 1\r\n  puts \"Getting
        receipt\"\r\n  fail \"I
        forgot what I was doing\" if $n < 4\r\n  fail
        \"I left it in my other pants\" if
        $n < 5\r\n  :receipt\r\nend\r\n\r\ndef
        attempt(times:
        1, on_error: ->(e){raise
        e})\r\n  yield\r\nrescue
        => e\r\n  times -= 1\r\n  retry if times > 0\r\n  on_error.(e)\r\nend\r\n\r\nsession
        = attempt(times: 1)  {login}\r\npurchase_rec
        = attempt(times: 3)  {make_purchase(session)}\r\noffers
        = attempt(times: 1, on_error:
        ->(_){}) {\r\n  get_special_offers(purchase_rec)\r\n}\r\nreceipt = attempt(times: 10) {get_receipt(purchase_rec)}\r\n\r\n# >>
        Logging in\r\n# >> Making purchase\r\n#
        >> Making purchase\r\n# >>
        Getting special offers\r\n# >> Getting receipt\r\n
\r\n
\r\n

Now let's throw a wrench in the works. We don't actually want these steps to raise exceptions when they fail. What we really want to do is collect both the exception and some extra contextual information for diagnostic use.

\r\n

In order to do this, we add a new variable error_details, which starts out nil. We then make a lambda named capture_error, which will accept an exception as an argument and set the error_details variable to a hash of failure data. In the hash we include the actual exception, the time at which it was raised, and the hostname of the current node. For this last item, we also need to require the socket library.

\r\n

We then go through each of our steps, except for the optional special offers one, changing the error handler to be our custom lambda.

\r\n

We add a line to our output, examining the state of the error_details variable. Then we alter one of the workflow steps to force it to always fail.

\r\n

When we run the code, we can see that rather than raising an exception as it did before, it now saves information into the error_details variable.

\r\n
\r\n
$n
        = 0\r\n\r\ndef login\r\n
        \ puts \"Logging in\"\r\n  $n
        += 1\r\n  return :session123\r\nend\r\n\r\ndef
        make_purchase(session)\r\n  $n += 1\r\n  puts \"Making
        purchase\"\r\n  fail \"The
        API was rude to me\" if $n
        < 100\r\n  :purchase_record\r\nend\r\n\r\ndef
        get_special_offers(purchase_record)\r\n
        \ $n += 1\r\n  puts \"Getting
        special offers\"\r\n  fail \"Special offers server is down.\"\r\nend\r\n\r\ndef get_receipt(purchase_record)\r\n
        \ $n += 1\r\n  puts \"Getting
        receipt\"\r\n  fail \"I
        forgot what I was doing\" if $n < 4\r\n  fail
        \"I left it in my other pants\" if
        $n < 5\r\n  :receipt\r\nend\r\n\r\ndef
        attempt(times:
        1, on_error: ->(e){raise
        e})\r\n  yield\r\nrescue
        => e\r\n  times -= 1\r\n  retry if times > 0\r\n  on_error.(e)\r\nend\r\n\r\nrequire
        \"socket\"\r\nerror_details = nil\r\ncapture_error
        = ->(e){\r\n  error_details = {\r\n    error:
        e,\r\n    time:  Time.now,\r\n
        \   host: Socket.gethostname\r\n
        \ }\r\n}\r\nsession = attempt(times: 1,
        on_error: capture_error)  {login}\r\npurchase_rec
        = attempt(times: 3, on_error:
        capture_error) {\r\n  make_purchase(session)\r\n}\r\noffers = attempt(times: 1, on_error:
        ->(_){}) {\r\n  get_special_offers(purchase_rec)\r\n}\r\nreceipt = attempt(times: 10, on_error:
        capture_error) {\r\n  get_receipt(purchase_rec)\r\n}\r\n\r\nerror_details\r\n# =>
        {:error=>#<RuntimeError: The API was rude to me>,\r\n# :time=>2014-10-17
        00:35:15 -0400,\r\n# :host=>\"hazel\"}\r\n\r\n#
        >> Logging in\r\n#
        >> Making purchase\r\n# >>
        Making purchase\r\n# >> Making purchase\r\n#
        >> Getting special offers\r\n# >>
        Getting receipt\r\n
\r\n
\r\n

Unfortunately, we can also see an unintended consequence in the output. Because we are no longer terminating execution early with an exception, the steps go right on executing even after the purchase step has failed.

\r\n

In order to ensure execution stops as soon as a required step fails, we make a few more changes. First, we modify our error handler lambdas. We make the capture_error one return false, and the special offer's no-op one returns true.

\r\n

Then we add and operators connecting each of the steps together in a chain. You might recall this idiom from episode #125.

\r\n

We've now made the execution of each step depend on the previous step returning a truthy value. We happen to know that all of our steps return something truthy when they succeed. And we've ensured that the capture_error lambda returns false, which should end the chain of execution when an error is encountered.

\r\n

To test this, we run the code again. Sure enough, we can see that the workflow now only gets as far as the failing step, and then stops.

\r\n
\r\n
$n = 0\r\n\r\ndef login\r\n
        \ puts \"Logging in\"\r\n  $n
        += 1\r\n  return :session123\r\nend\r\n\r\ndef
        make_purchase(session)\r\n  $n += 1\r\n  puts \"Making
        purchase\"\r\n  fail \"The
        API was rude to me\" if $n
        < 100\r\n  :purchase_record\r\nend\r\n\r\ndef
        get_special_offers(purchase_record)\r\n
        \ $n += 1\r\n  puts \"Getting
        special offers\"\r\n  fail \"Special offers server is down.\"\r\nend\r\n\r\ndef get_receipt(purchase_record)\r\n
        \ $n += 1\r\n  puts \"Getting
        receipt\"\r\n  fail \"I
        forgot what I was doing\" if $n < 4\r\n  fail
        \"I left it in my other pants\" if
        $n < 5\r\n  :receipt\r\nend\r\n\r\ndef
        attempt(times:
        1, on_error: ->(e){raise
        e})\r\n  yield\r\nrescue
        => e\r\n  times -= 1\r\n  retry if times > 0\r\n  on_error.(e)\r\nend\r\n\r\nrequire
        \"socket\"\r\nerror_details = nil\r\ncapture_error
        = ->(e){\r\n  error_details = {\r\n    error:
        e,\r\n    time:  Time.now,\r\n
        \   host:  Socket.gethostname\r\n
        \ }\r\n  false\r\n}\r\nsession =
        attempt(times: 1, on_error:
        capture_error)  {login} and\r\npurchase_rec
        = attempt(times: 3, on_error:
        capture_error) {\r\n  make_purchase(session)\r\n} and\r\noffers
        = attempt(times: 1, on_error:
        ->(_){true}) {\r\n  get_special_offers(purchase_rec)\r\n}
        and\r\nreceipt = attempt(times:
        10, on_error: capture_error) {\r\n  get_receipt(purchase_rec)\r\n}\r\n\r\nerror_details\r\n# =>
        {:error=>#<RuntimeError: The API was rude to me>,\r\n# :time=>2014-10-17
        00:34:58 -0400,\r\n# :host=>\"hazel\"}\r\n\r\n#
        >> Logging in\r\n#
        >> Making purchase\r\n# >>
        Making purchase\r\n# >> Making purchase\r\n
\r\n
\r\n

There is a lot more we could try and tackle here. For instance, what about specifying timeouts for slow operations? And what do we do about tasks that return a falsy value even when they succeed?

\r\n

There are clearly refinements we could make. But what we have already done works well, and I like that we've managed to accomplish without radically changing the basic structure of this code. I think this is enough for today. Happy hacking!

\n
\n \

Attached Files

\n ]]>
\n \ dpd-d89b1518e6104b0ec5778aac966933caa386ab5a\n \ Mon, 17 Nov 2014 12:27:00 -0500\n \n Today's episode is about coordinating application workflow using simple Ruby idioms.\n \ \n \
\n \n <![CDATA[255 httpd]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=630\n \ \n

I want to share with you one of my favorite Ruby parlor tricks.

\r\n

The other day I was updating an old website, and I needed to make sure the changes I had made looked right. It was a purely static site, so I just needed something that would serve the files in the current directory to a browser.

\r\n

In order to do this, I used the following command line:

\r\n
\r\n
ruby -run -e httpd . --port=8080\r\n
\r\n
\r\n

This booted up a Webrick instance, and I was able to test the site.

\r\n

Let's talk about what's going on here. We'll start by breaking down the command I used.

\r\n

The ruby part is hopefully self-explanatory. Then comes a dash and an 'r'. This is where things get a little sneaky. '-r' is mnemonic for require. This flag causes Ruby to treat the next argument as the name of a feature and attempt to load it.

\r\n

Next we supply the library to be required, which is called simply un. Ruby does not require us to put a space between short-form flags and their arguments, so we can squash them together. The result is that it looks like the word run… which is exactly what the authors playfully intended when they named the library.

\r\n

The next flag is -e, which tells Ruby to execute the following argument as Ruby code. The code we execute is simply a one-word method invocation: httpd.

\r\n

Next we supply the current directory as the directory to serve, and specify a port number.

\r\n
\r\n
ruby
        -r un -e httpd . --port=8080\r\n
\r\n
\r\n

So what is this mysterious un library, and this httpd method?

\r\n

Let's ask Ruby. As we learned in episode #237, Ruby keeps a list of the library files it has already loaded. We can tell Ruby to load the un library, and then grep through the $LOADED_FEATURES list until we find one that ends with un.rb.

\r\n
\r\n
$ ruby -run -e 'puts $LOADED_FEATURES.grep(/un.rb$/)'\r\n/home/avdi/.rubies/ruby-2.1.2/lib/ruby/2.1.0/un.rb\r\n
\r\n
\r\n

Now that we know where to find this library, lets open it up and take a peek inside.

\r\n

What we find is a is one-file library that offers a number of handy command-line utilities written in pure Ruby. A lot of them we will probably never need, like clones of the UNIX copy and move commands. Although, these might just be useful if we ever wanted to write a simple script that worked the same on Windows as on Linux or Mac OS.

\r\n

If we scroll down, we can find the httpd method. It's pretty short and simple. There's come boilerplate for handling command-line flags, and then just enough code to start up a Webrick server, serving the given directory.

\r\n

And that's really Ruby in a nutshell: it has fun magic tricks, but it's easy to look behind the curtain and learn how to the trick was accomplished. Happy hacking!

\n \
\n

Attached Files

\n ]]>
\n \ dpd-e4befa5159c5e99c2752a853d787063c278f3e42\n \ Thu, 13 Nov 2014 09:36:00 -0500\n \n Today's dish is about a fun-but-useful easter egg in the Ruby standard library.\n \n
\n \n \ <![CDATA[254 Step]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=628\n \ \n

So far on this show we have covered a few different ways to iterate over a sequence of incrementing or decrementing integers, without resorting to a traditional for-loop. We've used Ruby ranges, as well as the #upto and #downto= methods. We saw how both of those approaches have their own limitations.

\r\n

Today, we'll look at a generalization of this concept.

\r\n

Let's jump back to some code we saw in the last episode, #253. It prints a few verses of the song \"99 bottles of beer on the wall.

\r\n
\r\n
99.downto(95) do
        |n|\r\n  puts \"#{n}
        bottles of beer on the wall, #{n} bottles of beer\"\r\n  puts \"Take
        one down, pass it around, #{n-1} bottles of beer on the wall\"\r\nend\r\n\r\n# >>
        99 bottles of beer on the wall, 99 bottles of beer\r\n#
        >> Take one down, pass it around,
        98 bottles of beer on the wall\r\n#
        >> 98 bottles of beer on the wall,
        98 bottles of beer\r\n# >> Take one down, pass it around, 97 bottles of
        beer on the wall\r\n# >> 97 bottles of beer on the wall, 97 bottles
        of beer\r\n# >>
        Take one down, pass it around, 96 bottles of beer on the wall\r\n# >>
        96 bottles of beer on the wall, 96 bottles of beer\r\n#
        >> Take one down, pass it around,
        95 bottles of beer on the wall\r\n#
        >> 95 bottles of beer on the wall,
        95 bottles of beer\r\n# >> Take one down, pass it around, 94 bottles of
        beer on the wall\r\n
\r\n
\r\n

Now let's say we want to get to the end of the song faster, by only printing every other verse. We can't do this with the #downto method. But we can do it with the #step method. We give #step two optional keyword arguments: the number to end on, and the number to add on each iteration. In our case, we tell it to end on 94, and step by -2. The result is an abbreviated rendition of the song.

\r\n
\r\n
99.step(to: 95, by:
        -2) do |n|\r\n  puts \"#{n} bottles of beer on the wall,
        #{n} bottles of beer\"\r\n  puts
        \"Take one down, pass it around,
        #{n-1} bottles of beer on the wall\"\r\nend\r\n\r\n#
        >> 99 bottles of beer on the wall,
        99 bottles of beer\r\n# >> Take one down, pass it around, 98 bottles of
        beer on the wall\r\n# >> 97 bottles of beer on the wall, 97 bottles
        of beer\r\n# >>
        Take one down, pass it around, 96 bottles of beer on the wall\r\n# >>
        95 bottles of beer on the wall, 95 bottles of beer\r\n#
        >> Take one down, pass it around,
        94 bottles of beer on the wall\r\n
\r\n
\r\n

As I mentioned, both arguments are optional. If we omit the by keyword, we get an upwards count by one.

\r\n
\r\n
1.step(to: 5) do |n|\r\n  puts n\r\nend\r\n\r\n# >>
        1\r\n# >>
        2\r\n# >>
        3\r\n# >>
        4\r\n# >>
        5\r\n
\r\n
\r\n

If we omit the to keyword, we get an infinite progression. Of course, this means we need to have some condition for breaking out of the block unless we want it to loop infinitely. Here's an example of a loop that searches for an unused ID.

\r\n
\r\n
ids = [3, 2, 5, 4, 1, 7]\r\nid = 1.step do
        |n|\r\n  break n if
        !ids.include?(n)\r\nend\r\nputs \"Free
        ID: #{id}\"\r\n\r\n# >>
        Free ID: 6\r\n
\r\n
\r\n

(If the idiom of using break to return a value from a block is unfamiliar to you, check out episode #71.)

\r\n

Also unlike the #upto and #downto methods, #step is available on all Numeric types, not just integers. This means that we could increment floating point numbers if we wanted to.

\r\n
\r\n
0.0.step(to: 1.0, by:
        0.05) do |n|\r\n  puts n\r\nend\r\n\r\n# >>
        0.0\r\n# >>
        0.05\r\n# >>
        0.1\r\n# >>
        0.15000000000000002\r\n# >> 0.2\r\n#
        >> 0.25\r\n#
        >> 0.30000000000000004\r\n# >>
        0.35000000000000003\r\n# >> 0.4\r\n#
        >> 0.45\r\n#
        >> 0.5\r\n#
        >> 0.55\r\n#
        >> 0.6000000000000001\r\n# >>
        0.65\r\n# >>
        0.7000000000000001\r\n# >> 0.75\r\n#
        >> 0.8\r\n#
        >> 0.8500000000000001\r\n# >>
        0.9\r\n# >>
        0.9500000000000001\r\n# >> 1.0\r\n
\r\n
\r\n

Finally, as you might imagine from some of the other iteration methods we've explored on this show, there is also a form of #step that returns an Enumerator. Want to know the sum of just the even numbers from 0 through 100? We can set up the iteration and then, instead of supplying a block directly to the #step message, we can chain on sends to other Enumerable methods. In this case, we use #reduce to calculate a sum.

\r\n
\r\n
0.step(to: 100,
        by: 2).reduce(:+)
        # =>
        2550\r\n
\r\n
\r\n

(We introduced this usage of #reduce in episode #149)

\r\n

While it isn't quite as expressive as special case methods like #upto and #downto, #step enables us to iterate over numeric sequences in a very generalized and flexible way. Hopefully, knowing about #step will save you from having to write a specialized loop with counter variables at some point. Happy hacking!

\n \
\n

Attached Files

\n ]]>
\n \ dpd-1c6615b916ae4fa6ff5a5c0ae2980ec7ca71ddfc\n \ Mon, 10 Nov 2014 09:14:00 -0500\n \n We've seen ranges and upto/downto methods. Today we take a look at a generalized way to step through arbitrary, potentially infinite number sequences.\n \n
\n \n \ <![CDATA[253 Downto]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=625\n \ \n

Here's a quickie for you. The other day I was working on a little coding kata, where the goal is to print out the verses to the drinking song \"99 Bottles of Beer on the Wall\". For the purpose of this demonstration, I'll narrow the problem down to just the first five verses.

\r\n

Fresh off of making an episode about Ruby ranges, I automatically typed up some code that looked something like this.

\r\n
\r\n
(99..95).each
        do |n|\r\n  puts \"#{n} bottles of beer on the wall,
        #{n} bottles of beer\"\r\n  puts
        \"Take one down, pass it around,
        #{n-1} bottles of beer on the wall\"\r\nend\r\n
\r\n
\r\n

I executed this code, confident I would get the result I expected. And then I was very surprised when nothing at all came out.

\r\n

What I had forgotten was that Ruby ranges are one-way: they only go up, never down. When we expand this range to an array, it is empty:

\r\n
\r\n
(99..95).to_a                   #
        => []\r\n
\r\n
\r\n

To be perfectly frank, I consider this an omission in Ruby. Other languages have range features that can go either up or down. This is one of those rare cases in Ruby where typing out the code we imagine should work results in a failure.

\r\n

It is very rare to see traditional C-style for loops in Ruby code, and it would be jarring if we had to resort to one now. Fortunately, Ruby doesn't leave us completely out in the cold in this situation. Instead of a Range, we can use the #downto method. We send it to the starting integer, and provide the ending number as an argument. Our block will now be called with the number 99, then 98, then 97, 96, and finally 95.

\r\n
\r\n
99.downto(95) do
        |n|\r\n  puts \"#{n}
        bottles of beer on the wall, #{n} bottles of beer\"\r\n  puts \"Take
        one down, pass it around, #{n-1} bottles of beer on the wall\"\r\nend\r\n\r\n# >>
        99 bottles of beer on the wall, 99 bottles of beer\r\n#
        >> Take one down, pass it around,
        98 bottles of beer on the wall\r\n#
        >> 98 bottles of beer on the wall,
        98 bottles of beer\r\n# >> Take one down, pass it around, 97 bottles of
        beer on the wall\r\n# >> 97 bottles of beer on the wall, 97 bottles
        of beer\r\n# >>
        Take one down, pass it around, 96 bottles of beer on the wall\r\n# >>
        96 bottles of beer on the wall, 96 bottles of beer\r\n#
        >> Take one down, pass it around,
        95 bottles of beer on the wall\r\n#
        >> 95 bottles of beer on the wall,
        95 bottles of beer\r\n# >> Take one down, pass it around, 94 bottles of
        beer on the wall\r\n
\r\n
\r\n

Like so many other iterative methods in Ruby, #downto can also return an Enumerator. If we omit the block, we can then chain on whatever Enumerable method we like, such as #map.

\r\n
\r\n
99.downto(95).map{ |n| \"#{n} bottles of beer\" }\r\n#
        => [\"99 bottles of beer\",\r\n# \"98
        bottles of beer\",\r\n# \"97 bottles of beer\",\r\n#
        \"96 bottles of beer\",\r\n# \"95
        bottles of beer\"]\r\n
\r\n
\r\n

It probably goes without saying that the #downto method is mirrored by an #upto method, which does exactly what it sounds like.

\r\n
\r\n
1.upto(5) do |n|\r\n
        \ puts n\r\nend\r\n\r\n#
        >> 1\r\n#
        >> 2\r\n#
        >> 3\r\n#
        >> 4\r\n#
        >> 5\r\n
\r\n
\r\n

Sadly, neither of these methods accepts a step unit, so they are only helpful in cases where we want to count up or down in increments of one.

\r\n

If we want to get fancier, such as using custom increments or generating infinite sequences, there is a way to do that. But that's a topic for the next episode. Until then: Happy hacking!

\n
\n

Attached Files

\n ]]>
\n \ dpd-ed17302a7117207f96462851fc97439a4a6dff0d\n \ Thu, 06 Nov 2014 09:00:00 -0500\n \n Sometimes a range doesn't cut it.\n \n \
\n \n <![CDATA[252 Pop]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=623\n \ \n

Let's say we run a subscription screencasting service. Some of our episodes are members-only, and some of the episodes are free for anyone to watch. On our website's home page, we'd like to feature a random selection of the free videos so that visitors can get a sense of the content.

\r\n

But we don't want the selection to be completely random. In particular, we don't want to accidentally show just videos from a particular miniseries. We want to ensure that the featured samples are picked from a broad range of the show's history.

\r\n

Here's one way we might handle this. We define the number of sample episodes to show on the front page - let's say 2. Then we calculate a slice size from the total number of free episodes divided by the number of samples to feature. With this number in hand, we can use #each_slice to split the free episode list into equal-sized bins. Each bin represents a different period of time in the show's history.

\r\n

We can map over those bins, and pick a single random episode out of each one with #sample. After flattening the result, we have our selection of episodes. It is random, but random in such a way that it is impossible for two brand-new or two very old episodes to be selected.

\r\n
\r\n
eps = [7, 11, 17, 20, 24, 27, 35, 42, 45, 46]\r\n\r\nslice_count
        = 2\r\nslice_size  = eps.size / slice_count #
        => 5\r\n\r\nslices = eps.each_slice(slice_size).to_a\r\n# =>
        [[7, 11, 17, 20, 24], [27, 35, 42, 45, 46]]\r\n\r\npicks = slices.map
        { |s|\r\n  s.sample(1)                   #
        => [11], [27]\r\n}.flatten\r\n# =>
        [11, 27]\r\n
\r\n
\r\n

Of course, it would still be possible for two episodes to be selected from right in the middle of the show's run. In order to ensure a better spread, and because we like the idea of having more featured videos on the homepage, we increase the slice count 3.

\r\n

Unfortunately, this messes everything up. Because we currently have 10 free episodes to work with, the set doesn't divide evenly into three parts. As a result, when we tell the array to slice itself into 3-element arrays, it also generates a fourth one-element array with the remainder.

\r\n
\r\n
eps = [7, 11,
        17, 20, 24, 27, 35, 42, 45, 46]\r\n\r\nslice_count = 3\r\nslice_size  = eps.size
        / slice_count # =>
        3\r\n\r\nslices = eps.each_slice(slice_size).to_a\r\n#
        => [[7, 11, 17], [20, 24, 27], [35,
        42, 45], [46]]\r\n\r\npicks = slices.map { |s|\r\n  s.sample(1)                   # =>
        [7], [20], [45], [46]\r\n}.flatten\r\n#
        => [7, 20, 45, 46]\r\n
\r\n
\r\n

We decide to introduce a special case for this eventuality. If the actual number of slices is greater than the intended slice count, we will do a bit of extra processing. First, we'll find the slice that should have been the final one. Next, we'll remove the leftover slice using pop, and assign that removed slice to a variable. The #pop method, if you're not familiar with it, removes the last item in an array and returns the element it popped off.

\r\n

Then we'll concatenate the leftovers onto the slice before it.

\r\n

The result is a set of 3 bins, as originally intended, where the fourth bin has one extra element. We can sample these bins as before, and get the expected number of final picks.

\r\n
\r\n
if slices.size >
        slice_count\r\n  end_slice = slices[slice_count - 1] #
        => [35, 42, 45]\r\n  leftover
        \ = slices.pop              # => [46]\r\n  end_slice.concat(leftover)          # =>
        [35, 42, 45, 46]\r\nend\r\n\r\nslices\r\n# =>
        [[7, 11, 17], [20, 24, 27], [35, 42, 45, 46]]\r\n\r\npicks = slices.map
        { |s|\r\n  s.sample(1)                   #
        => [17], [20], [45]\r\n}.flatten\r\n# =>
        [17, 20, 45]\r\n
\r\n
\r\n

This works well, but we hate to introduce a conditional into this algorithm. Conditionals make code harder to reason about, because now in addition to thinking about what each line of code is doing, we also need to think about whether a line of code is being executed at all.

\r\n

Let's see if we can get rid of this conditional while still handling all cases. We'll do this progressively, moving more and more code out of the conditional until hopefully we can do away with it entirely.

\r\n

First off, we don't technically need the line which finds the ending slice to be inside the if block. We move that line out.

\r\n
\r\n
end_slice = slices[slice_count - 1] #
        => [35, 42, 45]\r\nif
        slices.size > slice_count\r\n  leftover  = slices.pop              # =>
        [46]\r\n  end_slice.concat(leftover)          #
        => [35, 42, 45, 46]\r\nend\r\n
\r\n
\r\n

For the next move, we need to understand a bit more about how #pop works. If we just send it with no arguments, it returns the last element. Or nil, if there are no elements.

\r\n

But if we supply an integer argument, #pop snips off and returns an array of that many elements. 1 gets us an array of one, 3 gets an array of 3, and so on. If we supply an argument of zero, we get an empty array back—/not/ a nil value.

\r\n
\r\n
a = [1, 2, 3,
        4, 5, 6, 7, 8, 9, 10]\r\na.pop                           #
        => 10\r\na                               # =>
        [1, 2, 3, 4, 5, 6, 7, 8, 9]\r\n\r\n[].pop                          # =>
        nil\r\n\r\na.pop(1)                        #
        => [9]\r\na.pop(3)                        # =>
        [6, 7, 8]\r\na.pop(0)                        #
        => []\r\n
\r\n
\r\n

With this knowledge in mind, we move on to the next step. We initialize a leftover_count variable to zero. Inside the if statement we reset it to 1. Then we use the leftover count as an argument to pop, and flatten the resulting array.

\r\n
\r\n
leftover_count
        = 0\r\nif slices.size > slice_count\r\n
        \ leftover_count = 1\r\n  leftover  = slices.pop(leftover_count).flatten # =>
        [46]\r\n  end_slice.concat(leftover)          #
        => [35, 42, 45, 46]\r\nend\r\n
\r\n
\r\n

At this point, we no longer need the pop and concat actions to be inside the conditional block. If there are no leftovers, the leftover count will be zero and the pop will return an empty array and leave the source array untouched. And of course concatenating an empty array to another array is a harmless no-op, so it's safe to execute the concat in either situation.

\r\n

Accordingly, we move those two lines out of the conditional.

\r\n
\r\n
leftover_count = 0\r\nif
        slices.size > slice_count\r\n  leftover_count = 1\r\nend\r\nleftover
        \ = slices.pop(leftover_count).flatten #
        => [46]\r\nend_slice.concat(leftover)
        \         # =>
        [35, 42, 45, 46]\r\n
\r\n
\r\n

At this point, all the if statement is doing is deciding whether to set a variable to 1 or 0. We can simplify this using the ternary operator.

\r\n
\r\n
leftover_count = slices.size > slice_count ? 1 :
        0\r\nleftover  = slices.pop(leftover_count).flatten #
        => [46]\r\nend_slice.concat(leftover)
        \         # =>
        [35, 42, 45, 46]\r\n
\r\n
\r\n

We've now reduced the conditional to a single line, but can we get rid of it completely? Well, remember what we need to do here. The result should be one if the slice size is one greater than the intended slice count, and it should be zero if the two are equal. When we phrase the problem this way, it rings a bell in the arithmetic centers of our brains: we can get the result we need by just subtracting the intended slice count from the actual size of the slices array.

\r\n
\r\n
eps = [7, 11, 17, 20, 24, 27, 35, 42, 45, 46]\r\n\r\nslice_count
        = 3\r\nslice_size  = eps.size / slice_count #
        => 3\r\n\r\nslices = eps.each_slice(slice_size).to_a\r\n# =>
        [[7, 11, 17], [20, 24, 27], [35, 42, 45], [46]]\r\n\r\nend_slice =
        slices[slice_count - 1] # => [35, 42, 45]\r\nleftover_count = slices.size
        - slice_count     # =>
        1\r\nleftover = slices.pop(leftover_count).flatten #
        => [46]\r\nend_slice.concat(leftover)
        \         # =>
        [35, 42, 45, 46]\r\n\r\nslices\r\n#
        => [[7, 11, 17], [20, 24, 27], [35,
        42, 45, 46]]\r\n\r\npicks = slices.map { |s|\r\n  s.sample(1)                   # =>
        [17], [24], [46]\r\n}.flatten\r\n#
        => [17, 24, 46]\r\n
\r\n
\r\n

When there's a leftover slice, the leftover count will be 1 and the leftovers will be moved into the last full bin. If we switch the desired slice count to one that evenly divides the list, the leftover count is zero and the concatenation has no effect.

\r\n

At this point, we have achieved our goal: we've found a way to divide a set of videos into equal or nearly equal sized bins, even when the division results in a leftover element. And we've done it without any conditionals at all. Along the way, we've seen that the #pop method has more to it than just removing and returning single values. Happy hacking!

\n
\n

Attached Files

\n ]]>
\n \ dpd-794736e856c3fd342cfd2d67abf2560dcdde9488\n \ Mon, 03 Nov 2014 09:00:00 -0500\n \n In today's episode, we'll eliminate a conditional by passing an argument to the Array#pop method\n \ \n \
\n \n <![CDATA[251 Email Template]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=620\n \ \n

Hey folks! I'm experimenting with putting the script in the episode body this time. Not sure how this will work out with RSS readers, etc. Let me know what you think!

\r\n

We cover a lot of slightly obscure features of Ruby and its libraries on this show. Some of these capabilities may seem like they only have very niche use cases. Today, I want to show an example of how, by putting together a bunch of little bits of Ruby knowledge, we can solve a pragmatic application programming problem.

\r\n

Let's say we are working on the latest and greatest social networking site. We're at the point where we want to start inviting beta users into the site, and so we need to send some emails out. We decide to separate emailing into its own gateway class. In this class we have a method called =#sendinvitationemail. It takes a recipient address and some other data as parameters.

\r\n
\r\n
class
        EmailGateway\r\n  def
        send_invitation_email(to:,
        **data)\r\n    subject = \"Invitation to join Squawker\"\r\n
        \   body = \"Hi,
        #{data[:name]}!\\n\" +\r\n      \"You're invited to join Squawker! To get started, go
        there: \\n\" +\r\n      data[:signup_url]\r\n
        \   send_mail(to: to, subject:
        subject, body: body)\r\n  end\r\n\r\n
        \ private\r\n\r\n  def
        send_mail(to:,
        subject:, body:)\r\n    puts \"***
        Sending mail to: #{to}\"\r\n    puts \"Subject: #{subject}\"\r\n
        \   puts\r\n    puts body\r\n  end\r\n\r\nend\r\n
\r\n
\r\n

To invite a new member, we can instantiate a gateway object and send it the #send_invitation_email message, along with all the requisite extra data used to fill in the email template.

\r\n
\r\n
require
        \"./email_gateway\"\r\n\r\ngw = EmailGateway.new\r\n\r\ngw.send_invitation_email(to: \"marvin@example.org\",\r\n
        \                        name: \"Marvin
        the Robot\",\r\n                         signup_url:
        \"http://example.com/signup\")\r\n\r\n# >>
        *** Sending mail to: marvin@example.org\r\n#
        >> Subject: Invitation to join Squawker\r\n# >>\r\n# >>
        Hi, Marvin the Robot!\r\n# >> You're invited to join Squawker! To get started,
        go there:\r\n# >>
        http://example.com/signup\r\n
\r\n
\r\n

With beta invitations working, we realize we now need to send out welcome messages as well. In adding another type of mailing, we realize we don't want to duplicate the ugly string-concatenation approach of our first method. We also feel like we should probably generalize our mail-sending system a bit.

\r\n

But up till now this has been a very lightweight app. It's built on Sinatra rather than Rails, and we don't have an industrial-strength mailer framework all set up and ready to go.

\r\n

As we think through the requirements of a generalized mail-sending subsystem, based on what we know about existing frameworks such as ActionMailer, we start to get discouraged. If we're going to be writing many more email bodies, we don't want to embed them as strings… we'll probably want to write them in their own template files… so we'll have to write some code to find the right template file for a given mailing… and then we'll need a way to personalize each template, so we'll need to hook in a templating system like ERB or mustache…

\r\n

Hang on, hang on. Let's take a step back for a moment. We're still only talking about two different email templates. And we're not worried about sending both HTML and text versions of the emails or anything else fancy like that. So let's see if we can use what we know about Ruby and come up with a pragmatic solution. Something that makes it a little more pleasant to write new types of mailing, without overthinking things too much.

\r\n

Here's what we come up with. First off, we bring in our unindent method from episode #249. Then we write a new method, #send_welcome_email. Like the other mailing method, it requires a to keyword, and can accept an arbitrary set of other data attributes.

\r\n

We start it similarly to the last method, by defining a subject. Then we define a body. This is where we diverge from the other mailer method. Instead of a quoted string, we start a heredoc, filtered by our unindent method to get rid of unwanted leading spaces. Then we apply the format operator to the unindented heredoc. Let's complete the heredoc before we talk about the format operator and its other argument.

\r\n

Inside the heredoc, we define the text of the message. Since the whole doc will be unindented, we are free to indent the body consistent with our usual coding style. Anywhere in the body where we need a personalized value, we enclose its name in curly braces preceded by a percent sign. The personalizable elements in this message are the recipient's name, and a URL for the site homepage.

\r\n

Once we have the subject and body defined, we use the private send_mail method just as in the other mailer method.

\r\n

Now that we are done defining the method body, let's talk a bit more about how the personalization will work. As you may recall from episode #194, Ruby strings have a format operator, the percent sign. This operator performs printf-style expansions in the string, using the supplied arguments. When we pass a hash instead of an array to the format operator, Ruby treats the hash as a source of values for named expansion keys. Everywhere the text contains a curly-braced word preceded by a percent sign, the format process looks up that word in the given hash of data and replaces the whole specifier with the value it finds there.

\r\n

The upshot is that string formats give us a quick and dirty way to implement templated text without resorting to templating engines.

\r\n
\r\n
class EmailGateway\r\n
        \ def unindent(s)\r\n
        \   s.gsub(/^#{s.scan(/^[ \\t]+(?=\\S)/).min}/, \"\")\r\n
        \ end\r\n\r\n  def
        send_invitation_email(to:,
        **data)\r\n    subject = \"Invitation to join Squawker\"\r\n
        \   body = \"Hi,
        #{data[:name]}!\\n\" +\r\n      \"You're invited to join Squawker! To get started, go
        there: \\n\" +\r\n      data[:signup_url]\r\n
        \   send_mail(to: to, subject:
        subject, body: body)\r\n  end\r\n\r\n
        \ def send_welcome_email(to:, **data)\r\n    subject = \"Welcome
        to Squawker!\"\r\n    body    = unindent(<<-'EOF')
        % data\r\n Hi %{name}! Welcome to Squawker!\r\n\r\n Your account is all set up and ready to go! Access it
        anytime\r\n at: %{home_url}\r\n EOF\r\n\r\n    send_mail(to:
        to, subject: subject, body:
        body)\r\n  end\r\n\r\n  private\r\n\r\n
        \ def send_mail(to:, subject:, body:)\r\n
        \   puts \"*** Sending mail to:
        #{to}\"\r\n    puts \"Subject: #{subject}\"\r\n
        \   puts\r\n    puts body\r\n  end\r\n\r\nend\r\n
\r\n
\r\n

Let's give our new mailer method a try. We supply a recipient email address, a name, and a home URL. When we look at the output, we can see that everything has been filled in where it should be.

\r\n
\r\n
gw = EmailGateway.new\r\ngw.send_welcome_email(to: \"crow@example.org\",\r\n
        \                     name: \"Crow
        T. Robot\",\r\n                      home_url:
        \"http://example.com\")\r\n\r\n#
        >> *** Sending mail to: crow@example.org\r\n# >>
        Subject: Welcome to Squawker!\r\n#
        >>\r\n#
        >> Hi Crow T. Robot! Welcome to Squawker!\r\n# >>\r\n# >>
        Your account is all set up and ready to go! Access it anytime\r\n# >>
        at: http://example.com\r\n
\r\n
\r\n

As it turns out, we haven't really generalized our mail gateway at all. What we've done is come up with a new convention for writing mailer methods that lets us avoid ugly and typo-prone string quoting and concatenation. It also allows us to insert personalized values into the templates by name, in a way that is decoupled from where the values came from.

\r\n

Once we write a few more mailer methods, we might feel the urge to take another pass and factor out some of the commonalities between our mailer methods. If we write even more mailing types, we may eventually look for a way to push the mail templates out into their own files rather than expanding this file indefinitely. But for right now, we've found a way forward that starts us on an incremental path to greater generality, without becoming overwhelmed by all the requirements of an elaborate mailing subsystem. Happy hacking!

\n
\n

Attached Files

\n ]]>
\n \ dpd-684a63e389b5779939bcf1c3e3fefc224ed1442b\n \ Fri, 31 Oct 2014 11:49:00 -0400\n \n In today's episode, we put together several of the small bits of Ruby lore we've learned in order to build a simple and pragmatic mail templating system.\n \ \n \
\n \n <![CDATA[250 Refinements]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=618\n \ \n

Today we tackle one of Ruby's most controversial new features. Building on the example from episode #249, we'll look at how to extend a core class with custom methods, without attracting the kinds of potential bugs that \"monkey-patching\" invites.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-a797d664660ad955b75c1860ba7f76f61d6cd39f\n \ Mon, 27 Oct 2014 09:00:00 -0400\n \n Lexically extending core classes.\n \n \
\n \n <![CDATA[249 Unindent]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=616\n \ \n

In today's episode we explore methods for unindenting text. Along the way, we'll learn some tricks and play some code golf.

\n
\n

Attached Files

\n ]]>
\n \ dpd-bb27e66a17b731fb00f88ffb8a4869063fe7ea7c\n \ Fri, 24 Oct 2014 11:13:00 -0400\n \n Unindenting text, for fun and education.\n \n \
\n \n <![CDATA[248 Min By]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=614\n \ \n

In this episode, we'll look at an easy way to find items in a collection with the lowest (or highest) of some attribute. And at how we can apply this knowledge to more than just arrays.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-b8cb1edcdd8456c750734f4503c2726374c6d960\n \ Mon, 20 Oct 2014 11:56:00 -0400\n \n Finding the item with the min (or max) of some attribute.\n \n \
\n \n <![CDATA[247 Multiline Strings]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=612\n \ \n

Today we'll compare a few different ways to quote large, multi-line blocks of text.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-6e21b25eb274c1b5f884171f9e2600a179f7ad95\n \ Thu, 16 Oct 2014 21:54:00 -0400\n \n Three different ways of quoting multiline strings.\n \n
\n \n \ <![CDATA[246 Values At]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=609\n \ \n

Today's dish is a quick little refactoring that demonstrates working with multiple values pulled from a Hash.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-6221efb60ffdd2a0fd15ca1580e2d6939630893e\n \ Mon, 13 Oct 2014 13:01:00 -0400\n \n Fetching multiple values from hashes\n \n \
\n \n <![CDATA[245 Current Dir]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=607\n \ \n

An easier way to find the location of the current source file.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-c8091c9b7602d3d9408a00bd350fe6418be9612f\n \ Thu, 09 Oct 2014 13:58:00 -0400\n \n An easier way to find the location of the current source file.\n \n
\n \n \ <![CDATA[244 Drop While]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=605\n \ \n

In today's episode, we'll compare two methods for getting to the body of an email: one imperative; one functional; and both idiomatic Ruby.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-4e935fa249e7e32882df2c72cfaf05edebb5ea4f\n \ Mon, 06 Oct 2014 10:04:00 -0400\n \n Getting functional with arrays\n \n \
\n \n <![CDATA[243 Replace Parameter with Option]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=603\n \ \n

In this episode we take a method with a large and unweildy signature, and begin to pull parameters out into configurable options.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-2bee2df5610a4c0f66545d811b2aa17be5ac32e3\n \ Thu, 02 Oct 2014 09:00:00 -0400\n \n Paring down a method signature.\n \n \
\n \n <![CDATA[242 Logical Require]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=602\n \ \n

Today we discuss the downsides of using require_relative, and explore a more robust way to handle code loading within projects and libraries.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-125847689b7fb5d2d55ead2e7792432954501a74\n \ Mon, 29 Sep 2014 14:22:00 -0400\n \n A robust way to load code in projects\n \n \
\n \n <![CDATA[241 Match]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=599\n \ \n

In this episode, we look into how to get the most out of the Regexp#match method.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-1e5968bd46153517fcea5127d7d22643c9f719e2\n \ Thu, 25 Sep 2014 10:38:00 -0400\n \n Some tips on using the results of regex matches\n \n \
\n \n <![CDATA[240 Relative Require]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=595\n \ \n

Today our ongoing series on loading code brings us to the topic of loading features relative to the current file.

\n
\n

Attached Files

\n ]]>
\n \ dpd-828878a6b7b30b6b15afacd41d5d4f58edd4eb1e\n \ Mon, 22 Sep 2014 09:00:00 -0400\n \n Loading features relative to the current file\n \n \
\n \n <![CDATA[239 Parameter Defaults]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=594\n \ \n

Think you know everything there is to know about parameter defaults? Think again!

\n \
\n

Attached Files

\n ]]>
\n \ dpd-8e936531c5c1236c473e9be77e1e4b594fd03a35\n \ Thu, 18 Sep 2014 12:08:00 -0400\n \n Digging deep into parameter default values.\n \n
\n \n \ <![CDATA[238 Gem Require]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=592\n \ \n

In this episode we dig into RubyGems in order to understand how it augments Ruby's built-in code loading mechanisms.

\n
\n

Attached Files

\n ]]>
\n \ dpd-20dc4b02f618929b496d55c14452e8daa98218f9\n \ Mon, 15 Sep 2014 09:00:00 -0400\n \n The mechanics of loading gems\n \n \
\n \n <![CDATA[237 Require]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=591\n \ \n

As we continue to talk about the process of loading code in Ruby, today we look at how requiring features differs from loading files.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-052f1de71a775483970d5db549a7862ea1bcc7e2\n \ Thu, 11 Sep 2014 09:00:00 -0400\n \n From loading files to requiring features\n \n \
\n \n <![CDATA[236 Wrapped Load]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=590\n \ \n

As we continue our exploration of how code is loaded into Ruby processes, today we take a look at a technique for running an external script inline without polluting the current Ruby context.

\n
\n

Attached Files

\n ]]>
\n \ dpd-21c6d2330d671d593c170af7f981aacaa5973dbe\n \ Mon, 08 Sep 2014 12:21:00 -0400\n \n Running external scripts inline\n \n \
\n \n <![CDATA[235 Load]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=589\n \ \n

Today's episode kicks off a little series on loading code in Ruby. To start off, we'll look at what it would take to recreate Ruby's logic for locating code files to be loaded.

\n
\n

Attached Files

\n ]]>
\n \ dpd-f856cd2e9dce05c4fb5d8b7212bb6f10960c23d4\n \ Thu, 04 Sep 2014 12:44:00 -0400\n \n How Ruby finds code\n \ \n \
\n \n <![CDATA[234 Warn]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=586\n \ \n

Today we talk about how to let client coders know that something might be amiss... the Ruby way.

\n
\n

Attached Files

\n ]]>
\n \ dpd-9b6ac7ae969d9016c5b1d578be2b485be14e812e\n \ Mon, 01 Sep 2014 19:40:00 -0400\n \n Warning! Warning! Danger, Will Robinson!\n \n \
\n \n <![CDATA[233 Flip-Flop]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=584\n \ \n

Continuing from the last episode's them of exploring the Range type, today we look at one of Ruby's more peculiar legacies from AWK and Perl.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-d1562ea4ff93cd56d6d358f55ba282643ac60c38\n \ Thu, 28 Aug 2014 10:33:00 -0400\n \n Sometimes Ruby likes to wear flip-flops\n \n \
\n \n <![CDATA[232 Range]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=582\n \ \n

Good morning diners! On today's menu, we have a deep exploration of Ruby's Range type. Even if you've used ranges before, you might find something new to chew on. Enjoy!

\n
\n

Attached Files

\n ]]>
\n \ dpd-88a6119aeb9eaddde7d7ea653cfe4f96e952600c\n \ Mon, 25 Aug 2014 09:00:00 -0400\n \n Exploring Ruby's Range type\n \ \n \
\n \n <![CDATA[231 First]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=578\n \ \n

Today, some options for getting the first N elements from a list.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-a751ff97e3373d605d2772fabc426b0c0e90bd30\n \ Fri, 22 Aug 2014 11:51:00 -0400\n \n Fetching the first element(s) from a list\n \n \
\n \n <![CDATA[230 Black Box]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=577\n \ \n

Last week we talked about the consequences of building an app without unit tests that might have forced us to deal with internal coupling. Today we look at the same app from a new perspective.

\n
\n

Attached Files

\n ]]>
\n \ dpd-65ad78f971c0fe0163d9c8d290aefb5829a0dfce\n \ Mon, 18 Aug 2014 10:37:00 -0400\n \n Extending software from the outside\n \n \
\n \n <![CDATA[229 Consequences]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=574\n \ \n

In today's episode we'll meet an application written without tests, and discover the repercussions of that decision.

\n
\n

Attached Files

\n ]]>
\n \ dpd-a6bbaa7e1ab10e52fdda15c8591ae77c2ada32af\n \ Thu, 14 Aug 2014 16:56:00 -0400\n \n Exploring the repercussions of a test-less implementation.\n \n
\n \n \ <![CDATA[228 Reconsidering Regexen]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=572\n \ \n

Regular expressions are one of the most powerful tools at our disposal. But sometimes they aren't as well suited to a job as they may first appear. In today's episode we look at an alternative to regexen for validating strings.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-ce0f8012ca0c9eefdfaa9f467ba4142a9df8e020\n \ Mon, 11 Aug 2014 10:24:00 -0400\n \n Sometimes a regex is the wrong tool for the job.\n \n
\n \n \ <![CDATA[227 Multiline Memoize]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=569\n \ \n

Today we'll look at two different ways to memoize a complex method, and talk about why I prefer one over the other.

\n
\n

Attached Files

\n ]]>
\n \ dpd-ed6538bc87329d0567ff8f088bc38a5fa7a05663\n \ Thu, 07 Aug 2014 10:36:00 -0400\n \n Memoizing complex methods\n \n \
\n \n <![CDATA[226 Evil Monkeys]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=567\n \ \n

In which I rant a bit about the dangers of \"monkey-patching\".

\n
\n \

Attached Files

\n ]]>
\n \ dpd-ec0f213c166adcf4b050d4b24c4bfbd66c77355a\n \ Mon, 04 Aug 2014 15:05:00 -0400\n \n In which I rant a bit about the dangers of \"monkey-patching\".\n \ \n \
\n \n <![CDATA[225 Unitwise]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=563\n \ \n

As our series on representing physical quantities draws to a close, we turn our attention to existing libraries, specifically the Unitwise gem.

\n
\n

Attached Files

\n ]]>
\n \ dpd-582bae49f378b33076ecd0adf7c9fa9607143389\n \ Fri, 01 Aug 2014 15:27:00 -0400\n \n A gem for representing physical quantities\n \n \
\n \n <![CDATA[224 Surrogate Ordering]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=562\n \ \n

It's often desirable compare objects to see which is \"lesser\" or \"greater\"; but not all objects are inherently comparable. Today we'll look at a scheme for easily imposing an arbitrary ordering on a constrained set of value objects. 

\n \
\n

Attached Files

\n ]]>
\n \ dpd-4584a1919cb313052bc4b3fcde35c74ecb48106b\n \ Mon, 28 Jul 2014 12:16:00 -0400\n \n Imposing order on objects\n \n \
\n \n <![CDATA[223 Equalizer]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=561\n \ \n

We know that Value Objects are useful; today we'll meet a gem that makes them easier to build.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-399f30be9686a392b73754a713d331466b564188\n \ Fri, 25 Jul 2014 14:06:00 -0400\n \n A shortcut to value objects\n \n \
\n \n <![CDATA[222 String Partition]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=559\n \ \n

Today we look at a lesser-known but handy pair of methods on Strings.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-9235edf710e646a89345f49ab26a370961aef2ad\n \ Mon, 21 Jul 2014 09:00:00 -0400\n \n Splitting filenames into their component parts\n \n
\n \n \ <![CDATA[221 Def Return Value]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=556\n \ \n

Since Ruby 2.1, def returns a symbol. In this episode we'll take a look at why this matters, and how it might change the way we define methods.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-9399d25e9a929e263b6c04b5cb17342544b31858\n \ Thu, 17 Jul 2014 09:42:00 -0400\n \n Exploring a Ruby 2.1 feature\n \n \
\n \n <![CDATA[220 Type and Class]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=554\n \ \n

Where do we draw the line between objects differntiated only by state, and objects differentiated by their class? That's the question we'll examine in this episode.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-1a345fbd0ee4ff42a887aaade2e3b10e3ea1cf6f\n \ Tue, 15 Jul 2014 11:35:00 -0400\n \n When does object state warrant creating a new class?\n \n
\n \n \ <![CDATA[219 Adamantium]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=550\n \ \n

Today we learn about Ruby gem that makes it easier to build immutable value objects.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-9a559c5cf2b5555aacc6dbceba42d15612a41c0c\n \ Thu, 10 Jul 2014 12:43:00 -0400\n \n Making objects immutable\n \n \
\n \n <![CDATA[218 Spaceship Revisted]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=547\n \ \n

In episode 205 we introduced the spaceship (<=>) operator, but we also introduced an incompatibility with how Ruby's builtin comparisons work. Today we'll address this oversight.

\n
\n

Attached Files

\n ]]>
\n \ dpd-677495622a2322c0d474e76997305ff0d3809cb6\n \ Mon, 07 Jul 2014 11:17:00 -0400\n \n Dealing with disparate types\n \n \
\n \n <![CDATA[217 Redesign]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=545\n \ \n

Today, a story about a refactoring that went south, and turned out not to be a refactoring at all.

\n
\n

Attached Files

\n ]]>
\n \ dpd-f92a5ae3df84fc163a0631e4364c992f1ec524c7\n \ Fri, 04 Jul 2014 10:57:00 -0400\n \n Sometimes a refactoring isn't\n \n \
\n \n <![CDATA[216 Tell, Don't Ask]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=543\n \ \n

In today's episode, we explore a practical application of the famous \"tell, don't ask\" principle.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-a120246fdaca97e388c125b075568f989e7b17e2\n \ Mon, 30 Jun 2014 12:42:00 -0400\n \n Applying the principle to unit conversions.\n \n
\n \n \ <![CDATA[215 Grep]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=541\n \ \n

Today we look at Ruby's grep method, and explore the idea of matching objects by example.

\n
\n

Attached Files

\n ]]>
\n \ dpd-4a0817708787b9ba9dd92f8a987f7cf2739a9f00\n \ Thu, 26 Jun 2014 14:16:00 -0400\n \n Grep: not just for the command line!\n \n \
\n \n <![CDATA[214 Conversion Ratio]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=539\n \ \n

Today we puzzle through a design problem that threatens to result in dozens of extra methods. 

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-6f0e41ed57a6f89c33ff703fb76b1afa59962909\n \ Mon, 23 Jun 2014 11:52:00 -0400\n \n Representing relationships as objects\n \n \
\n \n <![CDATA[213 Conversion Protocol]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=535\n \ \n

Today's topic is \"conversion protocols\", an extensible way to enable safe, automatic conversions between types.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-3b965764613cdb2097448e370d555cb62d665c08\n \ Thu, 19 Jun 2014 15:44:00 -0400\n \n Safe, automatic conversions between types.\n \n \
\n \n <![CDATA[212 Self Class]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=533\n \ \n

Today's episode deals with a small matter of style, one that can have an impact on how easy it is to refactor code.

\n
\n

Attached Files

\n ]]>
\n \ dpd-12d5d64b172f2f150ec45627a943d6544b9f24ef\n \ Mon, 16 Jun 2014 11:54:00 -0400\n \n How to refer to an object's own class\n \n \
\n \n <![CDATA[211 Protected]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=530\n \ \n

The distinction between \"public\" and \"private\" method visibility in Ruby is pretty obvious. But when should we use \"protected\" visibility? This episode attempts to answer that question.

\n
\n

Attached Files

\n ]]>
\n \ dpd-292facfac0946d86f0a9e837d75c4c4b5ca76c0b\n \ Fri, 13 Jun 2014 13:04:00 -0400\n \n When to tag methods as protected\n \n \
\n \n <![CDATA[210 Implicit Conversion]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=528\n \ \n

Have you ever wondered why Ruby has both #to_i and #to_int methods? Or both #to_a and #to_ary? In today's episode we'll answer this question, and look at how we can use implicit conversion methods to our advantage.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-e6ee8eaed02922cdb15e28c0de62c0eb223ef5ec\n \ Mon, 09 Jun 2014 09:00:00 -0400\n \n Ruby doesn't automatically convert one type to another... except when it does.\n \ \n \
\n \n <![CDATA[209 Explicit Conversion]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=523\n \ \n

In this episode we look at how to convert our Feet objects back to core numeric types. In the process, we gain the ability to use our Feet objects in format strings.

\n
\n

Attached Files

\n ]]>
\n \ dpd-2bd0b7f2e8c36e4b561a26de9b6ea4178cc9c50c\n \ Thu, 05 Jun 2014 09:00:00 -0400\n \n Converting from quantities to numeric types\n \n \
\n \n <![CDATA[208 Lenient Conversions]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=522\n \ \n

For core types like Float, Ruby has both conversion methods (#to_f), and conversion functions (Float()). When should we use one vs. the other? This episode attempts to answer that question.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-0985c1c62bad155b2780081aa4bc575674496756\n \ Mon, 02 Jun 2014 10:09:00 -0400\n \n When to use a conversion function, and when not to\n \n
\n \n \ <![CDATA[207 Conversion Function]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=519\n \ \n

In this episode we create a function for converting arbitrary values into Feet objects.

\n
\n

Attached Files

\n ]]>
\n \ dpd-f84fa6d922730877c020399cfd3605c05243345c\n \ Thu, 29 May 2014 11:59:00 -0400\n \n A Ruby convention for converting types\n \n \
\n \n <![CDATA[206 Coercion]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=517\n \ \n

Ruby generally doesn't allow mixing of types without explicit conversion. So have you ever wondered why it's possible to multiply 2.3 (a float) by 5 (an integer)? In today's episode we'll discover how Ruby's implicit coercions work, and how to apply them to our own custom numeric-like classes.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-04724729e366e33899f4f4d88d32a40165574c6a\n \ Tue, 27 May 2014 10:53:00 -0400\n \n Coercing different kinds of numbers\n \n \
\n \n <![CDATA[205 Comparable]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=515\n \ \n

In this episode we meet a Ruby standard module that makes it easy to make classes comparable and sortable.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-ef982a22766b3b1de10a2e936239d6bd57032b35\n \ Thu, 22 May 2014 13:04:00 -0400\n \n Making objects comparable\n \n \
\n \n <![CDATA[204 Hash Equality]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=512\n \ \n

Building on the previous episode on hash tables, today we tackle the concept of hash equality: how Ruby decides if two objects are the same for the purpose of use as hash keys.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-0e92e74c178439bd13bf45441998886d7bcde763\n \ Mon, 19 May 2014 15:49:00 -0400\n \n Some objects are more equal than others\n \n \
\n \n <![CDATA[203 Hash Table]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=509\n \ \n

Today's dish is an exploration of how Ruby is able to quickly look up keys in hashes.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-8c7a020dab5fe4448a4b7c17c9ab1e5b95898af5\n \ Thu, 15 May 2014 11:06:00 -0400\n \n How Ruby looks things up in hashes\n \n \
\n \n <![CDATA[202 Identity and Equality]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=507\n \ \n

Today's episode deals with the concepts of identity and equality - what makes one object equal to another.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-9514bd148dd24b862f6e78d94a22b8a95120f513\n \ Mon, 12 May 2014 11:24:00 -0400\n \n Defining object equality in terms of state.\n \n
\n \n \ <![CDATA[201 Immutable Object]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=505\n \ \n

In this episode we learn how mutability can lead to bugs in Value Objects... and how we can eliminate these types of bugs once and for all.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-00689d2784375ae6ba94e6ffbdf08e3fbbbbc781\n \ Thu, 08 May 2014 11:40:00 -0400\n \n Mutability bugs, meet Mr. Freeze!\n \n \
\n \n <![CDATA[200 Quantity]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=504\n \ \n

In today's episode we take a look at a storied problem in software: defects caused by accidentally mixing-up the units of measurement used in calculations. And we kick off a multi-part series exploring how to build objects that represent physical quantities.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-b2dd3762c62758ab37f4c8698bbd1c55de1acc24\n \ Mon, 05 May 2014 09:37:00 -0400\n \n Representing physical quantities\n \n \
\n \n <![CDATA[199 Regexp Union]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=500\n \ \n

Today's episode demonstrates an easy way to build big regular expressions out of little ones.

\n
\n

Attached Files

\n ]]>
\n \ dpd-e9969577668306aff5d77075cc7b808b17a51e3f\n \ Thu, 01 May 2014 09:00:00 -0400\n \n Joining regular expressions\n \n \
\n \n <![CDATA[198 Decorator Transparency]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=499\n \ \n

In today's episode we look at a potential complication of using the Decorator pattern, and discuss how to resolve it by enforcing Command/Query Separation.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-8826bd7a5ed32da59535dc3c29ad7165e204ba20\n \ Mon, 28 Apr 2014 09:00:00 -0400\n \n Decorators and CQS\n \n \
\n \n <![CDATA[197 Decorator]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=496\n \ \n

Today we explore the Decorator pattern, with the help of the SimpleDelegator standard library.

\n
\n

Attached Files

\n ]]>
\n \ dpd-979bc792de1d89a37e151f0e18114f3b77142860\n \ Fri, 25 Apr 2014 13:33:00 -0400\n \n Adding new functionality without expanding existing classes\n \n
\n \n \ <![CDATA[196 String Templates]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=494\n \ \n

Have you ever wanted a way to customize the format of certain generated strings, but felt like ERB or some other templating language was overkill? If so, today's episode should satisfy!

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-c6499e49ba3da474eb2a5112caf1690497308e90\n \ Mon, 21 Apr 2014 13:41:00 -0400\n \n When ERB is overkill\n \n \
\n \n <![CDATA[195 Advanced String Formats]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=488\n \ \n

Following on from the last episode, today we look at some more advanced uses of Ruby's string formatting features.

\n
\n

Attached Files

\n ]]>
\n \ dpd-71905d67a70117b6d22f0633ca9cbaf98fab7622\n \ Thu, 17 Apr 2014 09:00:00 -0400\n \n Show those strings who's boss.\n \n \
\n \n <![CDATA[194 String Formats]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=487\n \ \n

Sometimes Kernel#puts and string interpolation doesn't give us the level of control we want over our program's output. When we need to control numeric formatting and field widths, we need to understand string formats. And that's what this episode is all about!

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-7c3fa3be0b91b78e2a01760cb771b6f0c7926a9b\n \ Mon, 14 Apr 2014 09:00:00 -0400\n \n Taking control of number formatting\n \n \
\n \n <![CDATA[193 Pathname]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=485\n \ \n

While the File methods are great for occasional use, for programs that deal extensively in filenames we need to bring out the big guns. Today we'll get an overview of Pathname, Ruby's swiss army knife for path manipulation.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-4728b2992335085ec71bd524cf76868be401117f\n \ Fri, 11 Apr 2014 09:45:00 -0400\n \n Manipulating file paths the easy way\n \n \
\n \n <![CDATA[192 Filenames]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=483\n \ \n

Ruby provides a lot of tools for breaking filenames into their component parts, but they aren't always well documented. Today's episode combines goes over some basics as well as a tip you might not be familiar with.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-ed35fe35fcf1068069be864c4650e676fdb56bd7\n \ Mon, 07 Apr 2014 17:28:00 -0400\n \n Breaking filenames into pieces\n \n \
\n \n <![CDATA[191 Virtual Proxy]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=479\n \ \n

When loading domain objects from an external service, it can be expensive to load up associated objects with them. Today's episode takes a look at a pattern for transparently lazy-loading such associations.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-8e37714153d6250b9ba8ff5a7090dfcd7a2be089\n \ Fri, 04 Apr 2014 08:55:00 -0400\n \n A scheme for lazy-loading associations\n \n \
\n \n <![CDATA[190 gsub]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=478\n \ \n

Today we look at the humble String#gsub method, and learn that it has some surprising tricks up its sleeve.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-40b8c59b311bf7f1ed1c0cdc2a48e6123e999372\n \ Mon, 31 Mar 2014 16:13:00 -0400\n \n Defending national security with string substitutions\n \n \
\n \n <![CDATA[189-assisted-refactoring]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=473\n \ \n

In today's episode, we'll examine how the availability of tools to aid refactoring can change how we write code.

\n
\n

Attached Files

\n ]]>
\n \ dpd-a9b70a25e7122dec4baede9c0acc43a39110627c\n \ Thu, 27 Mar 2014 10:10:00 -0400\n \n Sometimes an IDE is useful\n \n \
\n \n <![CDATA[188 Fail and Raise]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=471\n \ \n

Today's episode is about a semantic convention for error handling that I learned from Jim Weirich.

\n
\n

Attached Files

\n ]]>
\n \ dpd-a8e7720405b427bfc7c89f99c90afec2dc3719b1\n \ Mon, 24 Mar 2014 11:08:00 -0400\n \n Two ways to raise an exception\n \n \
\n \n <![CDATA[187 More Keyword Arguments]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=468\n \ \n

Today we go a little deeper into keyword arguments in Ruby 2.0/2.1, covering a few use cases we didn't cover in the last episode.

\n
\n

Attached Files

\n ]]>
\n \ dpd-fff890a31425e917c6b8f3ab8f755b44abed06ab\n \ Thu, 20 Mar 2014 12:03:00 -0400\n \n Advanced keyword arguments in Ruby 2.1\n \n \
\n \n <![CDATA[186 Keyword Arguments]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=466\n \ \n

Today's episode is a guide to transitioning various hash argument idioms to Ruby 2.0/2.1 keywords.

\n
\n

Attached Files

\n ]]>
\n \ dpd-8991f074c7f70edf5db8a1f46ceef209f770638e\n \ Mon, 17 Mar 2014 08:50:00 -0400\n \n Descriptive parameters without the pain\n \n \
\n \n <![CDATA[185 Two Refactorings]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=463\n \ \n

Today's dish is a refactoring approached from two different perspectives. Enjoy!

\n \
\n

Attached Files

\n ]]>
\n \ dpd-b4d063060d7012c8eebf4edf3e7d02ffae56c961\n \ Thu, 13 Mar 2014 14:57:00 -0400\n \n Functional! OO! Fight!\n \n \
\n \n <![CDATA[184 Sequel, Postgres, JSON]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=458\n \ \n

Recently we looked at the Sequel library for interacting with SQL databases. Today, we'll use Sequel again to play with the native JSON support in PostgreSQL 9.3.

\n
\n

Attached Files

\n ]]>
\n \ dpd-416c071674529a88ee41d71bd5d990d4376903a6\n \ Mon, 10 Mar 2014 09:00:00 -0400\n \n Working with JSON data in Postgres from Ruby\n \n \
\n \n <![CDATA[183 Extracting Ghost Load]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=457\n \ \n

We've used the \"ghost object\" pattern to lazily load attributes of a model object. And we've made a macro to easily declare \"ghost-loadable\" attribute accessors. Today we complete the generalization of ghost loading by extracting a module that makes it easy for any model object to declare lazily-loaded attributes.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-d433bf30b0e973f4d6089f96e10b04818f9ac20c\n \ Thu, 06 Mar 2014 09:00:00 -0500\n \n Extracting a reusable module\n \n \
\n \n <![CDATA[182 Macro]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=456\n \ \n

When is it appropriate to metaprogram? Today's episode looks at one situation in which it may be a good choice.

\n
\n

Attached Files

\n ]]>
\n \ dpd-06d32231a45e6b19233ea00fa59ac0726914c059\n \ Mon, 03 Mar 2014 09:50:00 -0500\n \n Eliminating duplication with metaprogramming\n \n \
\n \n <![CDATA[181 Schwartzian Transform]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=452\n \ \n

Today's episode demonstrates a technique for speeding up the process of sorting some collections.

\n
\n

Attached Files

\n ]]>
\n \ dpd-5ff1edfd51f9d7e41a990e112fc789233f2541ce\n \ Thu, 27 Feb 2014 17:38:00 -0500\n \n Faster sorting\n \ \n \
\n \n <![CDATA[180 Ghost Load]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=451\n \ \n

In today's episode we learn at how to implement lazy loading using the \"ghost object\" pattern.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-6a41312ec3aa90024db8642528a5ea6ea7afe7ec\n \ Mon, 24 Feb 2014 10:28:00 -0500\n \n Who you gonna call?\n \n \
\n \n <![CDATA[179 Sequel]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=449\n \ \n

ActiveRecord has become practically synonymous with SQL database access in Ruby, but it's not the only way to talk to SQL stores. Today we'll explore Sequel, a wonderfully rich tool for interacting with many different SQL RDBMSes.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-aee9e5e7347fd3e4ccf32aa63220ac275f19a5cf\n \ Thu, 20 Feb 2014 11:52:00 -0500\n \n Talking to SQL databases\n \ \n \
\n \n <![CDATA[178 Identity Map]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=447\n \ \n

In the last episode we looked at the problem of \"aliasing\", here there are multiple objects representing a single row in a database. Today, we'll look at one possible solution to that problem.

\r\n

Notes:

\r\n\n \
\n

Attached Files

\n ]]>
\n \ dpd-76965ebcd3cb2997b5039c670047f49c67a73e6a\n \ Mon, 17 Feb 2014 10:47:00 -0500\n \n Putting an end to aliasing\n \n \
\n \n <![CDATA[177 Aliasing]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=443\n \ \n

Today we look at a perncious problem that sometimes plagues code which uses an Object-Relational Mapper (ORM).

\r\n

Documentation of the ActiveRecord inverse_of option mentioned in the episode can be found here: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

\n \
\n

Attached Files

\n ]]>
\n \ dpd-4116919baac611a5d1163a9f8b03cc514cf4ea37\n \ Thu, 13 Feb 2014 15:32:00 -0500\n \n ORMs and evil clones\n \n \
\n \n <![CDATA[176 Dotenv]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=440\n \ \n

In this episode we look at a tool that I've found invaluable for managing configuration in my applications.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-6bc9f4a790c3306d35fa2a9fc322b0f038346593\n \ Mon, 10 Feb 2014 09:00:00 -0500\n \n Config without config files\n \ \n \
\n \n <![CDATA[175 REPL-Driven Development]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=438\n \ \n

In today's episode I cover a classic development technique that doesn't get enough press these days. Interactive, exploratory development was and is common in the Lisp world, and Ruby's dynamic nature is aptly suited to take advantage of this style of programming. Today I'll show you how I used REPL-driven development in Pry and Emacs to begin to build a video export tool.

\r\n

By the way, right after I made this episode I discovered that Conrad Irwin had done a RubyConf talk on REPL-driven development. You can watch it here: https://www.youtube.com/watch?v=D9j_Mf91M0I

\n \
\n

Attached Files

\n ]]>
\n \ dpd-8812065c775a4f0a41195bfd9add1e7788bb73e8\n \ Thu, 06 Feb 2014 09:43:00 -0500\n \n Developing via exploration\n \n \
\n \n <![CDATA[174 Multiple Assignment]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=436\n \ \n

Today's special is a note on style with a side order of opinion. Enjoy!

\r\n

(Note: I made a mistake in the visualization of which variables correspond to which on either side of the equals sign. Which kind of illustrates my point!)

\n \
\n

Attached Files

\n ]]>
\n \ dpd-93b1e87480e2faefe3dcffa569d3215ab2281351\n \ Mon, 03 Feb 2014 10:34:00 -0500\n \n Assigning multiple variables on a single line\n \n
\n \n \ <![CDATA[173 for]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=432\n \ \n

Newer arrivals to Ruby sometimes wonder when to use #each, and when to use 'for'. Today, a detailed explanation of how they differ, and an opinion on when to use 'for'.

\n
\n

Attached Files

\n ]]>
\n \ dpd-533be121d8008a765b296da22c21acf020a2e0ce\n \ Thu, 30 Jan 2014 10:21:00 -0500\n \n What's \"for\" for anyway?\n \ \n \
\n \n <![CDATA[172 Registry]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=431\n \ \n

Today's dish is another one from one of my favorite cookbooks: Martin Fowler's Patterns of Enterprise Application Development. This time, we'll use the Registry pattern to make it easy for objects to find the collaborators they need, without hardcoding their dependencies.

\n
\n

Attached Files

\n ]]>
\n \ dpd-cf5805ade621122e559d12c5bdf4f4d1a46ad0f7\n \ Mon, 27 Jan 2014 09:53:00 -0500\n \n Simplifying collaborator discovery\n \n \
\n \n <![CDATA[171 puts]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=427\n \ \n

Even the most basic of Ruby methods sometimes have hidden depth. Today we'll take a look at some advanced uses of the puts method.

\n
\n

Attached Files

\n ]]>
\n \ dpd-e825ff5acf4dd4c3183c58dfe5a83883ce96e49c\n \ Thu, 23 Jan 2014 11:50:00 -0500\n \n Think you know about puts? Think again!\n \n \
\n \n <![CDATA[170 Hash Merge]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=426\n \ \n

Today's episode covers an incredibly handy capability of Ruby's hashes that I didn't know about until recently.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-e3291c6d0fbdb20fa5faa591999d50482ddf02aa\n \ Mon, 20 Jan 2014 10:10:00 -0500\n \n Putting hashes together\n \n \
\n \n <![CDATA[169 Caching Proxy]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=423\n \ \n

You might remember the Gateway and Mapper patterns from recent episodes. Today we'll look at how to insert a caching layer between those two patterns. In the process, we'll examine how pattern-based design decisions can make it easy to add new functionality without changing existing classes.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-c8bd83b3faa63fca9d6c0eaadc87432d9eb29e2c\n \ Thu, 16 Jan 2014 09:18:00 -0500\n \n Cleanly inserting a caching layer\n \n \
\n \n <![CDATA[168 Enumerable Internals]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=421\n \ \n

In this very special episode, guest chef Pat Shaughnessy takes us on a whirlwind tour of the Ruby internals that make the Enumerable#all? method tick.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-05632265ab13651e07282412fe9d755af61673fb\n \ Mon, 13 Jan 2014 10:19:00 -0500\n \n With guest chef Pat Shaughnessy!\n \n \
\n \n <![CDATA[167 Debugging in Gems]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=419\n \ \n

When debugging a tricky problem, don't you sometimes wish you could just drop a line of debugging code right in the middle of a third-party gem? This episode shows how to do this in a responsible fashion.

\n
\n

Attached Files

\n ]]>
\n \ dpd-9b4f03a52a16fd3e29c1c48c3e6ea4d16036a8ef\n \ Thu, 09 Jan 2014 10:07:00 -0500\n \n A heretical but effective technique\n \n \
\n \n <![CDATA[166 Not Implemented]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=416\n \ \n

Ruby doesn't have a built-in concept of \"abstract\" classes, but sometimes we want a way to show other programmers when methods are left as an exercise for the implementor. This episode discusses how.

\n
\n

Attached Files

\n ]]>
\n \ dpd-bc9d5d94391daa889375efd308212c18726c2e16\n \ Mon, 06 Jan 2014 09:14:00 -0500\n \n Writing placeholder methods\n \n \
\n \n <![CDATA[165 Refactor Tapas::Queue]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=415\n \ \n

In previous episodes, we got the Tapas::Queue class under test, using a couple of different thread-testing techniques. Now that it has tests, it's time to refactor.

\r\n

The steps of this refactoring can be seen on this Github branch: https://github.com/avdi/tapas-queue/commits/refactor-conditions

\n \
\n

Attached Files

\n ]]>
\n \ dpd-b0698a7c42a196075f5293a66a4989b23ad42c7b\n \ Thu, 02 Jan 2014 09:21:00 -0500\n \n Extracting common functionality, one step at a time\n \n
\n \n \ <![CDATA[164 Mapper]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=412\n \ \n

Today we explore a pattern for bridging the gap between different domain models.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-5231c8eb0a06d6e2bcecdda9e64c9c91958fcfb6\n \ Mon, 30 Dec 2013 09:11:00 -0500\n \n Bridging the gap between domain models\n \n \
\n \n <![CDATA[163 YAML::Store]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=410\n \ \n

Today we follow up on the last episode to talk about YAML::Store. It's like PStore, only with YAML!

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-3540e07b098dded9f1f1ab87dbf56d3692ed51a7\n \ Thu, 26 Dec 2013 10:34:00 -0500\n \n A more readable alternative to PStore\n \n \
\n \n <![CDATA[162 PStore]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=408\n \ \n

In today's episode we take a look at PStore, a simple but capable persistence mechanism that ships with Ruby.

\n
\n

Attached Files

\n ]]>
\n \ dpd-4df23d874139ddfcaedd38a7c8db0b73d9d1da32\n \ Mon, 23 Dec 2013 09:45:00 -0500\n \n A simple storage solution\n \ \n \
\n \n <![CDATA[161 Thread Local Variable]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=406\n \ \n

Today's episode introduces the concept of thread-local variables, and shows how they can be put to use in an ActiveRecord-like library.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-d011048df1e76b2bc69b5656f3bc8acac99a6639\n \ Thu, 19 Dec 2013 09:13:00 -0500\n \n Scoping variables to the current stack\n \n \
\n \n <![CDATA[160 Reduce Redux]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=403\n \ \n

Today we revisit the Enumerable#reduce method, addressing some viewer feedback about seed values as well as exploring a novel application of reduce for traversing data structures.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-54749680a9c91fefffb7307d6436c676f1e7aa51\n \ Mon, 16 Dec 2013 09:00:00 -0500\n \n More about Enumerable#reduce\n \n \
\n \n <![CDATA[159 Array Set Operations]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=400\n \ \n

Sometimes we'd like to treat Ruby arrays like sets, in which each item is unique. Today's dish shows how!

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-5d772653bde85e1c4a2e94ba5c430a76ac0fa4c3\n \ Thu, 12 Dec 2013 10:47:00 -0500\n \n Treating arrays like sets\n \n \
\n \n <![CDATA[158 Constant Lookup Scope]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=397\n \ \n

This episode takes a look at some potentially surprising rules for how Ruby looks up constants.

\r\n

Notes:

\r\n\n \
\n

Attached Files

\n ]]>
\n \ dpd-1a150f6b47f3529945a37e63b946d7a88df70c75\n \ Mon, 09 Dec 2013 09:00:00 -0500\n \n How Ruby looks up constants\n \n \
\n \n <![CDATA[157 Lockstep Testing]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=393\n \ \n

In this episode we'll explore a novel technique for testing multithreaded code.

\r\n

The lockstep library can be found here: https://github.com/avdi/lockstep

\n \
\n

Attached Files

\n ]]>
\n \ dpd-f6bd001316a691b1b2a9f1bf86c04073cde512f5\n \ Thu, 05 Dec 2013 11:09:00 -0500\n \n Testing threaded code without threads\n \n \
\n \n <![CDATA[156 Array.new]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=391\n \ \n

Today, special guest chef James Edward Gray II hosts, and shows us a thing or two about generating arrays pre-filled with values!

\n
\n

Attached Files

\n ]]>
\n \ dpd-9b2cf32a2ab987e5de30c6b7aecb2a02c29b9c48\n \ Mon, 02 Dec 2013 09:50:00 -0500\n \n With guest host James Edward Gray II\n \n \
\n \n <![CDATA[155 Matching Triples]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=388\n \ \n

Today's episode delves into some advanced regular expression features, including \"lookahead\" and \"lookbehind\".

\r\n

Notes for further exploration:

\r\n\r\n

(Clip from \"Beneath the Surface: Regular Expressions in Ruby\", CC by Nell Shamrell. Recorded by Confreaks at Lone Star Ruby Conference 2013)

\n
\n

Attached Files

\n ]]>
\n \ dpd-5f4166f631e3133b6a7fed9a8ac045a787d06173\n \ Thu, 28 Nov 2013 09:46:00 -0500\n \n An episode on advanced regular expressions\n \n \
\n \n <![CDATA[154 Testing Threads]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=386\n \ \n

Working with threads is hard enough, but getting them under test is even trickier. In this episode we start to look at techniques for verifying the logic of multithreaded code.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-d8d28b21a0dd9fa9d7f1235ef49bd9516ccdb641\n \ Mon, 25 Nov 2013 09:29:00 -0500\n \n Unit testing threaded code\n \n \
\n \n <![CDATA[153 Testing Sleep]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=383\n \ \n

We try to avoid writing slow unit tests, but what if we are testing a method whose responsibilities include waiting for a period of time? In this episode we look at a few approaches, talk about the difference between testing logic and testing system interactions, and finally settle on a strategy that leads us to a more flexible design.

\n
\n

Attached Files

\n ]]>
\n \ dpd-77d49fc0580d2bb2630cbe2210d0001d86da5570\n \ Thu, 21 Nov 2013 10:24:00 -0500\n \n How to test a method that sleeps\n \n \
\n \n <![CDATA[152 Progress Bar]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=378\n \ \n

Today we look at a gem that can make command-line scripts more pleasant to use.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-b48ee87e17ae635452b48037331278a41e8bfe3f\n \ Mon, 18 Nov 2013 09:00:00 -0500\n \n Displaying visual progress for command-line scripts\n \n
\n \n \ <![CDATA[151 Sleep]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=375\n \ \n

Today's episode tackles a subject I've always wondered about: how accurate is Kernel#sleep?

\n \
\n

Attached Files

\n ]]>
\n \ dpd-be4001143ef3181f539c1874ab0be46ba66a2896\n \ Thu, 14 Nov 2013 09:00:00 -0500\n \n To sleep(), perchance to dream\n \ \n \
\n \n <![CDATA[150 Stats]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=374\n \ \n

Sooner or later you'll need to generate statistics from a collection. In this episode, we look at how to produce min, max, sum, average, median, and standard deviation from a set of samples.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-9ca19b9d8d748f4e4bd3797c284c15a2f7869bd4\n \ Mon, 11 Nov 2013 18:29:00 -0500\n \n Deriving essential statistics in Ruby\n \n \
\n \n <![CDATA[149 Sum]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=371\n \ \n

Today's episode takes on a simple task--summing up a list of numbers--and uses it to demonstrate both the Enumerable#reduce method as well as Symbol#to_proc.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-0b353dd124bcc104e4d27214e68f53b0588b9e71\n \ Thu, 07 Nov 2013 15:49:00 -0500\n \n How to idiomatically sum up a list of numbers\n \n \
\n \n <![CDATA[148 Rake Invoke]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=368\n \ \n

This episode looks at how to invoke Rake tasks from other programs.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-3f1a8f4eea86234fa3e0b58c1db0f9ec89e511ab\n \ Mon, 04 Nov 2013 00:29:00 -0500\n \n Invoking Rake tasks from other programs\n \n \
\n \n <![CDATA[147 Atomicity]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=355\n \ \n

Today's is another threading episode. This time around, we tackle the subject of \"atomicity\", and learn about the false assumption at the root of many threading bugs.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-f8aec54a254df14453e4dbf38891640a88708f16\n \ Thu, 31 Oct 2013 09:00:00 -0400\n \n More fun with threads.\n \n \
\n \n <![CDATA[146 Monitor]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=354\n \ \n

Today we learn about the concept of a \"recursive mutex\" as we help a turtle-racing league update their software systems.

\n
\n

Attached Files

\n ]]>
\n \ dpd-378bc343078034810d85b76f50fe2945302be58a\n \ Mon, 28 Oct 2013 09:00:00 -0400\n \n It's mutexes all the way down.\n \ \n \
\n \n <![CDATA[145 Thread Pool]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=353\n \ \n

Continuing with the general theme of threads and concurrency, today we look at another pattern for splitting up work in parallel.

\n
\n

Attached Files

\n ]]>
\n \ dpd-4fa1f9cabb34858f0c7f895269af4e31cd61c994\n \ Thu, 24 Oct 2013 09:00:00 -0400\n \n Accelerating work with threads and work queues.\n \n
\n \n \ <![CDATA[144 Bulk Generation]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=352\n \ \n

In this episode we take some already-good code and make it even better.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-902579d3b227c9a603e8cd6388ac4ea403d48996\n \ Mon, 21 Oct 2013 09:00:00 -0400\n \n Refactoring from good to great.\n \n \
\n \n <![CDATA[143 Thread Interruptions]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=351\n \ \n

In this episode we finally discover why the Timeout module is unsafe, and a facility new in Ruby 2.0 that makes dealing with thread interruptions much less error-prone.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-9c155830e2908afa5e9b94b9bcaba7d8fdcaa4ca\n \ Thu, 17 Oct 2013 19:14:00 -0400\n \n Safely interrupting thread execution\n \n \
\n \n <![CDATA[142 Infinity]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=349\n \ \n

In today's episode, we'll simplify our thread-safe queue code by employing a \"benign value\" to represent the default max queue size.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-983b7a40211a2f047595d1287824ff9ec136ae17\n \ Mon, 14 Oct 2013 09:00:00 -0400\n \n Using Ruby's INFINITY constant.\n \n \
\n \n <![CDATA[141 Bounded Queue]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=346\n \ \n

We continue to evolve our thread-safe queue implementation. Today, we add the ability to set a bound on the queue size.

\n
\n

Attached Files

\n ]]>
\n \ dpd-60a9c260289709fb279c17c11aaa154a638d9038\n \ Thu, 10 Oct 2013 10:14:00 -0400\n \n We continue to evolve our thread-safe queue implementation. Today, we add the ability to set a bound on the queue size.\n \n \
\n \n <![CDATA[140 Threads are Hard]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=337\n \ \n

In this episode some bugs turn up in our thread-safe queue class.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-e25279959b422373b41db2a4aeb766689c8dd8af\n \ Mon, 07 Oct 2013 09:00:00 -0400\n \n Fixing some threading bugs\n \n \
\n \n <![CDATA[139 Timed Queue]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=336\n \ \n

Continuing to build our own thread-safe queue class, today we give it the ability to handle time out enqueues and dequeues.

\n
\n

Attached Files

\n ]]>
\n \ dpd-0137c0aa7e0a6b55c93f438b747487cbff463de2\n \ Thu, 03 Oct 2013 09:00:00 -0400\n \n Making a thread-safe queue that can time out\n \n
\n \n \ <![CDATA[138 Condition Variable]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=297\n \ \n

As we continue to explore the fundamental building blocks of multithreaded programming, today we encounter the Condition Variable, and what it has to do with my local delicatessen. 

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-98290194a4645b1edf4f716473f4226fc845c7e5\n \ Mon, 30 Sep 2013 09:00:00 -0400\n \n Guarding scarce resources in a multithreaded program\n \n
\n \n \ <![CDATA[137 Mutex]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=296\n \ \n

In this episode we introduce the concept of a \"critical section\", and learn about one of the fundamental primitives that makes multithreaded programming possible.

\n
\n

Attached Files

\n ]]>
\n \ dpd-21996b4c010e526ed3d7f3957d86a38772e9ea13\n \ Thu, 26 Sep 2013 09:00:00 -0400\n \n Keeping threads exclusive\n \ \n \
\n \n <![CDATA[136 Dead Thread]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=295\n \ \n

Concurrent programming is hard, and one of the things that makes it so hard is that a thread can unexpectedly die without giving the programmer any indication. In this episode we look at some ways to make threads fail fast and loudly while in development.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-21bfd7498b22a74536946be33751e8a849b468b3\n \ Mon, 23 Sep 2013 09:00:00 -0400\n \n Dead threads tell no tales\n \n \
\n \n <![CDATA[135 Rake MultiTask]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=294\n \ \n

In this, the final (for now) episode of the Rake miniseries, we take a look at how to speed up Rake runs by taking advantage of multiple cores.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-195820dd766a18de2c21d007e8ca5217c3e874dd\n \ Thu, 19 Sep 2013 09:00:00 -0400\n \n Building more than one file at a time\n \n \
\n \n <![CDATA[134 Rake Clean]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=291\n \ \n

My children know they are supposed to clean their place at the table after dinner. Sometimes software builds need to be cleaned up too, and today we'll learn about an optional Rake library which streamlines this process.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-1ac5d64a7ea686b228752e2db339fe2921855f89\n \ Mon, 16 Sep 2013 09:00:00 -0400\n \n Cleaning up after ourselves\n \n \
\n \n <![CDATA[133 Rake File Operations]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=290\n \ \n

Rake has a lot of tricks up its sleeve. In this episode we'll look at some of the helpers it provides for performing various common operations on files.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-a9ef808ea0d8f783b2bc0f80d6fcdf8d58197b2f\n \ Thu, 12 Sep 2013 09:00:00 -0400\n \n Making, moving, and removing files\n \n \
\n \n <![CDATA[132 Rake Pathmap]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=289\n \ \n

Today's dish is a real delicacy... we're going to dig into one of Rake's most powerful but little-known capabilities and see how we can easily transform collections of path names with the #pathmap method.

\n
\n

Attached Files

\n ]]>
\n \ dpd-8d6ff9aba4c189bf15c0883ffeadef6d99171448\n \ Mon, 09 Sep 2013 09:00:00 -0400\n \n Munging path names\n \n \
\n \n <![CDATA[131 Rake Rules]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=288\n \ \n

In this episode we learn how to write advanced Rake rules which programatically determine the appropriate prerequisites for a given target file.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-c81106d0809290345842d512bef38df3bbf30307\n \ Thu, 05 Sep 2013 09:00:00 -0400\n \n Using advanced Rake rules\n \n \
\n \n <![CDATA[130 Rake File Lists]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=287\n \ \n

As we continue our series on Rake, today we look at the Rake::FileList and how it can help us find the files we need and ignore the ones we don't.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-52119538a9dfa09f14cf08be57c49c7dbb83ff66\n \ Mon, 02 Sep 2013 09:00:00 -0400\n \n Building lists of files in Rake\n \n \
\n \n <![CDATA[129 Rake]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=286\n \ \n

Today we begin a series on the Rake build tool. We'll be getting into some powerful, lesser-known features as we continue on; but we'll start out with a quick review of Rake basics.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-f074d908690511c21a699a038946548ba2040411\n \ Thu, 29 Aug 2013 09:00:00 -0400\n \n Automating builds with Rake\n \ \n \
\n \n <![CDATA[128 Enumerable Queue]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=283\n \ \n

Queue, somewhat surprisingly, is not enumerable. Which is all the excuse we need to have some more fun with the Enumerator class, in today's episode.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-a162c36f26499c58d955ea0e5181379b2fafe96f\n \ Mon, 26 Aug 2013 14:14:00 -0400\n \n More fun with Enumerators\n \n \
\n \n <![CDATA[127 Parallel Fib]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=279\n \ \n

Last time around we introduced the Queue class. In this episode, we'll put it to work!

\n
\n

Attached Files

\n ]]>
\n \ dpd-fb3d46a18c7dbcbc56732ea447a44d40b38a60b5\n \ Thu, 22 Aug 2013 13:48:00 -0400\n \n Putting Queue to work\n \n \
\n \n <![CDATA[126 Queue]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=274\n \ \n

Today's episode introduces the Queue standard library, and shows how it can be used to coordinate threads.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-5da61d3a82ecb44c42ee79ee92b2573d3b4bf0f4\n \ Mon, 19 Aug 2013 12:17:00 -0400\n \n Introducing the Queue stdlib\n \ \n \
\n \n <![CDATA[125 And/Or]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=270\n \ \n

Ruby has both symbolic and English forms of the logical \"and\" and \"or\" operators. Which one to choose may seem like a matter of taste, but that assumption can get you into trouble.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-b196a0f7db86e64b806c330773725e6286bbcbc0\n \ Thu, 15 Aug 2013 09:00:00 -0400\n \n Understanding Ruby's logical operators.\n \n \
\n \n <![CDATA[124 Elixir]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=269\n \ \n

Today we take a brief sojourn out of Ruby and into the Elixir programming language. I'll show you one of my first programs in Elixir and point out how the language's pattern-matching abilities can contribute to some very elegant solutions.

\r\n

If this episode piques your interest in Elixir, here are some resources you can explore to find out more:

\r\n

 

\r\n\n \
\n

Attached Files

\n ]]>
\n \ dpd-b3722db3bee261d21615fa30ab86582ab0cbec9d\n \ Mon, 12 Aug 2013 10:33:00 -0400\n \n A little diversion into the Elixir language\n \n \
\n \n <![CDATA[123 Removing Debug Output]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=267\n \ \n

Have you ever had a project that spat out so much deubg output when it ran that you couldn't tell when it had a legitimate error or warning to report? In today's episode I'll show you how to quickly and easily track down the source of unwanted output.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-b0940cf027731b48f1d87ffce81d7e8305a3edf2\n \ Thu, 08 Aug 2013 12:42:00 -0400\n \n Cleaning up chatty code\n \n \
\n \n <![CDATA[122 Testing Blocks with RSpec]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=266\n \ \n

Last episode we looked at a simple way to test if a method executes a block as intended that will work in any test framework. But if your tool of choice is RSpec, there are some matchers that make these kinds of tests shorter and more declarative.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-8095a859c01f2ffb64c65c2afbb4db5c79da384c\n \ Mon, 05 Aug 2013 19:03:00 -0400\n \n Test that a method calls a block with RSpec matchers\n \n
\n \n \ <![CDATA[121 Testing Blocks]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=263\n \ \n

Today's episode answers the question: how do you test that a block passed to a method is executed as intended?

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-023a62e1e05486c411319d31a65584982ef2fbf7\n \ Thu, 01 Aug 2013 16:28:00 -0400\n \n Testing that a block is called\n \n \
\n \n <![CDATA[120 Outside-In]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=258\n \ \n

In today's episode I tackle the question of how many tests are enough. I'll show how for me, it's all about how long it's been since I last got new information from a test.

\n
\n

Attached Files

\n ]]>
\n \ dpd-2388a51e93b0d526bf95d182aeac114ab948a66e\n \ Mon, 29 Jul 2013 11:05:00 -0400\n \n How many tests are enough?\n \n \
\n \n <![CDATA[119 Intention Revealing Argument]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=257\n \ \n

In this episode, we examine the pros and cons of a few of techniques for making boolean flags passed to methods read more meaningully.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-a1d3de228a3a52700640f46f7b0fb3dffcf26e51\n \ Thu, 25 Jul 2013 09:00:00 -0400\n \n Making boolean flags more readable\n \n \
\n \n <![CDATA[118 Even and Odd]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=256\n \ \n

A small dish today, about some numeric methods I often forget exist.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-b25afd6d5232a963a1c9d170a5b5054cde28fc08\n \ Mon, 22 Jul 2013 09:00:00 -0400\n \n Making full use of Ruby's numeric interfaces\n \n \
\n \n <![CDATA[117 Client Session Object]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=255\n \ \n

In another peek into my ongoing behind-the-scenes work on RubyTapas, today I extract the responsibility for representing a logged-in screen-scraping user-agent session into its own class.

\n
\n

Attached Files

\n ]]>
\n \ dpd-bd0d9a5836f76506297539220dbcede9441fdc68\n \ Thu, 18 Jul 2013 09:00:00 -0400\n \n Extracting the responsibility for managing a client session\n \n
\n \n \ <![CDATA[116 Extract Command Object]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=252\n \ \n

In today's live-recorded episode I show one of the most fundamental refactorings for breaking up a large class into smaller classes.

\r\n

Since this is a live episode there is no transcript today. The Naught codebase can be found at http://github.com/avdi/naught

\n \
\n

Attached Files

\n ]]>
\n \ dpd-cd8c9fa90354a3fb8a820c8817af7b3fedd62be4\n \ Mon, 15 Jul 2013 10:09:00 -0400\n \n Introducing a fundamental refactoring\n \n \
\n \n <![CDATA[115 pp]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=249\n \ \n

In this quick live episode I demonstrate a standard library that improves on Kernel#p

\n
\n

Attached Files

\n ]]>
\n \ dpd-ed1f8444e7324ddd7f5fcb1b82670314fc4c228e\n \ Thu, 11 Jul 2013 17:28:00 -0400\n \n An improvement on p\n \ \n \
\n \n <![CDATA[114 Null Object]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=248\n \ \n

In today's episode, we encounter a starship in trouble, and a software pattern that helps cleanly disable whole categories of behavior.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-517ebdcd900aef1fb627b510e268b5b0766312ba\n \ Mon, 08 Jul 2013 09:12:00 -0400\n \n Something for nothing\n \n \
\n \n <![CDATA[113 p]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=247\n \ \n

We all use 'puts' for debugging at some point. This episode shows why 'p' is a better choice.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-393a580ee3eb22b9d293d03f6063c40ddc6a78b3\n \ Thu, 04 Jul 2013 10:58:00 -0400\n \n Better debugging with p\n \ \n \
\n \n <![CDATA[112 Special Case]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=246\n \ \n

In this episode we take a look at the Special Case pattern, and see how it can be used to drastically simplify some typical logic around logged-in and anonymous users.

\n
\n

Attached Files

\n ]]>
\n \ dpd-73e7ec16a6fd36fa485025da0689ddd53853a372\n \ Mon, 01 Jul 2013 10:50:00 -0400\n \n A pattern to help you avoid null checks\n \n \
\n \n <![CDATA[111 Symbol Placeholder]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=245\n \ \n

Nobody wants to debug an \"undefined method for NilClass\" error. Today's episode shows a little trick for making these errors more meaningful.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-c7017f8a590d23c446057fba4982c2de327df590\n \ Thu, 27 Jun 2013 09:53:00 -0400\n \n A cheap way to improve on nil\n \n \
\n \n <![CDATA[110 Catch and Throw]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=243\n \ \n

A refactoring episode, showing how to replace exceptions-as-flow-control with the \"catch\" and \"throw\" methods.

\n
\n

Attached Files

\n ]]>
\n \ dpd-896112d014236d1a7fea685c5ef56b257ae01a01\n \ Mon, 24 Jun 2013 10:53:00 -0400\n \n Cleanly signaling early termination.\n \n \
\n \n <![CDATA[109 SAX]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=242\n \ \n

This episode takes a look at how to use the Nokogiri gem to extract data from large HTML documents without reading the whole document into memory.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-9f861cc2f61b720cdf347ad66980f9f3a46d5b90\n \ Thu, 20 Jun 2013 09:00:00 -0400\n \n Efficient XML/HTML processing in Ruby\n \n \
\n \n <![CDATA[108 The Trouble with nil]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=241\n \ \n

This episode explores some of the many ways we can come across a nil value, and why that's a problem.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-14d94c350aef7d4289733c8e465946aadbacdad4\n \ Mon, 17 Jun 2013 09:10:00 -0400\n \n nil is nobody's friend\n \n \
\n \n <![CDATA[107 String Subscript Assignment]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=240\n \ \n

In today's episode: super-powered string-munging with regexes and the square-bracket operator.

\n
\n

Attached Files

\n ]]>
\n \ dpd-682990fcd3ae769be185826bf839d0a949013a91\n \ Thu, 13 Jun 2013 09:00:00 -0400\n \n Flipping the \"more awesome\" switch on strings.\n \n
\n \n \ <![CDATA[106 Class Accessors]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=239\n \ \n

Today, some thoughts on writing class-level attribute accessors.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-395add83752da27b9b57117498ca86776bd59788\n \ Mon, 10 Jun 2013 12:01:00 -0400\n \n Some thoughts on writing class-level attribute accessors\n \n
\n \n \ <![CDATA[105 Checking for a Terminal]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=238\n \ \n

In this episode we'll mimic the behavior of command-line tools like Git that automatically page their output if they detect that they are being run from a terminal.

\n
\n

Attached Files

\n ]]>
\n \ dpd-f611f249d4e6be60ac8cd798d2142f6fc8588345\n \ Thu, 06 Jun 2013 09:00:00 -0400\n \n How to tell if a program is being executed at the console\n \n \
\n \n <![CDATA[104 Parsing Time]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=237\n \ \n

It would be great if time were always represented in readable, un-ambiguous ISO8601 formats. But  when importing legacy data we don't often have that luxury. In this episode, we look at some tools for parsing various time and date representations.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-a4ae297ee9ef49a95a894641904a35a9d03ad886\n \ Mon, 03 Jun 2013 10:13:00 -0400\n \n Parsing time formats, simple and not-so-simple\n \n \
\n \n <![CDATA[103 Gem-Love Part 11]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=234\n \ \n

Finishing up my list of TODO items, today I address some structural coupling that my test stubs have revealed.

\r\n

This is a live episode, so no script today.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-50012ca14dc68dfdf487252cb75b8fc2e6303d0a\n \ Thu, 30 May 2013 09:00:00 -0400\n \n Addressing structural coupling in the Endorsement class\n \n
\n \n \ <![CDATA[102 Gem-Love Part 10]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=233\n \ \n

Note: This week RubyTapas moves to its new release schedule of two episodes a week. I've already sent out an update detailing the reason for the change, but for those who have disabled email updates, I've also added a note to the FAQ here: http://www.rubytapas.com/faq#frequency-change

\r\n

Working through my TODO list from the recent feature additions, in today's episode I seek to resolve the semantic conflict between User and GemUser through refactoring.

\r\n

This is a live episode, so no script today.

\r\n

Code here: https://github.com/avdi/gem-love/tree/rubytapas-episode-102

\n \
\n

Attached Files

\n ]]>
\n \ dpd-8584e25f46fe0b72784fd41f145a9708dea4c972\n \ Mon, 27 May 2013 09:00:00 -0400\n \n Refactoring the GemUser class\n \n \
\n \n <![CDATA[101 Intention Revealing Message]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=211\n \ \n

Not all refactoring is about eliminating duplication. In today's episode we'll refactor some code for the purpose of revealing intent to future readers.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-3dcf247d389eef3f58afbfc0dc451f0f9129885f\n \ Fri, 24 May 2013 09:00:00 -0400\n \n Refactoring for readability\n \n \
\n \n <![CDATA[100 Screen-Scraping Gateway]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=210\n \ \n

Today's episode tackles the problem of talking to a web app which has no published API, using the Mechanize screen-scraping gem. In the process, we'll explore the Gateway pattern for encapsulating external resources.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-d46bdd017dbe03ce0d94dda4ed0a99722c37f50a\n \ Wed, 22 May 2013 09:00:00 -0400\n \n Encapsulating external resources\n \n \
\n \n <![CDATA[099 String Subscript Regex]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=208\n \ \n

Today the focus is on a convenient way to extract substrings using regular expressions. We glossed over this technique briefly in an earlier episode, but now it's front and center.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-f08ee5119333a8be5294e58156b7b14964b0435a\n \ Mon, 20 May 2013 09:00:00 -0400\n \n Extracting substrings with regular expressions\n \n
\n \n \ <![CDATA[098 Gem-Love Part 9]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=205\n \ \n

In today's concluding slice of this live-coding session, I wrap up my work on adding users to Gem-Love by TDDing the server side support for API keys.

\r\n

This is a live episode, so no script today.

\r\n

Source: https://github.com/avdi/gem-love/tree/rubytapas-095

\n \
\n

Attached Files

\n ]]>
\n \ dpd-68c25db3852e31f2b7e40869a46e939900b5cf91\n \ Fri, 17 May 2013 09:00:00 -0400\n \n Server-side auth token support\n \n \
\n \n <![CDATA[097 Gem-Love 8]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=204\n \ \n

As I continue to add the concepts of users to Gem-Love, I tackle the problem of making the client side auth-token aware.

\r\n

This is a live episode, so there is no script today.

\r\n

Source: https://github.com/avdi/gem-love/tree/rubytapas-095

\n \
\n

Attached Files

\n ]]>
\n \ dpd-a88b288195ec73b9aa84997023ec2f64bcc13588\n \ Wed, 15 May 2013 09:00:00 -0400\n \n Building client-side support for auth tokens\n \n
\n \n \ <![CDATA[096 Gem-Love 7]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=203\n \ \n

In this continuation of episode 95, I finish writing new acceptance test helpers, and get to my first proper test failure.

\r\n

This is a live episode, so no script today.

\r\n

Source here: https://github.com/avdi/gem-love/tree/rubytapas-095

\n \
\n

Attached Files

\n ]]>
\n \ dpd-3c6b48a8155a94d70564e33e8de165048eb6f4e0\n \ Mon, 13 May 2013 09:00:00 -0400\n \n More acceptance test helpers\n \n \
\n \n <![CDATA[095 Gem-Love Part 6]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=198\n \ \n

Diving back into my Gem-Love project, today I begin the process of adding users to the system. Because this is a larger feature, it will be split across multiple episodes. However, since they are all parts of the same programming session, I'll be running them back-to-back to preserve continuity.

\r\n

This episode was cut from a live recording, so there is no script available.

\r\n

The code can be found here: https://github.com/avdi/gem-love/tree/rubytapas-095

\n \
\n

Attached Files

\n ]]>
\n \ dpd-fe4eb328f1e517162477045804b70aaa79c35d95\n \ Fri, 10 May 2013 09:00:00 -0400\n \n Adding users to a client/server app\n \n \
\n \n <![CDATA[094 Bang Bang]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=197\n \ \n

Have you ever found yourself looking for a '#to_bool' method in Ruby? This episode explores why boolean conversion isn't needed as often as you might think; as well as how to accomplish it when it IS called for.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-73d319c58040c5b0120ea48183dd1a22d453e5c9\n \ Wed, 08 May 2013 09:00:00 -0400\n \n Converting arbitrary values to booleans\n \n \
\n \n <![CDATA[093 Boolean]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=196\n \ \n

A common question on Ruby forums and mailing lists is: \"why is there no Boolean data type?\". This episode attempts to answer that question.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-a724bd6408235ac38fe0dbfcc29b83994d63443c\n \ Mon, 06 May 2013 09:00:00 -0400\n \n Why Ruby has no Boolean type\n \ \n \
\n \n <![CDATA[092 Coincidental Duplication Redux]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=185\n \ \n

Katrina Owen contributed an example of coincidental duplication I liked so much I decided to make a second episode about it. Enjoy!

\n
\n \

Attached Files

\n ]]>
\n \ dpd-5b84a418456992f342a46fe896aa2835b09bd7f4\n \ Fri, 03 May 2013 09:00:00 -0400\n \n Another example of over-DRYing code\n \n \
\n \n <![CDATA[091 Ruby 2.0: Rebinding Methods]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=184\n \ \n

In this, the first (but far from the last!) RubyTapas episode to focus on a Ruby 2.0 feature, we look at the implications of Ruby's newly relaxed rules for binding method objects to objects.

\n
\n

Attached Files

\n ]]>
\n \ dpd-6d3a819ae521e9330e2e3513929ae39fe2e7bce2\n \ Wed, 01 May 2013 09:00:00 -0400\n \n Temporarily adding methods to objects\n \n \
\n \n <![CDATA[090 class << self]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=182\n \ \n

Ruby gives as a number of options when it comes to defining class methods. This episode starts by demonstrating three of them, and then gets a bit opinionated.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-634a821f59b6de6261682efb3aa9d197e945e7fa\n \ Sun, 28 Apr 2013 09:00:00 -0400\n \n What's the best way to define class methods?\n \n \
\n \n <![CDATA[089 Coincidental Duplication]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=181\n \ \n

The DRY principle teaches us to get rid of duplication wherever we find it. But not all duplication is created equal...

\n
\n

Attached Files

\n ]]>
\n \ dpd-c0f31018d214f1e7af311f2b21a239820d658add\n \ Fri, 26 Apr 2013 09:00:00 -0400\n \n Can code be too DRY?\n \n \
\n \n <![CDATA[088 Gem-Love Part 5]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=180\n \ \n

In this continuation of the Gem-Love project, I complete the end-to-end acceptance test, verifying that the client can talk to the server.

\r\n

Source code here: https://github.com/avdi/gem-love/tree/rubytapas-088

\r\n

Just a reminder, I'm collecting feedback so I can make RubyTapas even better! If you want your voice to be heard, fill out the survey: http://shiprise.wufoo.com/forms/r7x2q5/

\n \
\n

Attached Files

\n ]]>
\n \ dpd-f67ca2ecd81c65e6d38400c624b7e0dee6eade92\n \ Wed, 24 Apr 2013 09:00:00 -0400\n \n Integration client against server\n \n \
\n \n <![CDATA[087 Naming: Head Count]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=179\n \ \n

Naming things is hard, and it's difficult to come up with hard-and-fast rules for it. But it can be helpful to learn from examples of real-world naming decisions that led to beneficial design changes. In this episode, we'll look at a method name change that clarified object responsibilities in a program dealing with live events.

\n
\n

Attached Files

\n ]]>
\n \ dpd-2e89317b104a5c5d70140fdbd67065526dca00b2\n \ Mon, 22 Apr 2013 09:00:00 -0400\n \n Renaming a method, for great justice\n \n \
\n \n <![CDATA[086 Naked Splat]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=178\n \ \n

In this episode we tackle how to override base class methods while cleanly passing through multiple base-class arguments.

\r\n

Notes:

\r\n\n
\n

Attached Files

\n ]]>
\n \ dpd-9109930112eb1ba7eadc0f2019eb7d6f42adc9bb\n \ Fri, 19 Apr 2013 09:00:00 -0400\n \n Ignoring many arguments\n \n \
\n \n <![CDATA[085 Ignore Arguments]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=177\n \ \n

Sometimes a block or method may receive arguments that you just don't care about. This episode introduces the idiomatic Ruby way to indicate that certain arguments should be ignored.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-9ee8bd40538092def429602944acf4e196044389\n \ Wed, 17 Apr 2013 09:00:00 -0400\n \n Some arguments are more interesting than others\n \n \
\n \n <![CDATA[084 Splat Group]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=176\n \ \n

Today we take a look at a related feature to Ruby's \"splat\" operator: the ability to recursively destructure arrays using grouped assignment.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-7806cd5b0701651c5881286d5ef3ac4c54c41a58\n \ Mon, 15 Apr 2013 09:00:00 -0400\n \n Pulling values out of nested arrays\n \n \
\n \n <![CDATA[083 Custom Splat]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=172\n \ \n

In today's episode we discover how to make an arbitrary object implicitly \"splat\"-able.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-701acce5f846152bcdd9a623ae7330f26375fc36\n \ Fri, 12 Apr 2013 09:00:00 -0400\n \n Making arbitrary objects splattable\n \n \
\n \n <![CDATA[082 Inline Assignment]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=171\n \ \n

It's a small thing, but judicious use of inline assignment can make code more DRY and concise. In this episode we look at how to do it, and when.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-44c663f403d44b29e9c93287e68ef3c9aaa98e08\n \ Wed, 10 Apr 2013 09:00:00 -0400\n \n To assign inline is sometimes divine\n \n \
\n \n <![CDATA[081 Implicit Splat]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=169\n \ \n

Delving further into Ruby's destructuring assignment, or \"splat\", mechanism, today we look at some cases where Ruby performs splatting without an explicit '*' operator.

\n
\n

Attached Files

\n ]]>
\n \ dpd-e3cee3be5a344a86c6339dd6ffce43ea1e81bc88\n \ Mon, 08 Apr 2013 09:00:00 -0400\n \n Splatting without the splat\n \n \
\n \n <![CDATA[080 Splat Basics]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=168\n \ \n

Ruby supports a limited form of destructuring assignment, in the form of the \"splat\" operator. In today's episode we go over splat basics, in order to lay a foundation for more advanced splatting techniques to come.

\n
\n

Attached Files

\n ]]>
\n \ dpd-208821d167fae5fc59cff742c7393668c9dc8603\n \ Fri, 05 Apr 2013 09:00:00 -0400\n \n Splatting out collections, and slurping them back up\n \n
\n \n \ <![CDATA[079 Concat]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=167\n \ \n

There is more than one way to concatenate arrays, but not all ways are created equal. In this episode we'll compare three approaches in terms of both semantics and efficiency.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-4cd280308ca7e64a9f7c03faf4ebc524d0e3214d\n \ Wed, 03 Apr 2013 09:00:00 -0400\n \n Appending arrays to other arrays\n \ \n \
\n \n <![CDATA[078b Java Dregs: Double Brace Initialization]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=183\n \ \n

We all know Java is the best programming lanagueg out there, but maybe you've seen the literal syntax some lesser languages have for initializing data structures like arrays and maps. In this episode we'll look at a technique for concisely initializing Java data structures with values.

\n
\n

Attached Files

\n ]]>
\n \ dpd-b3f0f611592fb3d7fea31d8c78f16b8452d9e09b\n \ Mon, 01 Apr 2013 09:00:00 -0400\n \n Concisely initializing data structures in Java\n \n
\n \n \ <![CDATA[078 Tail Part 7: Cooperating Objects]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=166\n \ \n

In a final refactoring to our pseudo-tail(1), we use an enumerator to encapsulate the process of searching text chunks for newlines.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-add8dcd3bbdb9b1f5b1e7ed467b8d949ef7d23d2\n \ Fri, 29 Mar 2013 09:00:00 -0400\n \n The return of Enumerator\n \n \
\n \n <![CDATA[077 Tail Part 6: Process as Object]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=159\n \ \n

Continuing to refactor our minimal tail(1) implementation, today we clean up a loop by encapsulating its state in a new object.

\n
\n

Attached Files

\n ]]>
\n \ dpd-26781adbe5c9d2894edf6e7bbc5e2dbb8e06b5aa\n \ Wed, 27 Mar 2013 09:00:00 -0400\n \n Representing a process as an object\n \n \
\n \n <![CDATA[076 Tail Part 5: Idiom]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=158\n \ \n

So far, our reimplementation of tail(1) bears a striking resemblance to the style of code we might find in the C implementation of the same utility. In this episode we'll make the code a little more Rubyish by extracting loops into block methods.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-01a0e2b96221ca7918e55ae7dc87d65ca8df65e2\n \ Mon, 25 Mar 2013 09:00:00 -0400\n \n From C to Ruby, one block at a time.\n \n \
\n \n <![CDATA[074 Tail Part 3: #rindex]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=155\n \ \n

We've figured out how to read chunks of text backwards from a file, now it's time to tackle searching that text for the beginnings of lines.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-90ba8fad69a3a48e0c799e7976cbe481d8ca81d0\n \ Wed, 20 Mar 2013 09:00:00 -0400\n \n Searching backwards in strings\n \n \
\n \n <![CDATA[075 Tail Part 4: copy_stream]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=157\n \ \n

Marching right along in our reimplementation of UNIX tail(1), today we tackle the problem of dumping the tail of the file to STDOUT once we've found the starting point.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-607d4614ad85141c2c3f4186248e18158f8da9bb\n \ Wed, 20 Mar 2013 09:00:00 -0400\n \n Efficiently channeling data between filehandles\n \n
\n \n \ <![CDATA[073 Tail Part 2: Do-While]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=151\n \ \n

As we continue to rewrite a subset of the UNIX tail(1) command, we learn how to write a do...while loop in Ruby.

\n
\n

Attached Files

\n ]]>
\n \ dpd-7393ef6c5a710e2899305f3f6830daff2fc62ba1\n \ Mon, 18 Mar 2013 09:00:00 -0400\n \n Ruby doesn't have a do-while loop... or does it?\n \n
\n \n \ <![CDATA[072 Tail Part 1: Random Access]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=149\n \ \n

This episode kicks off a short miniseries on re-implementing a small subset of the UNIX \"tail\" command in Ruby. We'll start by learning how to jump around to arbitrary positions in a file.

\n
\n

Attached Files

\n ]]>
\n \ dpd-a2fbb06ba2347709b576c2c49f3fac010dd5b0e2\n \ Fri, 15 Mar 2013 09:00:00 -0400\n \n Reading files from points other than the beginning\n \n
\n \n \ <![CDATA[071 break with a Value]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=147\n \ \n

In the second of two episodes about the 'break' keyword, we discover how to override method return values to suit our own needs.

\n
\n

Attached Files

\n ]]>
\n \ dpd-07b75fa2401b9e11fb58540ed6dfbe81c5773ecb\n \ Wed, 13 Mar 2013 09:00:00 -0400\n \n Overriding method return values with break\n \n \
\n \n <![CDATA[070 break]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=146\n \ \n

In this, the first of two episodes on the 'break' keyword, we look at how it can be applied beyond breaking out of loops.

\n
\n

Attached Files

\n ]]>
\n \ dpd-9d4929804b882799685e82fe563cac3ad7fc237b\n \ Mon, 11 Mar 2013 09:00:00 -0400\n \n The break keyword is for more than just loops\n \n \
\n \n <![CDATA[069 Gem-Love Part 4]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=145\n \ \n

The last Gem-Love episode had me creating a command-line client. This time around, I use Rack and Sinatra to build a simple server.

\r\n

Source code can be found here: https://github.com/avdi/gem-love/tree/rubytapas-069

\n \
\n

Attached Files

\n ]]>
\n \ dpd-9fba79d538478986e44ec786f4bac99ca7cd53b4\n \ Fri, 08 Mar 2013 09:00:00 -0500\n \n Creating a Sinatra-based server\n \n \
\n \n <![CDATA[068 Display Builder]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=140\n \ \n

In some applications there is a many-to-many relationship between types of objects that may be displayed to users, and formats in which they may be rendered. There may even be multiple display styles within a given format: for instance, a \"summary\" style and a \"detailed\" style. In this episode well look at one possible option for decoupling what is being displayed from how it is displayed.

\n
\n

Attached Files

\n ]]>
\n \ dpd-0bf68eec152306a59ce0f3db2d968257f37d689e\n \ Wed, 06 Mar 2013 09:00:00 -0500\n \n Decoupling what is displayed from how\n \n \
\n \n <![CDATA[067 Moneta]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=144\n \ \n

In the last episode we gave a class the ability to cache HTTP responses in a Hash or something that behaves like a Hash. Now we'll introduce the Moneta gem, a library that abstracts many different key-value stores behind a uniform, Hash-like interface.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-80a24b3b8107df28c3c2a06a11e539c411b232b0\n \ Mon, 04 Mar 2013 09:00:00 -0500\n \n Pluggable cache backends\n \ \n \
\n \n <![CDATA[066 Caching an API]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=139\n \ \n

Web services are slow to request and often have rate caps. A cache can cut down on requests to remote APIs. But how best to design the interface between the code that uses an API, and the caching layer? In this episode we'll explore that question.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-b6e32891120fefa23356d574ef0dd778d21479d5\n \ Fri, 01 Mar 2013 09:00:00 -0500\n \n Caching the results of web service requests\n \n \
\n \n <![CDATA[065 PulseFFI Part 7]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=138\n \ \n

This is the culmination of the last few PulseFFI episodes. I'll add some insurance to the high-level PulseFFI.mainloop method, and then make use of it in the proof-of-concept script. Finally, I'll discuss some of my philosophy for building library APIs.

\r\n

Browse the source code here: https://github.com/avdi/pulse-ffi/tree/rubytapas-065

\r\n

Sorry, no transcript for this live-recorded episode.

\n
\n

Attached Files

\n ]]>
\n \ dpd-158461b263d469ef6c0835bf22ac3f9a1df2801f\n \ Wed, 27 Feb 2013 09:00:00 -0500\n \n Wrapping up the PulseFFI.mainloop method\n \n \
\n \n <![CDATA[064 Yield or Enumerate]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=137\n \ \n

A number of Ruby standard library methods return an Enumerator if no block is passed to them. In this episode, we'll learn how to emulate this behavior in our own methods.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-a4fc7c30d5362f2c35988031eee0548e0d7fe41c\n \ Mon, 25 Feb 2013 09:00:00 -0500\n \n Making iterative methods more flexible\n \n \
\n \n <![CDATA[063 Gem-Love Part 3]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=136\n \ \n

Returning to my Gem-Love project in this episode, I use TDD to drive out a client-side implementation for the first feature. In the process, I talk about message-first design.

\r\n

Check out the source here: https://github.com/avdi/gem-love/tree/rubytapas-063

\n \
\n

Attached Files

\n ]]>
\n \ dpd-ac8346ea71f6889985ef19c1eb79e5af52f0afd3\n \ Fri, 22 Feb 2013 09:00:00 -0500\n \n Implementing the first feature\n \n \
\n \n <![CDATA[062 Fiber]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=135\n \ \n

In this episode we'll explore Ruby's lightweight concurrency primitive by using it to emulate the Enumerator class.

\n
\n

Attached Files

\n ]]>
\n \ dpd-168c76fd7441959ad2e0a3e797a4a8ca47505efe\n \ Wed, 20 Feb 2013 09:00:00 -0500\n \n Rebuilding Enumerator with Ruby's Fibers\n \n \
\n \n <![CDATA[061 PulseFFI Part 6]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=134\n \ \n

Continuing in my extraction of a high-level API for the PulseFFI library, in this episode I test-drive the Mainloop.run, a composed method that brings together the work I've done so far.

\r\n

Browse the source code here: https://github.com/avdi/pulse-ffi/tree/rubytapas-061

\r\n

Sorry, no transcript for this live-recorded episode.

\n
\n

Attached Files

\n ]]>
\n \ dpd-0009ddedd5ab01bb56900a780df61249e65e1d9e\n \ Mon, 18 Feb 2013 09:00:00 -0500\n \n Tying together the PulseFFI::Mainloop class\n \n \
\n \n <![CDATA[060 Ascend]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=133\n \ \n

Now that we've been formally introduced to the Enumerator class, it's time to apply that knowledge in a concrete way. In this episode we use Pathname and Enumerator to locate a project-wide econfiguration file.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-9ecbcfa32c265d69e62779e1f332fa8f567a7d42\n \ Fri, 15 Feb 2013 09:00:00 -0500\n \n Searching directory ancestors with Pathname and Enumerator\n \n \
\n \n <![CDATA[059 Enumerator]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=132\n \ \n

In previous episodes we've touched on Enumerator in passing. This time it'll take center stage, as we explore just what an Enumerator is and what it's good for.

\n
\n

Attached Files

\n ]]>
\n \ dpd-20d31f438b46a88cdb8fbb077c29fb9065d11b96\n \ Wed, 13 Feb 2013 09:00:00 -0500\n \n An intro to the Enumerator class\n \n \
\n \n <![CDATA[058 ARGF]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=131\n \ \n

This episode explores ARGF, a powerful object for working with input files in command-line programs.

\n
\n

Attached Files

\n ]]>
\n \ dpd-de334c2d9fcbe0d4e523ee688f745893810163bf\n \ Mon, 11 Feb 2013 09:00:00 -0500\n \n Easily work with command-line input files\n \n \
\n \n <![CDATA[057 PulseFFI Part 5]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=130\n \ \n

In this episode I continue to felsh out the high-level API for my PulseFFI library, using TDD and mock objects to define how the object model interacts with the C bindings layer. I also compare the MiniTest and RSpec mocking/stubbing libraries.

\r\n

Browse the source for this episode at: https://github.com/avdi/pulse-ffi/tree/rubytapas-057

\r\n

Sorry, no transcript for this live-recorded episode.

\n
\n

Attached Files

\n ]]>
\n \ dpd-e7462bafbdfaf370e4e679fb895ab51b94ac91ec\n \ Fri, 08 Feb 2013 09:00:00 -0500\n \n TDDing the PulseFFI API\n \ \n \
\n \n <![CDATA[056 xmpfilter]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=129\n \ \n

In this, the most requested RubyTapas episode ever, we'll take a look at xmpfilter, the program I use to evaluate Ruby expressions from within my editor.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-5c274a7c86342ed6c1b5bcb997350c4d8fbd9ddb\n \ Wed, 06 Feb 2013 09:00:00 -0500\n \n Evaluating Ruby code from the comfort of your editor\n \n
\n \n \ <![CDATA[055 Runnable Library]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=128\n \ \n

It's a library! No, it's an executable! Relax, your Ruby files can be both require-able libraries and executables!

\n
\n

Attached Files

\n ]]>
\n \ dpd-401ec9576c108e2540c2663ac714888036e0abff\n \ Mon, 04 Feb 2013 09:00:00 -0500\n \n Making libraries double as executables\n \n \
\n \n <![CDATA[054 PulseFFI Part 4]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=127\n \ \n

In this edition of a miniseries on building a wrapper library using FFI, I begin to sketch out a higher-level API for starting up a PulseAudio mainloop.

\r\n

Source code here: https://github.com/avdi/pulse-ffi/tree/rubytapas-054

\n \
\n

Attached Files

\n ]]>
\n \ dpd-beb8343869177ba4b797e57568cb15ea5c7bff76\n \ Fri, 01 Feb 2013 09:00:00 -0500\n \n Sketching out a higher level API\n \n \
\n \n <![CDATA[053 Selectively Run Tests]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=126\n \ \n

Selectively running tests can be a lifesaver in a large codebase. This episode covers how to run just the tests you care about in both MiniTest and RSpec - including how to slectively run tests under Rake.

\n
\n

Attached Files

\n ]]>
\n \ dpd-509ccdfeab8e6c49bf86a6e35914e297ca6a754f\n \ Wed, 30 Jan 2013 09:00:00 -0500\n \n Running just the tests you care about\n \n \
\n \n <![CDATA[052 The End of Mocking]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=125\n \ \n

At the borders of our systems, mockist testing hits a point of diminishing returns. In this episode we take a look at when to stop mocking and start integration testing.

\n
\n

Attached Files

\n ]]>
\n \ dpd-e4bd3090f74ad373eaf12ec5eba21bb3cb37c4eb\n \ Mon, 28 Jan 2013 09:00:00 -0500\n \n Mock objects and the point of diminishing returns\n \n
\n \n \ <![CDATA[051 PulseFFI Part 3]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=124\n \ \n

In this \"live-style\" episode, I begin to transition the PulseFFI proof-of-concept script into a Ruby library, using my smoke test to ensure everything continues to work.

\r\n

Browse the source code here: https://github.com/avdi/pulse-ffi/tree/rubytapas-051

\r\n

Sorry, no transcript for this live recording.

\n
\n

Attached Files

\n ]]>
\n \ dpd-1716f6062421e79fb3ee9afbd7509e261c9d0903\n \ Fri, 25 Jan 2013 09:00:00 -0500\n \n Transitioning the proof-of-concept to a library\n \n \
\n \n <![CDATA[050 Include Namespace]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=122\n \ \n

Sometimes a utility module is overkill. In this episode, we make a namespace module do double-duty.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-362159e0e87e6ee6f476b67542b359d0c07c1630\n \ Wed, 23 Jan 2013 09:00:00 -0500\n \n Sometimes a utility module is more than you need\n \n
\n \n \ <![CDATA[049 Utility Function]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=121\n \ \n

Some functions are useful in many different contexts. In this episode we'll explore some ways to make them available both to library code and to client code of a library.

\n
\n

Attached Files

\n ]]>
\n \ dpd-108d67ea8bdb834c2ad70884deeb95b0baa32a0d\n \ Mon, 21 Jan 2013 09:00:00 -0500\n \n Some functions are useful in many different contexts. In this episode we'll explore some ways to make them available both to library code and to client code of a library.\n \ \n \
\n \n <![CDATA[048 Memoize]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=119\n \ \n

This episode covers how to write a \"macro\"-a method that generates or modifies other methods-using the classic example of memoizing method results.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-b989f1d65dd31b321521d212d7e17fe2587674f1\n \ Fri, 18 Jan 2013 09:00:00 -0500\n \n A macro for caching method results\n \n \
\n \n <![CDATA[047 FFI Part 2]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=118\n \ \n

In part 2 of this series, we buy ourselves some peace of mind with a simple smoke test.

\r\n

The code for this episode can be found here: https://github.com/avdi/pulse-ffi/tree/RubyTapas047

\n \
\n

Attached Files

\n ]]>
\n \ dpd-b5f40d344136af810b3ee0c2fbffeb687f827f83\n \ Wed, 16 Jan 2013 15:13:00 -0500\n \n Adding a smoke test\n \n \
\n \n <![CDATA[046 Gem-Love Part 2]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=117\n \ \n

In Part 2 of this series chronicling the creation of an app, I establish a test-driven rhythm, starting with a high-level acceptance test.

\r\n

The source code for this episode is available here: https://github.com/avdi/gem-love/tree/rubytapas-episode-046

\n \
\n

Attached Files

\n ]]>
\n \ dpd-754cda16c11a4fbcbadc050413a525011d5fca37\n \ Mon, 14 Jan 2013 09:00:00 -0500\n \n Kickstarting the BDD rhythm\n \n \
\n \n <![CDATA[045 Hash Default Value]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=115\n \ \n

This cautionary episode demonstrates and explains a Hash gotcha that often comes as a surprise.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-2bf9da2d7d99d17c501de016354d559a3b21e4b2\n \ Fri, 11 Jan 2013 14:07:00 -0500\n \n A surprising Hash gotcha\n \n \
\n \n <![CDATA[044 #one?]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=114\n \ \n

Somtetimes you need to know if exactly one element in a collection has a given property. For that situation, we have the #one? predicate method.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-7685b2efcbb4cfb6e7c312b1653a0811ec2e82c3\n \ Wed, 09 Jan 2013 09:00:00 -0500\n \n Finding if one and only one element matches\n \n \
\n \n <![CDATA[043 Exclusive Or]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=113\n \ \n

This episode looks at Ruby's logical XOR operator, and how it can be useful in checking that a method was called with the correct optional arguments.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-d39799f2377eee0af56b5d6a570d414221d5a30d\n \ Mon, 07 Jan 2013 09:00:00 -0500\n \n Asserting A or B but not both\n \n \
\n \n <![CDATA[042 Streaming]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=112\n \ \n

Does code optimized for RAM usage need to be ugly? Find out, in today's episode!

\n \
\n

Attached Files

\n ]]>
\n \ dpd-bcab43eb06bfdb1b8465e5933d17e9cf13dcdf3c\n \ Fri, 04 Jan 2013 09:00:00 -0500\n \n Processing big data without maxing out memory\n \n
\n \n \ <![CDATA[041 String#scan]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=110\n \ \n

Today's episode looks at a convenient tool for weeding through text and pulling out parts that match a given pattern.

\n
\n

Attached Files

\n ]]>
\n \ dpd-9da4c6910bdc6dca3e86ab619280234f81455281\n \ Wed, 02 Jan 2013 09:00:00 -0500\n \n Using String#scan to comb through text\n \n \
\n \n <![CDATA[040 Gradual Stiffening]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=109\n \ \n

In today's episode we'll try to come to grips with a slippery quality---a quality which is of importance to differentiating code that is easy to evolve, from code that isn't. Along the way, we'll write some Ruby code that looks disturbingly like Perl!

\n
\n

Attached Files

\n ]]>
\n \ dpd-88dc7ce35680aa8b3f08e5aa576c8a1b77969546\n \ Mon, 31 Dec 2012 09:00:00 -0500\n \n From a one-off script to a reusable method in tiny steps\n \n
\n \n \ <![CDATA[039 Gem-Love Part 1]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=108\n \ \n

In this, the first of another ongoing occasional series, I revisit an open-source application I started three years ago and begin to rewrite it. Part 1 focuses on building a proof-of-concept RubyGems plugin.

\r\n

The source code for this episode can be found on GitHub: https://github.com/avdi/gem-love/tree/rubytapas-episode-039

\r\n

Other notes:

\r\n\n
\n

Attached Files

\n ]]>
\n \ dpd-1d5893a8db9e3d4defae8469c8fa44e0c6f99494\n \ Fri, 21 Dec 2012 09:00:00 -0500\n \n First in a series following the creation of an app\n \n
\n \n \ <![CDATA[038 Caller-Specified Fallback]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=107\n \ \n

Sometimes it's hard to decide how to handle a failure in a method call. Raise an exception? Return a nil? Log an error? The best choice may differ depending on where the method is being called. In this episode, we look at a technique for defering the decision about how to deal with a failure to the point when a method is called.

\n
\n

Attached Files

\n ]]>
\n \ dpd-f23b997833a15f555998a45f39405c3408f82ca5\n \ Wed, 19 Dec 2012 11:23:00 -0500\n \n Punting failure-handling to the caller\n \n \
\n \n <![CDATA[037 Proc and Threequal]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=106\n \ \n

Today's dish combines the great flavor of Ruby's 'case' statement with a little Proc seasoning and a threequals demiglace!

\n
\n

Attached Files

\n ]]>
\n \ dpd-97374819aa4806e23042ffa33c828d5ae1569862\n \ Mon, 17 Dec 2012 09:00:00 -0500\n \n A fun way to use Procs as predicates\n \n \
\n \n <![CDATA[036 Blocks, Procs, and Lambdas]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=105\n \ \n

How is a Proc different from a Lambda? When do you use one over the other? And how to blocks fit into all this? Today's episode attemptes to answer these burning questions.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-c178b82e058c0752ae909c36c3d48ccbe4ccf197\n \ Fri, 14 Dec 2012 09:00:00 -0500\n \n Demystifying blocks, procs, and lambdas. Hopefully.\n \n
\n \n \ <![CDATA[035 Callable]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=104\n \ \n

Many different kinds of Ruby objects respond to #call. Today's dish is a demonstration of how this fact can be harnessed to easily swap out different implementations of a collaborator object.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-f3d0497ca974b56d19b030694baf98e7a6c66594\n \ Wed, 12 Dec 2012 09:00:00 -0500\n \n A common protocol for callable objects\n \n \
\n \n <![CDATA[034 Struct from Hash]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=103\n \ \n

For such similar and broadly useful data structures, Struct and Hash are surprisingly incompatible. In this episode we look at some ways to smooth over their differences, and answer a viewer question in the process.

\n
\n

Attached Files

\n ]]>
\n \ dpd-56175b4cc446a5928f49147253d4ae33cc7dfc5d\n \ Mon, 10 Dec 2012 09:00:00 -0500\n \n Struct and Hash, sittin' in a tree...\n \n \
\n \n <![CDATA[033 Classes and Constants]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=102\n \ \n

On the menu today is an exploration of what, exactly, happens when we define a class in Ruby.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-1b98b7922654f0f135aa461e91af7e91ec7c4e36\n \ Fri, 07 Dec 2012 09:00:00 -0500\n \n Exactly what does the \"class\" keyword do?\n \n \
\n \n <![CDATA[032 Hash Default Blocks]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=101\n \ \n

Following on to the episodes about #fetch, today's episode answers the question: what do we do when we want the same defaulting behavior everwhere a Hash is used?

\n \
\n

Attached Files

\n ]]>
\n \ dpd-b9f164476123506231d352c7e978de64c862d7be\n \ Wed, 05 Dec 2012 09:00:00 -0500\n \n Making hash values appear out of nowhere.\n \n \
\n \n <![CDATA[031 Observer Variations]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=100\n \ \n

Today we revisit episode 21, and try out several different takes on registering observer callbacks inline.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-83dccabcfe70590e62b8738443db32ab325ad3e7\n \ Mon, 03 Dec 2012 09:00:00 -0500\n \n Improving the observable API\n \n \
\n \n <![CDATA[030 Backticks]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=95\n \n

Celebrate Friday with a stupid Ruby trick! In this epsiode we discover one of Ruby's most overlooked operators, and overload it just because we can.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-ed5128ebba504d2a33f9a61992490ae7a8a12b9b\n \ Fri, 30 Nov 2012 09:00:00 -0500\n \n Overloading Ruby's backtick operator, just for funsies!\n \n
\n \n \ <![CDATA[029 Redirecting Output]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=88\n \ \n

This episode takes a look at two different methods for redirecting standard out and standard error - one simple and quick, one more comprehensive.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-16beac88f371fc2c6f943aa04ad71b49bc842b2a\n \ Wed, 28 Nov 2012 13:26:00 -0500\n \n Telling output to take a detour.\n \n \
\n \n <![CDATA[028 Macros and Modules Part 2]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=80\n \ \n

In episode 27, we switched from adding generated methods directly to a class, to using anonymous modules. In this episode, we consolidate our metaprogramming modules, and make them more self-documenting.

\n
\n

Attached Files

\n ]]>
\n \ dpd-c79e2bf7b9cb3b55ae7078b9a6578385bd3e27dd\n \ Mon, 26 Nov 2012 09:00:00 -0500\n \n Cleaning up metaprogrammed methods\n \n \
\n \n <![CDATA[027 Macros and Modules]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=71\n \n

In this episode, we look at how to use dynamically generated modules to make metaprogrammed methods open for future extension.

\n
\n

Attached Files

\n ]]>
\n \ dpd-361722daab20abefb929e327d092af82449e33b9\n \ Fri, 23 Nov 2012 09:00:00 -0500\n \n How to add methods to classes in an extensible way.\n \n
\n \n \ <![CDATA[026 FFI]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=70\n \ \n

With the FFI Gem, building wrappers for C libraries is easier than ever. This episode gives a brief overview of building the beginnings of a Ruby interface to the Linux libpulse library.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-eba708ddea7fcb89b82d0829bf95f1d47a73d904\n \ Wed, 21 Nov 2012 09:00:00 -0500\n \n A brief introduction to the FFI Gem\n \n \
\n \n <![CDATA[025 OpenStruct]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=69\n \n

This episode focuses on OpenStruct, which makes it easy to create ad-hoc value objects from hashes.

\n
\n

Attached Files

\n ]]>
\n \ dpd-6f130b3f9a594e4eb4270b715d2b9d3748813a39\n \ Mon, 19 Nov 2012 09:00:00 -0500\n \n A look at Struct's freewheeling cousin, OpenStruct\n \n
\n \n \ <![CDATA[024 Incidental Change]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=66\n \ \n

Have you ever looked at a diff that was cluttered by meaningless \"noise\" - changes that existed just to satisfy the parser, and were a distraction from the semantic changes to the code? In this episode we look at some idioms and habits to help avoid these kinds of incidental changes.

\n
\n

Attached Files

\n ]]>
\n \ dpd-cef25d0a932c75ec347ada984dfe7985a3940ae9\n \ Fri, 16 Nov 2012 09:00:00 -0500\n \n Keeping diffs free from noise.\n \n \
\n \n <![CDATA[023 Tempfile]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=65\n \ \n

In this episode we look at the Tempfile standard library, and how it can facilitate communicating with subprocesses.

\n
\n

Attached Files

\n ]]>
\n \ dpd-43a1646684946fc7dcf34f5fae7fb2a7487eec48\n \ Wed, 14 Nov 2012 11:34:00 -0500\n \n Working with temporary files in Ruby\n \n \
\n \n <![CDATA[022 Inline Rescue]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=64\n \n

Putting a rescue at the end of a line can get you into unexpected trouble. In this episode we'll look at why, as well as at the one case where an inline rescue makes sense.

\n
\n

Attached Files

\n ]]>
\n \ dpd-ede0638a5c8715c4bbce0fd777995cfe9a788e3f\n \ Mon, 12 Nov 2012 09:00:00 -0500\n \n When to put a rescue at the end of a line.\n \n \
\n \n <![CDATA[021 Domain Model Events]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=59\n \n

In this episode we take a look at a common problem in Rails development: slimming down a controller whose functionality can't easily be pushed down into models. We address the problem using the Observer pattern and a tell-don't-ask design.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-bb31cc8d9b6a73a437d7cd2bb96debf661ef215f\n \ Fri, 09 Nov 2012 09:00:00 -0500\n \n Refactoring a Rails controller into a tell-don't-ask style\n \n
\n \n \ <![CDATA[020 Struct]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=58\n \ \n

In previous episodes we've used the Struct class in passing to quickly construct classes. This time, we put Struct in the spotlight, and put it through its paces. You'll want to watch this one through even if you're familiar with Struct; you may find it has more tricks up its sleeve than you realized.

\n \
\n

Attached Files

\n ]]>
\n \ dpd-a5f31fbed48fe51bc550940662ff7a433d24ff8b\n \ Wed, 07 Nov 2012 09:00:00 -0500\n \n A short but thorough introduction to the Struct class\n \n
\n \n \ <![CDATA[019 Pluggable Selector]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=57\n \ \n

In today's episode, we revisit episode 11 (Method and Message), and take a look at a way to make the coupling between objects even looser, by making the name of the message one object sends to another variable.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-c4e2cb82c50fa3c4cd188334ba85d5407895ecdc\n \ Mon, 05 Nov 2012 09:00:00 -0500\n \n Sometimes we need just one more level of indirection.\n \n
\n \n \ <![CDATA[018 Subclassing Array]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=53\n \ \n

Sooner or later you'll want to subclass Array. This episode looks at why that's a bad idea, and presents an alternative.

\n
\n

Attached Files

\n ]]>
\n \ dpd-696a9d33f9ba6a4f5c690fa4ee31ec3690962124\n \ Fri, 02 Nov 2012 09:00:00 -0400\n \n Why subclassing Array isn't such a good idea.\n \n \
\n \n <![CDATA[017 Pay it Forward]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=52\n \n

In this episode,  we explore command/query separation by looking at what happens to unit tests when the separation breaks down.

\n
\n

Attached Files

\n ]]>
\n \ dpd-d4b06cf0cbcb2f723b870ecc76abeb4ae733d1ea\n \ Wed, 31 Oct 2012 09:00:00 -0400\n \n Using tests to understand command/query separation.\n \n
\n \n \ <![CDATA[016 super in Modules]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=51\n \ \n

How to find out if there is a super method to be called, and other tricks for using super within a module.

\n
\n

Attached Files

\n ]]>
\n \ dpd-b0889d978cdfa43470f5ac1901daf12e555bdb6c\n \ Mon, 29 Oct 2012 09:00:00 -0400\n \n Special considerations for using the `super` keyword in a module.\n \n
\n \n \ <![CDATA[015 Advanced #fetch]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=48\n \ \n

In this final installment on the #fetch method we look at #fetch beyond Hash; #fetch with nested hashes; re-using default blocks; and more.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-03e2265057df71658f04e8e4dccef9a36204b4a1\n \ Fri, 26 Oct 2012 09:00:00 -0400\n \n In this final installment on the `#fetch` method we look at `#fetch` beyond Hash; `#fetch` with nested hashes; re-using default blocks; and more.\n \ \n \
\n \n <![CDATA[014 super]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=47\n \ \n

A dive into some of the dark corners of the `super` keyword.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-79a8b6297343c5f46332e5d9f9011480f41d51c7\n \ Wed, 24 Oct 2012 09:00:00 -0400\n \n A dive into some of the dark corners of the `super` keyword.\n \n
\n \n \ <![CDATA[013 Singleton Objects]]>\n https://rubytapas.dpdcart.com/subscriber/post?id=46\n \ \n

Sometimes one instance is all you need.

\n
\n

Attached Files

\n ]]>
\n \ dpd-26b1a34a404659ee23bc5204e51f93cca6bda40d\n \ Mon, 22 Oct 2012 09:00:00 -0400\n \n Sometimes one instance is all you need.\n \n \
\n \n <![CDATA[012: #fetch for Defaults]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=45\n \n

How to use Hash#fetch to provide defaults for missing keys, and why you might prefer that to other techniques for defaulting values.

\n
\n

Attached Files

\n ]]>
\n \ dpd-0c2f8c9eadd894907b23a158c3f72d38d2a747f3\n \ Fri, 19 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[011: Method and Message]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=44\n \n

A look at the difference between methods and messages, and why passing methods around isn't as common in Ruby as it is in e.g. JavaScript.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-526418232c6b44d3e6558186331d465cd6e8b33b\n \ Wed, 17 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[010 Finding $HOME]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=43\n \n

How to reliably discover a user's home directory.

\n
\n

Attached Files

\n ]]>
\n \ dpd-7ddc2d3a5de074bb541b10f8059fa15676de5377\n \ Mon, 15 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[009 Symbol Literals]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=39\n \n

Some alternative ways to write symbols in Ruby code.

\n
\n

Attached Files

\n ]]>
\n \ dpd-092696617ace450bc56ecdf3d32676c4ed649737\n \ Fri, 12 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[008 #fetch as an Assertion]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=38\n \n

Hash#fetch is one of my favorite methods. In this episode, I take a look at using it to assert the existence of hash keys.

\n
\n

Attached Files

\n ]]>
\n \ dpd-05db052781ab7577593af2587b04b18df321cab1\n \ Wed, 10 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[007 Constructors]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=32\n \n

A look into how Ruby's object construction works, and how we can customize constructors for special scenarios.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-4944ca6e0b9507b6f282f89a7ef898f523e27927\n \ Mon, 08 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[006 Forwardable]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=31\n \n

A short introduction to using Ruby's Forwardable library for object composition.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-a0a73da38a46bb08753c9371cff3d5800a453929\n \ Fri, 05 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[005 Array Literals]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=27\n \n

Composing command lines with fancy array literals.

\n
\n

Attached Files

\n ]]>
\n \ dpd-fc854d43484d212cf08a36fd13e5cf8c7fa427d5\n \ Wed, 03 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[004 Barewords]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=26\n \n

In this longer-than-usual episode, some thoughts on how to enable method logic to remain stable and unchanged while evolving and changing the source and scope of the values used by the logic.

\n
\n

Attached Files

\n ]]>
\n \ dpd-79af0936dda4a452fa19be8b54aecd2cb1885e2f\n \ Mon, 01 Oct 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[003 Character Literals]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=21\n \n

Character literal syntax in Ruby.

\n
\n

Attached Files

\n \ ]]>
\n \ dpd-ed1f01e1872210288982a61742c81df4c7905a4b\n \ Fri, 28 Sep 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[002 Large Integer Literals]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=20\n \n

How to format big numbers so that they are readable.

\n
\n

Attached Files

\n ]]>
\n \ dpd-8652bee88eb6fad318e874a29a26513083d47bab\n \ Wed, 26 Sep 2012 09:00:00 -0400\n \n \n \
\n \n <![CDATA[001 Binary Literals]]>\n \ https://rubytapas.dpdcart.com/subscriber/post?id=18\n \n

In this inaugural episode, a look at a handy syntax for writing out binary numbers.

\n
\n \

Attached Files

\n ]]>
\n \ dpd-89e8004c8242e7ad548833bef1e18a5b575c92c1\n \ Mon, 24 Sep 2012 09:00:00 -0400\n \n \n \
\n
\n
\n" http_version: recorded_at: Fri, 26 Dec 2014 07:13:18 GMT recorded_with: VCR 2.9.3