Back

When being lazy is actually a good thing

“Practice not-doing and everything will fall into place.” – Lao Tzu

Lao Tzu

Recently I encountered a small and funny problem that I’d like to share with you.

Imagine there is a model Trivium (question: string) and a model TriviaAnswer (trivium:references, answer:string, is_correct:boolean). Essentially they represent a question with related answer options. There is also a word document containing data to be loaded. It looks something like this:

1. What is the English translation of Punta Nizuc?
- Point of the peninsula 
- Nose of the dog
- A place of paradise
- Ear of the rabbit
2. The earliest Mayan settlements on the Yucatan Peninsula date back to?
- 1800 BC
- 1630 AD
- 1250 BC 
- 1830
...

The questions and answers are provided by non-technical people and are not strictly structured. Loading the data is a one-time operation, or at least it should not happen often, so no user interface is required for this.

Now we can shape the requirements:

  • the solution should be simple, quick and cheap
  • data will be loaded by a developer or devops guy
  • we need to test new data on staging before deploying to production

So what are our options?

Firstly, it’s possible to create a script parsing the incoming data. The downside is that it’s easy to overlook an error in the data structure, so the script will have to be quite intelligent. But the solution should not involve significant effort – this won’t do.

The other option is to delegate writing bunch of Trivium.create!(...) to a newbie or an intern. But obviously this would be too cruel in addition to being error prone and non-reusable. As for doing it myself – I feel too lazy for that.

We need something in between: quicker and dirtier than a parser, but smarter than straightforward create! calls.

Here comes the DSL. What if we could write something like this:

q "What is the English translation of Punta Nizuc?" do
  a "Point of the peninsula"
  a "Nose of the dog", true
  a "A place of paradise"
  a "Ear of the rabbit"
end
q "The earliest Mayan settlements on the Yucatan Peninsula date back to?" do
  a "1800 BC", true
  a "1630 AD"
  a "1250 BC"
  a "1830"
end
# ...

Looks pretty, in my opinion! Let’s convert the initial data into DSL format with a few regular expressions:

s/^\d+\.\s+(.+)/q "\1" do/g
s/^-\s+(.+)/a "\1"/g

And wrap the data into this:

DSL::load_questions_with_answers do
  # questions and answers
end

Now add some code to parse it:

module DSL
    def self.load_questions_with_answers(&block)
      DSL::Root.new.instance_eval(&block)
    end
    
    class Root
      def q(question, &block)
        @trivium = Trivium.create!(question: question)
        instance_eval(&block)
      end
    
      def a(answer, is_correct = false)
        @trivium.answers.create!(answer: answer, is_correct: is_correct)
      end
    end
end

And it’s quite ready. Wrap it as a rake task or migration and you’re good to go. The benefits are:

  • it was pretty quick
  • minimal routine work was required
  • solution is reusable
  • it was fun!

To sum up, it’s worth being lazy at times. Keeping that in mind helps you come up with cleaner and simpler solutions, compared to head-on approach or over-engineering like there’s no tomorrow.