Check your hashes

In this brief post I want to report a brief experiece regarding a Ruby 2.0+ feature.

A few weeks ago I wanted to enforce the name of the arguments accepted when initializing a simple PORO.

The object itself was pretty simple and its purpouse was to abstract the access and add some behavior to another one. Guess what, the encapsulated object was an Hash.

Thanks to the fact that I was on Ruby 2.0+ (specifically the last stable, i.e. 2.3.0) I immediately looked at keyword arguments.

This feature is already well known and there are actually many blog posts that describe how to use it. My favorite article is probably Justin’s post.

I’ll introduce some code I’ll explain later:

class Resource
  attr_reader :type, :id

  def initialize(resource_type:, resource_id:)
    @type = resource_type
    @id = resource_id
  end

  %i[name description].each do |method|
    define_method(method) { instance.send method }
  end

  def instance
    @instance ||= type.constantize.find(id)
  end

  def permitted?
    permitted_types.include? type
  end

  def not_permitted?
    !permitted?
  end

  def permitted_types
    self.class.permitted_types
  end

  def self.permitted_types
    %w[NewsItem Gate Course Document Gallery Video Survey Content]
  end
end

In this example I’ve created a simple PORO which can be used to interact with different ActiveRecord resources such as NewsItem, Gate, Course, Document Gallery, Video, Survey and Content.

I wrote the code with some specs and so I felt I had my back somehow guarded.

Despite that, when I put the object in use, I stumbled in a banal ArgumentError. The code was actually a simple initialization of my object: Resource.new home_block_params

I knew that home_block_params was an Hash and I also knew that it was composed by the expected key and value pairs, i.e. resource_type and resource_id.
You will probably have already realized what I clumsily missed: the nature of the Hash keys.
They were indeed strings while they had to be symbols!

I wrote some specs (just for fun ;)) to clear the things up:

describe '#initialize' do
  it 'raises an ArgumentError if no resource_type and resource_id arguments has been specified' do
    expect do
      described_class.new
    end.to raise_error(ArgumentError)
  end

  it 'raises an ArgumentError if resource_type and resource_id arguments has been specified as strings' do
    expect do
      described_class.new('resource_type' => 'News', 'resource_id' => 1)
    end.to raise_error(ArgumentError)
  end

  it 'does not raise any error if resource_type and resource_id arguments has been specified as strings' do
    expect do
      permitted_home_block_resource
    end.not_to raise_error
  end
end

The quick solution in my case was to rely on Hash#symbolize_keys. Unfortunately this is something given by Ruby On Rails.

Anyway the take home message here is to properly refresh your memory when dealing with some midly new language features and to always consider the nature of Hash objects keys!

Leave a Reply

wpDiscuz