Hardscrabble 🍫

By Maxwell Jacobson

See also: the archives and an RSS feed

participate in society

October 3, 2015

If there’s one piece of advice I feel comfortable giving, it’s this: participate in society. Find it in your introverted self to join in.

When you hear vaguely that you ought to be using zsh instead of bash, but when you try it you’re not sure why it’s better, stick with it. When you hear that oh-my-zsh is a good way to manage your zsh configuration and you think the name is dumb and don’t want to use it, get over yourself, because months later you’ll find yourself desperately googling to see if anyone else is using zsh in tmux on el capitan and experiencing a weird behavior where option-backspace isn’t working (it’s supposed to delete backwards a full word), but only when in tmux, and you can’t really find anything, just throw out your zsh configuration and use the thing that people use. You won’t regret it.

Anyway, that’s just my one piece of advice.

announcing metaphor loop

September 18, 2015

Last month I started a podcast but I didn’t really publicize it because I wasn’t sure I was going to keep doing it. I put it on this site, hidden down in the footer, and I put it on iTunes, and I tweeted a few cryptic things like:

Today I released the second episode, which makes it real enough that I’m ready to share it.

metaphor loop

metaphor loop

(Click on the art to visit the show homepage, which has subscription links)

It’s about “how we think about programming”. I started out with an agenda, which was to argue that figurative language is the best tool for teaching code, or something like that, and I’m finding that I’m not sure what I think anymore, but I’m excited to keep exploring the ways people build understanding by interviewing programmers about what goes on in their heads.

The first episode was an interview with my old friend Corey Mendell. I had a really good time recording and editing it and the few people I shared it with seemed to like it too. I was immediately addicted.

I don’t think I have it in me to do it on a weekly basis like a lot of podcasts. Today I’m sharing the second episode, approximately a month after the first. So, monthly? Maybe.

This one features Vaidehi Joshi who I don’t even really know, but whose blog I really like. I’m happy with how this one turned out and think you’ll like it.

a few details

I’m not planning to have ads. I want this to be a fun, pure endeavor that makes no money. I’m not tracking subscriber counts or anything like that. I don’t want to know.

I’m licensing it under a creative commons license, because

  1. why not?
  2. I wanted to include some CC-licensed music, and it had a “share-alike” clause
  3. so why not?

By the way: part of the reason I made this is that I really like listening to podcasts and my ego would often hear them and think “why don’t they invite me on?” One thing I’m realizing is that inviting people on is kind of hard, because you don’t know if they’ll want to do it and maybe they’ll think it’s dumb? So: if you listen to some episodes and think you’d like to be on the show, let me know, and we’ll at least have a phone call and if it feels like a show, it’ll be a show.

hardscrabble.net/metaphorloop

pro tip open firefox tabs in background

September 8, 2015

One good thing to know if you’re a firefox person: visit about:config and poke around, configuring things.

Here’s what happened today: I was watching a youtube video while browsing Twitter via Tweetbot. I clicked a link, which opened a new tab, pushing my video into the background. I diligently clicked the video’s tab to bring it back to the foreground so I could continue passively watching it while browsing twitter.

Then I clicked another link, and instinctively clicked the video’s tab to bring it back into the foreground again.

By the third time I did this, I realized I really wished there was a setting to automatically open tabs in the background. I tried googling it, but wasn’t really finding anything. So I checked about:config and searched through for “background”. The screen is a list of every configuration you can control. Many of them are boolean attributes, which can be toggled by simply double clicking the attribute.

I saw one, browser.tabs.loadDivertedInBackground;false and thought “hmm, maybe?” At this point, I’m not certain there’s even a configuration that does this, but I try toggling it… and … click a link from a tweet… and…

It did what I wanted. Sweet.

eighty character lines

September 5, 2015

Last month we talked about RuboCop, which analyzes your Ruby code and nitpicks it. One of its most difficult to follow suggestions is to keep your lines of code no longer than 80 characters.

The creator of rubocop, bbatsov, explained his perspective on his blog:

We should definitely have a limit – that’s beyond any doubt. It’s common knowledge that humans read much faster vertically, than horizontally. Try to read the source code of something with 200-character lines and you’ll come to acknowledge that.

I’m totally on board with the short lines train. For me, it only gets tricky when dealing with nested stuff (examples to follow) which add a lot of space to the left of the first character of code. For example:

module MyGreatGem
  module SomeOtherNamespace
    module OmgAnotherNamespace
      module LolYeahOneMore
        class SomethingGreat
          class SomethingOk
            class MyGreatClass
              def initialize
                puts "OMG I only have 64 characters to express something on " \
                     "this line! And now it's more like 'these lines' haha"
              end
            end
          end
        end
      end
    end
  end
end

Often strings are the first thing to get chopped up, as in that example.

The only approach I thought of to deal with that is to organize my code differently to not use many nested namespaces. That’s probably not the worst idea, honestly, but I’m writing this post to share an interesting style I observed in the wild (read: on github) that takes a whole nother approach:

# Excerpted from:
# https://github.com/net-ssh/net-sftp/blob/ebf5d5380cc533b69b308baa2e396e4a18abc900/lib/net/sftp/operations/dir.rb
module Net; module SFTP; module Operations
  class Dir
    attr_reader :sftp

    def initialize(sftp)
      @sftp = sftp
    end
  end
end; end; end

Huh! That’s a style I hadn’t seen before. RuboCop has many complaints about it, and I don’t totally love the style, but it’s a very novel and neat way to do it, and it certainly frees up columns to spend on your code if you’re planning to stick to an 80 character limit.

One possible alternative is to define your namespaced class using this shorthand:

class Net::SFTP::Operations::Dir
  attr_reader :sftp

  def initialize(sftp)
    @sftp = sftp
  end
end

If you do that, you get 2 extra characters on each line. Sweet!

One problem: it sort of doesn’t work, at least not in the same way.

If you just look at that example, and imagine that you’re the Ruby interpreter trying to figure out what this code means, how are you supposed to know whether Net, SFTP, and Operations are supposed to be classes or modules? You have to already know by them being previously defined. If they haven’t been defined yet, you are well within your right to raise a RuntimeException to complain that this constant hasn’t been defined yet, rather than try to guess.

Both of the earlier longhand examples were explicitly explaining what the type of each namespace constant is. That pattern works whether you’re defining the module or class in that moment, or “opening” a previously defined module or class to add something new to it. This shorthand, while optimal for line length, only works when opening previously defined constants.

One downside of this approach is that, by relying on all of the namespaces being predefined, it becomes harder to test this class in isolation (it’s probably possible to do it through some gnarly stubbing but, harder). You’re also introducing some requirements about the order in which the files from your code need to be loaded, which feels kind of fragile.

One possible upside comes to mind. When you follow the typical pattern of writing out all the namespace modules and classes, you introduce some room for error: what if in one file you write class Operations by mistake (instead of module Operations)? You’ll get an error. That’s not too bad, honestly.

I think 80 is usually enough but if you’re doing too many contortions to stay in that box, try like 90 or 100, you’re still a good person.

why I think RuboCop is so cool, and how to contribute to it

August 9, 2015

RuboCop as compiler

Ruby is not a compiled language.

You can write code which has obvious flaws and Ruby will run it and then it will fail at runtime. For example:

puts "hello world"
puts hello world

That produces this output:

$ ruby lol.rb
hello world
/Users/max/Desktop/lol.rb:2:in `<main>': undefined local variable or method `world' for main:Object (NameError)

Some other languages wouldn’t even run that program. For example, the same code in Go:

package main

import "fmt"

func main() {
	fmt.Println("hello world")
	fmt.Println(hello_world)
}

Running that produces this output:

$ go run lol.go
# command-line-arguments
./lol.go:7: undefined: hello_world

But Ruby can’t run anything. Look at this bullshit:

puts "hello world"

def lol
  puts "lol world"

lol

Running that produces this output:

$ ruby lol.rb
/Users/max/Desktop/lol.rb:7: syntax error, unexpected end-of-input, expecting keyword_end

Notice: it doesn’t even output “hello world”; it just straight-up fails to run. You might say it doesn’t compile.

Ruby actually has a command line flag for checking the syntactic-correctness of a program:

$ ruby -c lol.rb
/Users/max/Desktop/lol.rb:7: syntax error, unexpected end-of-input, expecting keyword_end
$ ruby -c ok_program.rb
Syntax OK

This is useful, but only to a point. It means your programs will usually run, but you’ll have more errors at runtime than you would writing in a language like Go (for example).

I barely know Go, but in the small exposure I’ve had, I’ve really enjoyed how nit-picky the compiler is. For example, this program:

package main

import "fmt"

func main() {
	msg := "Hello world"
	other_msg := "lol world"
	fmt.Println(msg)
}

Running it produces this output:

# command-line-arguments
./lol.go:7: other_msg declared and not used

What! I can’t run my program because I declared a variable but then didn’t use it?? Who cares?? Go cares! And I kind of do too, now. Why have it if you don’t need it? Go kind of forces you to write really intention-revealing code and to clean up anything which might obscure your intentions. That’s great.

Ruby doesn’t care as much about that.

msg = 'hello world'
other_msg = 'lol world'
puts msg

That’s fine:

$ ruby -c lol.rb
Syntax OK
$ ruby lol.rb
hello world

So now your code has this random unused variable. It’s not really hurting anyone or anything. In theory it has a performance impact, as it’s allocating an object you don’t really need it to, and if this code is run a ton that could matter. But more importantly, I think, it’s just clutter. When people come across this code in the future they won’t know why it’s there, but they might assume it’s there for a reason, and they’ll mentally mark this area of the code base as kind of strange and unknowable.

RuboCop is a gem which can help bring Ruby closer toward Go levels of nit-pickiness, and I’m a huge fan of it. I recommend including it in your Ruby projects and running it alongside your tests to enforce adherence to its rules (“cops” in its parlance).

Running that same ruby program through RuboCop produces this output (note: you have to gem install rubocop first):

$ rubocop lol.rb
Inspecting 1 file
W

Offenses:

lol.rb:2:1: W: Useless assignment to variable - other.
other = 'lol world'
^^^^^

1 file inspected, 1 offense detected

Awesome! Now we know our code contains some offense and might be confusing our collaborators, and we know exactly where to make the change.


RuboCop as code style nit picker

RuboCop is also very opinionated about code style. For example, it will complain if you aren’t consistent about using single or double quotes, or if you aren’t consistent about using two spaces for indentation, or if you leave spaces at the end of your lines.

I recommend configuring it to your taste and to not feel guilty about disabling cops which you don’t find valuable. As a tool, it adheres to the “strong opinions, weakly held” mantra: it’s very easy to bribe this cop into changing its opinions by adding a simple yml file to the root of your project.

The Go compiler isn’t picky about things like code style, but Go ships with a secondary, optional tool called gofmt which is extraordinarily opinionated: it straight-up rewrites your code to follow Go style conventions.

Similarly, many RuboCop cops are auto-correctable. Consider this program a “before picture”:

class Dog

  def initialize(name:name)
    
    @name=name
    end
end

milo = Dog.new(name: "Milo")
p milo


Kind of ugly looking, but it’s syntactically valid:

$ ruby -v
ruby 2.0.0p451 (2014-02-24 revision 45167) [x86_64-darwin14.3.0]
$ ruby -c lol.rb
Syntax OK
$ ruby lol.rb
#<Dog:0x007fbe64207a40 @name="Milo">

So let’s try autocorrecting it:

$ rubocop lol.rb --auto-correct
Inspecting 1 file
W

Offenses:

lol.rb:1:1: C: Missing top-level class documentation comment.
class Dog
^^^^^
lol.rb:2:1: C: [Corrected] Extra empty line detected at class body beginning.
lol.rb:2:23: W: Circular argument reference - name.
  def initialize(name:name)
                      ^^^^
lol.rb:3:1: C: [Corrected] Extra empty line detected at method body beginning.
lol.rb:3:1: C: [Corrected] Trailing whitespace detected.
lol.rb:4:1: C: [Corrected] Trailing whitespace detected.
lol.rb:4:5: W: end at 4, 4 is not aligned with def at 2, 2.
    end
    ^^^
lol.rb:4:10: C: [Corrected] Surrounding space missing for operator =.
    @name=name
         ^
lol.rb:5:10: C: [Corrected] Surrounding space missing for operator =.
    @name=name
         ^
lol.rb:8:22: C: [Corrected] Prefer single-quoted strings when you don't need string interpolation or special symbols.
milo = Dog.new(name: "Milo")
                     ^^^^^^
lol.rb:9:22: C: [Corrected] Prefer single-quoted strings when you don't need string interpolation or special symbols.
milo = Dog.new(name: "Milo")
                     ^^^^^^
lol.rb:10:1: C: [Corrected] 2 trailing blank lines detected.
lol.rb:11:1: C: [Corrected] 2 trailing blank lines detected.

1 file inspected, 13 offenses detected, 10 offenses corrected

Afterwards, the program looks like:

class Dog
  def initialize(name:name)
    @name = name
    end
end

milo = Dog.new(name: 'Milo')
p milo

It’s not perfect. I’m surprised it didn’t autocorrect the indentation on the first end. But it’s neat that it did as much as it did.

EDIT: I opened an issue about this surprise and it turns out they don’t want to autocorrect that unless you really opt in.

In the past, I’ve written about how Ruby Keyword Arguments aren’t Obvious, and mentioned “circular argument references” as a mistake I had to learn not to make. In an attempt to give back to others like me, I submitted a pull request which adds a cop to RuboCop that checks for the presence of those circular argument references and warns you about them. This is particularly useful on Ruby 2.0.0 and Ruby 2.1, which don’t emit warnings about their presence as Ruby 2.2 does.


contributing to RuboCop

Contributing to RuboCop is somewhat intimidating because, as you might imagine, its code base has very high standards. It also seems like it ought to be very complicated, because the project needs to be able to statically analyze code in order to complain about it.

Fortunately, the maintainers are very clear and quick in their feedback, and happy to merge things which seem useful. And the code base has many examples of how to write a cop. A cop is a class which inherits from RuboCop::Cop::Cop.

require 'rubocop'
require 'active_support/all'
RuboCop::Cop::Cop.subclasses.count #=> 226
# (required active support for the subclasses method, which I love)

Once you subclass Cop, your class will be on duty and RuboCop will ask it if it’s offended by the code it’s analyzing. You just need to tell it which types of code you care about. RuboCop thinks of Ruby code as a tree of nested nodes, each having a type. For example, when you assign a local variable, RuboCop sees that as a node whose type is :lvasgn. Let’s say you wanted to write a cop where local variables aren’t allowed to be named “harold”. You would write that like this:

# encoding: utf-8

module RuboCop
  module Cop
    module Lint
      class Harold < Cop
        def on_lvasgn(node)
          local_variable_name, value = *node
          return unless local_variable_name == :harold
          add_offense(node, :expression, 'Do not name local variables harold')
        end
      end
    end
  end
end

The trickiest bit is learning how RuboCop “sees” code as nodes and learning what the different parts are called. That comes from looking through the existing examples and experimenting. It’s fun, I recommend it. I added one other cop on a bored evening, about discouraging option hashes.


RuboCop as teacher

This summer, I coordinated a group of 5 developer interns at work. I helped pick them out, so I knew they were all very bright but not super experienced with Ruby. We asked them to study a bit and follow the Rails Tutorial Book before starting to help get them on the same page, but we didn’t expect them to know Ruby conventions or best practices.

From the beginning, their project had RuboCop linting their code, with zero configuration. I warned them, “you’re going to hate this”. On their last week, I asked them if it was helpful, and they all said yes, but that it was often very annoying. Some of them liked it more than others. One even made a contribution to RuboCop fixing an issue in the cop I added. They produced a very interesting code base. Superficially, it’s immaculate. They picked up a few tricks from RuboCop suggestions. More than one cited guard clauses as a thing they wouldn’t know about otherwise.

The class length maximum (100 lines) and the method length maximum (10 lines) were both unhelpful. They’re meant to guide you toward following the single responsibility principle, but if it’s not something you’ve internalized, it just sort of makes you stuck and frustrated.

I came to think of RuboCop (and, later, coffeelint and scss-lint) as an automated layer of mentorship filtering out some potential questions that didn’t need to reach the human layer, so that layer could be reserved for more interesting problems and discussions.


RuboCop as totalitarian police state?

I do think of programming as a creative outlet, but don’t worry about linters inhibiting your creativity; think of it like a poetic form with strict rules, like the sonnet. There’s something very satisfying about consistency, right?

some helpful tmux aliases

August 1, 2015

EDIT October 25, 2023: I have an updated post that is a better read: My tmux aliases (2023 edition).

tmux is still an essential tool in my development workflow. Today I’m writing to share a few aliases/helper functions I’ve recently added to my dotfiles. Those change all the time so I’m hesitant to link to the files which the helper aliases and functions currently live in.

I added them in these commits, though: d06362c and c9d8695.

The implementation is mostly stolen from other people’s dotfiles and is gnarly to look at so I’ll just share how I use them:

EDIT February 8, 2016: I totally changed the implementation: 6d883df because I would occasionally have a bug with the old helper functions. More details in this issue

Here’s how to use these aliases:


When I’m not in a tmux session, and I want to see the list of tmux sessions, I used to run tmux ls. Now I run tl.


When I’m not in a tmux session and I’d like to start a new one, I used to run tmux new -s blog (where blog is the name of the new session). Now I run t.

It auto-chooses a session name based on the current directory’s name.

If there’s already a session with the name of the current directory, it cleverly attaches to that session instead of trying to start a new one with that name.


If I’m not in a tmux session and I’d like to attach to any existing tmux session, and I don’t particularly care which one because I’m planning to go into the session switcher anyway (C-b s), I used to run tmux a. Now I run ta.

stubbing constants in ruby tests

July 20, 2015

Let’s say you have some code that doesn’t have tests and you want to add tests. Because the code wasn’t written with tests in mind, it might be hard to write tests for it.

Last year, DHH wrote a blog post called Test-induced design damage which argued that code “written with tests in mind” (quoting myself from the previous paragraph, not his post) isn’t necessarily better than code written with other goals in mind, and can often be worse.

When I’ve attempted TDD, I’ve had times when I felt like it helped me write nice code and times where I think I went too far like if you’re rolling out some dough to make a pie crust but you’re watching TV and you end up spreading it out until it covers the whole counter.

So this code you want to test. What makes it hard to test? Let’s say it’s a Ruby class in a Rails app. In Rails apps, all the classes are available for all the other classes to reference and depend on. Maybe it looks like this:

class PieCrust
  def initialize(pounds_of_dough)
    @lbs = pounds_of_dough
    @ready = false
  end

  def prep
    RollingPin.new.roll_out(self)
    Oven.new.preheat
    Instagram.upload(self)
    @ready = true
  end

  def ready?
    @ready
  end
end

(This example is revealing more about my state of mind right now than anything)

But like, look at this thing. How do we write a test that covers all that? And what if we want “fast tests”?

(Note: a lot of people really want their tests to run fast. For TDD enthusiasts, this allows a tight feedback loop between when you write the failing test to when you write the code which makes the test pass. They kind of expect to do that over and over and over and don’t want to wait for more than an instant. I don’t think they’re wrong to want that although I am personally often OK with waiting for longer than an instant.)

(Other people want their tests to run fast as a general principle, like they want their cars to go fast or their legs to.)

Let’s say there are a couple hundred classes in your app and a bunch of initializers which run whenever your Rails application is loaded and none of them are strictly necessary for you to feel confident that your PieCrust class is behaving properly. All you want to know is that calling the prep method rolls out the crust, preheats the oven, and uploads the pie crust to instagram.

You already know that all those things work as long as you call them properly because you have unit tests for those classes demonstrating how to call them properly. You can see that here they’re being called properly, so you don’t feel the need to actually load the rolling pin code, or the oven code, or the instagram code. And you really don’t want to upload something to instagram every time you run your tests.

What do you do?

There’s the dependency injection approach, where you might refactor the earlier code to look like:

class PieCrust
  def initialize(pounds_of_dough, roller: RollingPin.new, heater: Oven.new, photo_sharing_service: Instagram)
    @lbs = pounds_of_dough
    @roller = roller
    @heater = heater
    @photo_sharing_service = photo_sharing_service
    @ready = false
  end

  def prep
    roller.roll_out(self)
    heater.preheat
    photo_sharing_service.upload(self)
    @ready = true
  end

  def ready?
    @ready
  end

  private

  attr_reader :roller, :heater, :photo_sharing_service
end

Which lets you leave your other application code the same – it can interact with PieCrust the same as it did before, as the default values are totally sensible there. But you can now write a test like this:

RSpec.describe PieCrust do
  describe '#prep, #ready' do
    it 'rolls the crust, preheats the oven, and uploads the photo' do
      roller = double
      heater = double
      photo_sharing_service = double

      pie_crust = PieCrust.new(10, roller: roller, heater: heater, photo_sharing_service: photo_sharing_service)
      expect(pie_crust).to_not be_ready

      expect(roller).to receive(:roll_out).with(pie_crust)
      expect(heater).to receive(:preheat).with_no_arguments
      expect(photo_sharing_service).to receive(:upload).with(pie_crust)

      pie_crust.prep

      expect(pie_crust).to be_ready
    end
  end
end

I feel like this is OK but it feels like it prescribes and duplicates a lot of the stuff that’s going on in the application code, which doesn’t feel ideal to me but also feels kind of fine.

Is there any other way? There is. I learned this one from my brilliant coworker Máximo Mussini. While looking through a gem he made (Journeyman, a lightweight replacement to FactoryGirl), I discovered some super interesting code, and without his blessing I extracted it out into a gem which I may use to help me write tests in the future. That gem is called stub_constant, and using it, I would revert that code to the first version, avoiding the arguably awkward dependency injection.

You might be assuming: OK, so if you don’t inject the constants, you must load the entire application environment, because you’re going to be depending on those dependencies. Or like, maybe you don’t load the entire application environment, but you must at least load the code which defines those 3 constants, right?

Nope! Doing that is usually really difficult, because once you load the files which define those constants, those files are probably referencing other constants, so you need to load the files which define those constants and now you might as well just load the whole thing…

So… “Whaaaat?” You might say.

Here’s what the constant-referencing isolation tests would look like:

require "stub_constant"
StubConstant.klass(:Oven)
StubConstant.klass(:RollingPin)
StubConstant.module(:Instagram)

RSpec.describe PieCrust do
  describe '#prep, #ready?' do
    it 'rolls the crust, preheats the oven, and uploads the photo' do
      roller = double
      expect(RollingPin).to receive(:new).with_no_arguments.and_return(roller)

      heater = double
      expect(Oven).to receive(:new).with_no_arguments.and_return(heater)

      pie_crust = PieCrust.new
      expect(pie_crust).to_not be_ready

      expect(roller).to receive(:roll_out).with(pie_crust)
      expect(heater).to receive(:preheat).with_no_arguments
      expect(Instagram).to receive(:upload).with(pie_crust)

      pie_crust.prep

      expect(pie_crust).to be_ready
    end
  end
end

Sooo it got even more prescriptive. But it’s a pretty neat way to do a purely isolated test without needing to rewrite your code.

How would you test this? I want to know.

making a pull request to rails

July 14, 2015

Today I impulsively made a pull request to Rails, which feels kind of like a milestone for me. It’s about two years since I started using Rails at the Flatiron School. It’s also been about two years since the method I edited was last edited. I feel like there may be a reason and it won’t get merged, but who knows? I feel sort of exposed.

This post was helpful for me: Eileen Codes | Getting Your Local Environment Setup to Contribute to Rails.

In order to make a proper contribution, I needed to know that my change didn’t break the existing tests, and so I needed to be able to run the tests.

I also wanted to be able to add tests and confirm that they pass. So I really needed to be able to run the tests.

I had some trouble configuring my local environment, despite the post explaining it well (…databases…), BUT the post mentions rails/rails-dev-box which lets you skip a lot of the environment configuration by using a preconfigured virtual machine, and that turned out to be a god send of a casual aside for me and I’m writing this post largely to promote the existence of that project because it’s awesome.

It uses vagrant which is kind of magical… I had never used it before and it totally blew my mind. It allowed me to have a tmux session with windows like I’m used to, with the code open in vim in one tab using all the existing configuration from my local Mac machine, and then another tab where my code changes were immediately available for running the tests against in the Linux virtual machine. It was super seamless and sweet.

Here’s a four minute long gif of what it looks like – I’m refraining from embedding it so you don’t need to download a 4mb image if you don’t want to, and so you can open it in a new tab to start at the beginning easily if you want to.

I don’t really love my solution and I should probably consider it further, but I know the tests are passing, including the new one I added. I think being a little sleep deprived lowered my inhibition tonight.

Quick tip: track command history in zsh

July 11, 2015

I switched from bash to zsh a few months ago and it’s been mostly sweet. I noticed that it wasn’t tracking my command history, so I did a little googling and got it working by adding these commands to my ~/.zshrc:

export HISTFILE=~/.zsh_history
export SAVEHIST=1000

I picked 1000 kind of randomly; a bigger number would probably be fine or nice.

using method_missing with class methods in Ruby

July 8, 2015

Ruby’s method_missing let’s you write some weird code:

class Poet
  def initialize
    @words = []
  end

  def method_missing(message, *args, &block)
    @words << message
    message.to_s.end_with?("?") ? sentence : self
  end

  private

  def sentence
    @words.join(" ")
  end
end

puts Poet.new.why.not.go.for.a.walk?

I’ve seen method_missing used to handle unexpected message on instances of a class before, but never for class methods. But, like, why not?

class LoudSpeaker
  def self.method_missing(message, *args, &block)
    if [:exclaim, :yodel, :howl, :sob, :beg].include?(message)
      puts args.first
    else
      # we don't want to handle this missing method, we want Ruby to raise the
      # NoMethodError it ought to
      super
    end
  end
end

LoudSpeaker.exclaim "helloooo!"

It’s just methods, so go for it.