First Padrino framework contribution

How a find for bug ended in one contribution

Posted on January 7, 2017 2 minute read

For those who don’t know what Padrino is, it’s a web framework written in Ruby, known to be lighter compared with Ruby on Rails, but complete enough to create full-blown web applications.

Padrino allows setting default values in the controllers to be passed as URL parameters in case we don’t provide it, either via HTTP request parameters or via parameters in the absolute_url method, to compute the final URL of a request. In a quest to find the source of an odd behaviour, I ended finding a bug in the framework.

So, in order to isolate the problem I’ve created a sample application and the most important code is this controller:

1
2
3
4
5
6
7
8
9
module Padre
  App.controller provider: 'google' do
    get :index, map: '/:provider/index' do
    end

    post :a_to_a_to_agent_answered, map: '/:provider/a2a/:call_sid/agent/:agent_id/answered' do
    end
  end
end

In this case I set the default provider to google, so the parameters in the method should include {provider: 'google'}.

That was not the case, so I need to keep digging!

The first thing I’ve start looking was at the tests, and I found a very interesting test:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
it 'should use default values' do
  mock_app do
    controller :lang => :it do
      get(:index, :map => "/:lang") { "lang is #{params[:lang]}" }
    end
    # This is only for be sure that default values
    # work only for the given controller
    get(:foo, :map => "/foo") {}
  end
  assert_equal "/it",  @app.url(:index)
  assert_equal "/foo", @app.url(:foo)
  get "/en"
  assert_equal "lang is en", body
end

To check if the padrino code was performing as expected I’ve added the line: assert_equal "/pt", @app.url(:index, lang: 'pt')

and got the confirmation of the bug:

Expected: "/pt"
  Actual: "/it"

So using a TDD approach start digging in the code and found the problematic code:

1
2
3
4
5
6
7
8
9
10
11
12
##
# Expands the path by using parameters.
#
def expand(params)
  params = params.merge(@default_values) if @default_values.is_a?(Hash)
  params, query = params.each_with_object([{}, {}]) do |(key, val), parts|
    parts[handler.names.include?(key.to_s) ? 0 : 1][key] = val
  end
  expanded_path = handler.expand(:append, params)
  expanded_path += ?? + Padrino::Utils.build_uri_query(query) unless query.empty?
  expanded_path
end

In the method’s first line, the code is using the given parameters and merging them with the defaults ones, but what we want the other way around. We want the default parameters to be overridden by the ones we provide. So it seems a quick fix!

So I went to read the contribution guidelines and opened a bug. Later on, I’ve opened a Pull Request.

It was good to see that if you have a good Git work flow and follow some principles on creating issues and commit messages, it will ease the acceptance. So in this case, it was accepted without any question!

If you want to ask some question or share your thoughts please contact me from the links on the left.