Hidden Features

Posts tagged with "Hidden Features."
  • 11

    MAY
    2012

    Delaying Decisions

    I love playing with Ruby's Hash. I think it has a neat API and experimenting with it can actually help you understand how to write good Ruby. Let's dig into this idea to see what I mean.

    The nil Problem

    In Destroy All Software #9 Gary chooses to show an example in Python because, unlike Ruby's Hash, it will raise an error for a non-existent key. Ruby just returns nil, he explains.

    What Gary said isn't really true, but I'm guessing he just didn't know that at the time. He was in the process of switching to Ruby from Python and I'm guessing he just didn't have a deep enough understanding of Ruby's Hash yet. I bet he does know how it works now.

    But assume he was right. What's he saying and why does it matter? Consider some code like this:

    class SearchesController < ApplicationController
      def show
        terms = params[:terms]
        SomeModel.search(terms)
        # ...
      end
    end
    

    This is what Gary doesn't like, and rightfully so. Because I indexed into params here with the []() method, I will indeed get a nil if the :terms key wasn't in params.

    Read more…

  • 13

    OCT
    2008

    The Secret Shell Helper

    Someone pops onto the Ruby Talk mailing list fairly regularly asking how to break up content like:

    one "two" "a longer three"
    

    They expect to end with a three element Array, where the third item will contain spaces. They generally expect the quotes will have been removed as well.

    If your needs are very, very simple you may be able to handle this with a regular expression:

    data = 'one "two" "a longer three"'
    p data.scan(/"([^"]*)"|(\S+)/).flatten.compact
    # >> ["one", "two", "a longer three"]
    

    That just searches for either a set of quotes with some non-quote characters between them or a run of non-whitespace characters. Those are the two possibilities for the fields. Note that the two separate capture here mean scan() will returns contents in the form:

    [[nil, "one"], ["two", nil], ["a longer three", nil]]
    

    That's why I added a flatten() and compact() to get down to the actual matches.

    The regular expression approach can get pretty complex though if any kind of escaping for quotes is involved. When that happens, you may need to step up to a parser.

    Read more…

  • 10

    OCT
    2008

    All About Struct

    I build small little data classes all the time and there's a reason for that: Ruby makes it trivial to do so. That's a big win because we all know that what is a trivial data class today will be tomorrow's super object, right? If I start out using a simple Array or Hash, I'll probably end up redoing most of the logic at both ends eventually. Or I can start with the trivial class and grow it naturally.

    The key to all this though is that I don't write those classes myself! That's what Ruby is for. More specifically, you need to learn to love Struct. Allow me to show you what I mean.

    Imagine I need a basic class to represent a Contact. Ruby gives us so many shortcuts that the class could be very small even without Struct:

    class Contact
      def initialize(first, last, email)
        @first = first
        @last  = last
        @email = email
      end
    
      attr_accessor :first, :last, :email
    end
    

    You could shorten that up more with some multiple assignment if you like, but that's the basics. Now using Struct is even easier:

    Read more…

  • 3

    OCT
    2008

    I'm Addicted to the Word Array

    Continuing with my recent trend of showing of fun uses of Ruby syntax, I have a confession to make: I'm addicted to Ruby's "word Array." I really am.

    I suspect most of you know this, but the word Array is a shortcut that can lessen the quote-comma-quote syndrome of simple a simple Array like:

    ["a", "wordy", "Array"]
    

    You can create the same Array with the word Array syntax:

    %w[a wordy Array]
    

    That's essentially just a String that will automatically be split() on whitespace to build an Array. You can use any amount of space any place you like, so you can layout the data in whatever way makes the most sense for you:

    require "pp"
    pp %w[ one   two   three
           four  five  six
           seven eight nine
                 zero        ]
    # >> ["one",
    # >>  "two",
    # >>  "three",
    # >>  "four",
    # >>  "five",
    # >>  "six",
    # >>  "seven",
    # >>  "eight",
    # >>  "nine",
    # >>  "zero"]
    

    Note that you can chose the punctuation characters used at either ends of the Array, some of which are paired while others just repeat:

    Read more…

  • 2

    OCT
    2008

    Working With Multiline Strings

    I imagine most Rubyists are aware that Ruby has "heredocs," but do you really know all they can do? Let's find out.

    A "here document" is a literal syntax for a multiline String. In the most basic form, they look like this:

    p <<END_HEREDOC
    This is a
      multiline,
    as is String!
    END_HEREDOC
    # >> "This is a\n  multiline,\nas is String!\n"
    

    The <<NAME syntax introduces the heredoc, but it actually begins at the start of the following line. It continues until NAME occurs again, at the beginning of a line. Note the trailing newline in the example above. All of the data between start and finish is packaged up into a String and dropped in where the original <<NAME designator appeared.

    There are some important details in that description, namely that the String begins on the next line and that it's inserted where the heredoc was started. This means that the rest of the line where the heredoc is started can have normal Ruby code (though your editor may syntax highlight it badly):

    p <<END_SQL.gsub(/\s+/, " ").strip
    SELECT * FROM     users
             ORDER BY users.id DESC
    END_SQL
    # >> "SELECT * FROM users ORDER BY users.id DESC"
    

    Read more…

  • 1

    OCT
    2006

    I Just Want One Character!

    Every so often a person asks the question on Ruby Talk, "How can I get just one character from the keyboard (without needing the user to hit return)?" Everyone is always quick to post solutions, but sadly there are some issues with almost every one of them.

    The general consensus is that this is a tough problem to solve correctly. I say that's the exact reason to let HighLine handle this for you:

    #!/usr/bin/env ruby -w
    
    require "highline/system_extensions"
    include HighLine::SystemExtensions
    
    print "Enter one character:  "
    char = get_character
    puts char.chr
    

    That doesn't look too tough, does it?

    What's terrific about this solution is that under-the-hood HighLine will check your platform and libraries and then try to use the solution that makes the most sense for your environment. The code is really pretty robust too, because people a lot smarter than me have been sending in patches for over a year, slowly eliminating all of those tricky edge cases.

    As you can see, I've split this functionality of HighLine into a separate module so you don't even need to load the full HighLine system. This was done just because this is such a real and common problem. This section of HighLine is one pure Ruby file, so feel free to vendor it if the external dependency is an issue.

    Read more…

  • 30

    JUL
    2006

    PStore Meets YAML

    I love the PStore standard library. It's a very graceful interface to get some fairly robust serialized mini-database handling in just a few lines. With it you get:

    1. Transactions with commit and rollbacks (automatic on exception).
    2. File locking, shared and exclusive.
    3. Multiprocessing safety.

    PStore does even more, including some file mode checking and MD5 hashing to avoid unneeded writes, but the above are the major selling points for me.

    Now, if I had to level any one complaint at PStore, it would be that because it uses Marshal under the hood it doesn't create files you can easily browse or tweak by hand. (Marshal is a feature, don't get me wrong. It's fast, which is very helpful.) Sometimes though I want PStore protection with the YAML file format.

    I'm embarrassed to admit that I use to use a hack for this:

    require "pstore"
    require "yaml"
    class PStore; Marshal = YAML; end
    

    That just redefines the Marshal constant in a scope that should only alter PStore. The library only uses dump() and load() and those methods work the same with Marshal and YAML.

    Read more…

  • 16

    JUL
    2006

    String Has Other Methods Besides =~/match() and sub()

    Ask anyone who knows me and they will tell you I'm a huge fan of regular expressions. I use them all the time and my FasterCSV library is a regular expression powered parser. However, even I know they are not for everything, and lately I keep running into almost comical examples of misuse. Here are some of my favorites.

    First, we have:

    str =~ /=/
    

    That snippet is like calling for a military escort (the regular expression engine) to see you safely to the grocery store down the block. That's fun, but probably overkill. In this case, a call to include?() will do the trick:

    str.include?("=")
    

    That may be more like riding your bike to the grocery store, but it gets the job done and is a bit faster to boot.

    Funny example number two. I've seen this before:

    str =~ /\Aquit\Z/
    

    Again, the regular expression engine appreciates the love, but you really just want ==:

    str == "quit"
    

    Even for some of the fancier stuff, you don't need a full blown regular expression. For example, this:

    Read more…

  • 8

    JUL
    2006

    The Books are Wrong About Logger

    I've read several books that introduced the standard Logger library and they all agree on one thing: you can't customize the output. That's so last version in thinking! Behold…

    Here's a trivial Logger script, showing basic functionality:

    #!/usr/bin/env ruby -w
    
    require "logger"
    
    def expensive_error_report
      sleep 3  # Heavy Computation Simulation (patent pending)
      "YOU BROKE IT!"
    end
    
    log       = Logger.new(STDOUT)
    log.level = Logger::INFO  # set out output level above the DEBUG default
    
    log.debug("We're not in the verbose debug mode.")
    log.info("We do see informative logs though.")
    if log.error?  # check that this will be printed, before waste time
      log.error(expensive_error_report)
    end
    

    If you run that you will see:

    I, [2006-07-08T11:17:19.531943 #340]  INFO -- : We do see informative logs though.
    E, [2006-07-08T11:17:22.532424 #340] ERROR -- : YOU BROKE IT!
    

    Now everyone has always known you can format the date and time display using a strftime() compatible pattern:

    #!/usr/bin/env ruby -w
    
    require "logger"
    
    def expensive_error_report
      sleep 3
      "YOU BROKE IT!"
    end
    
    log                 = Logger.new(STDOUT)
    log.level           = Logger::INFO
    log.datetime_format = "%Y-%m-%d %H:%M "  # simplify time output
    
    log.debug("We're not in the verbose debug mode.")
    log.info("We do see informative logs though.")
    if log.error?
      log.error(expensive_error_report)
    end
    

    Read more…