Fork me on GitHub
Justin Ko
Creating an HTML table with Ruby

There are so many ways to build an HTML table in Ruby. I think everyone has built one like this:

html = "".tap do |str|
  str << '<table>'
    str << '<tr>'
      str << '<td>'
       str << 'a'
      str << '</td>'
      str << '<td>'
       str << 'b'
      str << '</td>'
    str << '</tr>'
  str << '</table>'
end

Not only is that repetitious, it’s also an eye sore. We can do much better. How about a DSL?

module HTMLMethods
  def wrap(tag)
    concat "<#{tag}>"
    yield
    concat "</#{tag}>"
  end
end

html = "".tap do |s|
  s.extend HTMLMethods

  s.wrap :table do
    s.wrap :tr do
      s.wrap(:td) { s << 'a' }
      s.wrap(:td) { s << 'b' }
    end
  end
end

puts html # => <table><tr><td>a</td><td>b</td></tr></table>

Ahh, much better. Don’t like calling wrap on the s variable? No problem. Let’s make a class to get rid of it:

class HTMLFactory
  def self.make(&block)
    new.instance_eval(&block)
  end

  attr_reader :str

  def initialize
    @str = ""
  end

  def wrap(tag)
    str.concat "<#{tag}>"
    yield
    str.concat "</#{tag}>"
  end
end

html = HTMLFactory.make do
  wrap :table do
    wrap :tr do
      wrap(:td) { str << 'a' }
      wrap(:td) { str << 'b' }
    end
  end
end

puts html # => <table><tr><td>a</td><td>b</td></tr></table>

Enjoy!

Source code: https://gist.github.com/773952

When To Subclass A Ruby Core Class

When browsing Ruby code, you will indefinitely come across a class that subclasses a Ruby core class:

class ArrayLike < Array
  ...
end

Obviously, subclassing in Ruby means the subclass inherits the parent class functionality (methods, constants, etc.). That’s easy to understand, but how do you know when to do it? The answer is simple: Does the class need to behave like the Ruby core class you want to inherit from?

Let me give you can example.

Let’s say you need a class for storing two things: people and places. Also, you decide to use a Hash to do this. A typical class might look like this:

class Storage
  attr_reader :elements

  def initialize
    @elements = {}.tap do |elements|
      elements[:people] = []
      elements[:places] = []
    end
  end

  def size
    @elements.size
  end

  def get(key)
    @elements.fetch(key)
  end
end

This class is pretty straight forward. When you call initialize it, it creates an @elements instance variable that stores a hash of the people and places. The two instance methods, size and get just calls methods on @elements. Let’s inspect this:

storage = Storage.new

puts storage.inspect # => #<Storage:0x100137bc0 @elements={:people=>[], :places=>[]}>
puts storage.size # => 2
puts storage.get(:people).inspect # => []

This class works well for its intended purpose, but the code can be reduced. Let’s see what we can do by subclassing Hash:

class HashStorage < Hash
  def initialize
    self[:people] = []
    self[:places] = []
  end

  alias :get :fetch
end

Wow, look how much smaller that is! Let’s explain some things here. Since HashStorage subclasses Hash, it is going to behave exactly like a Hash. For example, if you call HashStorage.new, it will return {}. In the initialize method, self refers to {} (the Hash object). Let’s inspect this:

storage = HashStorage.new

puts storage.inspect # => {:people=>[], :places=>[]}
puts storage.size # => 2
puts storage.get(:people).inspect # => []

This technique can really reduce your code. Have fun!

Source code: https://gist.github.com/773325

Common Ruby Gem Configuration Implementations

Ever wondered how some ruby gems set up their configuration like this?:

MyGem.configure do |config|
  config.my_setting = true
end

Well, here are some examples of how it’s done:

class MyGem
  class << self
    attr_accessor :color
  end

  def self.configure(&block)
    instance_eval(&block)
  end
end

MyGem.configure do
  self.color = true
end

puts MyGem.color # => true

Object.send(:remove_const, :MyGem)

########################################

class MyGem
  def self.configure
    yield configuration
  end

  def self.configuration
    @configuration ||= Configuration.new
  end

  class Configuration
    attr_accessor :color
  end
end

MyGem.configure do |config|
  config.color = true
end

puts MyGem.configuration.color # => true

Object.send(:remove_const, :MyGem)

########################################

class MyGem
  def self.configure
    yield configuration
  end

  def self.configuration
    @configuration ||= Configuration.new
  end

  class Configuration    
    def initialize
      @fields = Fields.new
    end

    def fields(&block)
      if block
        @fields.instance_eval(&block)
      else
        @fields.to_pretty
      end
    end

    class Fields
      def initialize
        @fields = []
      end

      def to_pretty
        {}.tap do |pretty|
          @fields.each do |field|
            pretty[field.name] = {:columns => field.columns}
          end
        end.inspect
      end

      def method_missing(sym, *args, &block)
        @fields << Field.new(sym, &block)
      end

      class Field
        attr_reader :name, :columns

        def initialize(name, &block)
          @name = name
          @block = block
          @columns = []

          set_columns
        end

        def column(name)
          @columns << name
        end

        def set_columns
          instance_eval(&@block) if @block
        end
      end
    end
  end
end

MyGem.configure do |config|
  config.fields do
    dog :canine
    cat :feline
    rat :rodent do
      column :birthdate
      column :food_type
    end
  end
end

puts MyGem.configuration.fields # => 
  # {
  #   :dog => { :columns => [] },
  #   :cat => { :columns => [] },
  #   :rat => { :columns => [:birthdate, :food_type] }
  # }

Github Gist: https://gist.github.com/743458

Hire Me!

I’m currently seeking a full-time remote position.

My qualifications:

  • 5 years of Rails and Ruby experience.
  • RSpec codebase contributor. Recommendation letter by David Chelimsky provided by request.
  • Creator of http://relishapp.com
  • BDD aficionado.

My requirements:

  • Must be remote. I live in Denver, Colorado, and have zero desire to relocate.
  • No corporate BS. You know what I mean.

Resume is available by request.

Email me at jko170@gmail.com

Thanks for stopping by!

Rails form_for with Ruby setter attributes

In Rails, you can use form_for for nearly everything. Let me give you an example where a lot of people think you’d have to use form_tag.

Let’s say there is a Membership model:

class Membership < ActiveRecord::Base
  # columns
  # id :integer, primary_key
  # membershipable_id :integer
  # membershipable_type :string(255)
  # user_id :integer
end

We’re going to ignore the polymorphic membershipable columns. But as you can see, Membership belongs to User. Here is the User model:

class User < ActiveRecord::Base
  # columns
  # id :integer, primary_key
  # handle :string(255)
end

Now, let’s say we want to add memberships by using a text_field and entering in the user’s handle. As we can see, Membership does not have a “handle” column, therefore that rids the possibility of using form_for. Therefore, we must use form_tag that posts to a MembershipsController like so:

<%= form_tag '/memberships' do %>
  <%= text_field_tag 'handle' %>
  <%= submit_tag 'Add Membership' %>
<% end %>

What’s so bad about that? Well, you cannot set the attributes on the Membership record like this:

Membership.new(params[:membership]) # params consist of {:handle => ""}

There are many ways you could set the membership’s user by using {:handle => "the-handle"} params, but this post is about simplicity and elegance.

So, how do we use Membership.new(params[:membership]) with a column that doesn’t exist on Membership? Easy, Ruby’s attribute setter methods. Let’s add one on the Membership model for the user’s handle:

class Membership < ActiveRecord::Base
  def handle=(text)
    self.user = User.find_by_handle(text)
  end
end

That’s it! But, we have one more thing to do. In order for form_for to work, we need to let Rails know that Membership responds to a handle method (reader method). There are a few ways to do this, but the easiest way is to use Ruby’s attr_reader:

class Membership < ActiveRecord::Base
  attr_reader :handle
end

All done! Your form can now look like this:

<%= form_for Membership.new do |f| %>
  <%= f.text_field :handle %>
  <%= f.submit %>
<% end %>

You can also extend your handle= method to do anything you want. For example, error handling:

def handle=(text)
  if user = User.find_by_handle(text)
    self.user = user
  else
    errors.add(:base, "User with the handle '#{text}' not found"
  end
end

Enjoy!

Extend an Object

Oh Rubyists, we are so quick to open core classes and add our own domain specific methods. Instead of doing that, we can extend the object that we want to have the methods. Let’s say we have an object that represents an URL:

"http://google.com"

Now, how do I get the domain portion of the string? I could extract it right there on the object, but I have URL’s all over the place, so that wouldn’t be DRY. I could write a URL class, but that seems a little overkill if all I want to do is get the domain.

One option is to open the String class and add my own method:

class String
  def domain
    split('/')[2]
  end
end

But really, do we need the domain method available to every string instance in our app/library? If not, one thing you can do is add the domain method to a particular instance of a string:

module UrlMethods
  def domain
    split('/')[2]
  end
end

url = "http://google.com"
url.extend(UrlMethods)
url.domain # => "google.com"

So next time think “Do I really need to open this Ruby core class?”

super()

You might run across some ruby code that calls the keyword super with parenthesis. This is because calling super with no arguments or parenthesis has special meaning.

Calling super with no arguments or parenthesis will pass the current methods arguments to the superclass method. Example:

class Dog
  def bark(message)
    puts message
  end
end

class Labrador < Dog
  def bark(message)
    super
  end
end

Labrador.new.bark('ruff') # => ruff

Now, if we use super with empty parenthesis, we get an exception:

class Dog
  def bark(message)
    puts message
  end
end

class Labrador < Dog
  def bark(message)
    super()
  end
end

Labrador.new.bark('ruff') # => ArgumentError: wrong number of arguments (0 for 1)

When calling super with no arguments or parenthesis, it will pass the arguments even if they have been modified:

class Dog
  def bark(message)
    puts message
  end
end

class Labrador < Dog
  def bark(message)
    message.upcase!
    super
  end
end

Labrador.new.bark('ruff') # => RUFF

Have fun with super

RSpec - should I use it “should…”?

When using RSpec, you either use it "should..." or you don’t. Should you use “should”? In my opinion, no. I’ll tell you why.

It’s redundant. It doesn’t actually describe anything. Consider the example:

describe Dog do
  it 'should jump' { ... }
  it 'should not jump' { ... }
end

VS:

describe Dog do
  it 'jumps' { ... }
  it "doesn't jump" { ... }
end

One, the should-less version is less words (who doesn’t want to type less?). Two, it sets a much more straight forward description of your example. To me, “should” is insecure - it feels flaky to me.

It’s your code, tell it exactly what you think it should be doing.

Praise For Rails 3 Validations

This is a small post, but I want to point out how Rails is ever improving.

In Rails 2.3, you would do:

validates_presence_of :email
validates_uniqueness_of :email

In Rails 3, you can do:

validates :email, :presence => true, :uniqueness => true

This is old news, but it is something that is not talked about nearly as much as the new routing system - it’s not even mentioned on the Rails Guides: http://guides.rubyonrails.org/active_record_validations_callbacks.html

For a more in depth article about Rails 3 validations, check out: http://lindsaar.net/2010/1/31/validates_rails_3_awesome_is_true

Have fun deleting lines of validation code!

Generate Devise Views In HAML

To generate your Devise views in HAML, run this command:

rails generate devise:views -e=haml

NOTE: You must have haml, hpricot and ruby_parser in your Gemfile.