Alexander Spitsyn

TILSeptember 18, 2019by Alexander Spitsyn

Rewind Files Before Reading

Suppose you have a form with a file input and you need to forward that file to an external service through your Rails app. Params will look like this:


{ "file"=> #<ActionDispatch::Http::UploadedFile:0x00007fce9a8af140 @tempfile=#<Tempfile:/path/to/the/file.xlsx>, ... > }

The external service can not be reached for the first time sometimes, so you decided to set some retry count.


retry_count = 5

while retry_count > 0

    begin

        HTTPClient.new.post(url, params, headers)

    rescue ExternalServiceUnavailable => e

        retry_count -= 1

    end

end

The code above uses HTTPClient gem for making http requests and it will raise the following error in case there will be at least one retry:

ArgumentError: Illegal size value: #size returns 154139 but cannot read

The thing is that HTTPClient uses IO#read to read the file by chunks for making POST request. The lineno pointer reaches the end of the file after read, and you can not read bytes from the I/O stream anymore:


params['file'].tempfile.eof? # => true

To fix that, use IO#rewind – it positions IO to the beginning of input, resetting lineno to zero. Just add checking the file for eof before each request and rewind it if needed:


retry_count = 5

while retry_count > 0

    begin

        params['file'].tempfile.rewind if params['file'].tempfile.eof? # check eof

        HTTPClient.new.post(url, params, headers)

    rescue ExternalServiceUnavailable => e

        retry_count -= 1

    end

end

TILSeptember 02, 2019by Alexander Spitsyn

Determining class of an object with case equality operator (===)

Case equality operator (or triple equals, ===) in Ruby returns true if the passed class is in the ancestors list of the passed object's class:


1.class.ancestors # [Integer, Numeric, Object, ...]

Numeric === 1 # true

Object === 1 # true

So it can be used for determining object's class:


String === 'abc' # true

'abc'.class #=> String

In cases above the case equality operator works like #kind_of? (or #is_a?):


1.kind_of?(Integer) # true

1.is_a?(Numeric) # true

The classes above has different implementations of === operator, that's why the results of comparison are different:


String.===('abc') # the same as String === 'abc'

Also it means that order of the arguments is important:


1 === Integer # false

TILAugust 30, 2019by Alexander Spitsyn

Testing external API integration with VCR gem

Suppose your application connects to an external service via API and you have a wrapper for this API that handles and parses response. The VCR gem gives you ability to store parsed response in a special format (cassetes). VCR makes a real request to the API for the first test run and writes it's response to the cassete for next test runnings.

First you need to set VCR configuration:


VCR.configure do |config|

  config.cassette_library_dir = "spec/vcr_cassettes"

  config.hook_into :webmock

end

And then write specs like the following:


RSpec.describe ExternalService do

  describe '#new_order' do

    let(:params) { { foo: 'bar' } }



    it 'creates new order' do

      VCR.use_cassette("external_service") do

        result = subject.new_order(params) # makes HTTP POST to an external service



        expect(result.successful?).to be_truthy

        expect(result.data).to have_key('order_id')

      end

    end

  end

end

See more details on the official page of the gem.

TILMay 15, 2019by Alexander Spitsyn

Handling IP addresses using PostgreSQL

PostgreSQL provides a inet and cidr datatypes for storing net addresses and proceed operations with them.

Host address and it's subnet can be stored with inet, while cidr can contain only network address:


select inet '192.168.0.1/24';

      inet

----------------

 192.168.0.1/24


select cidr '192.168.0.0/24'; -- valid cidr

      cidr

----------------

 192.168.0.0/24


select cidr '192.168.0.1/24'; -- invalid: cidr must not be a host address

ERROR:  invalid cidr value: "192.168.0.1/24"

LINE 1: select cidr '192.168.0.1/24';

                    ^

DETAIL:  Value has bits set to right of mask.

In case there's no number after slash in cidr address the netmask is to equal 32:


select cidr('127.0.0.1');

     cidr

--------------

 127.0.0.1/32

The value above represents a subnet address, while the same value passed to inet represents a host:


select inet('127.0.0.1');

   inet

-----------

 127.0.0.1

Checking inclusion or equality can be performed with >>= and <<= operators:


select inet '192.168.0.1/24' >>= inet '192.168.0.0'; -- returns true

select cidr '192.168.0.0/24' >>= inet '192.168.0.0/12'; -- returns false

select cidr '192.168.0.0' >>= cidr '192.168.0.0'; -- returns true

And getting a netmask by a net address can be performed with netmask:


select netmask(inet('192.168.0.0/24')); -- returns 255.255.255.0

select netmask(cidr('127.0.0.1')); -- returns 255.255.255.255

TILMarch 05, 2019by Alexander Spitsyn

How to conditionally render template in Grape resources?

Suppose you need to conditionally render some template in a given endpoint of a Grape resource. There is a env data in Grape::API class that represents Rack environment of the request. env is just a simple Ruby hash and we can pass the template name to it's api.tilt.template key to render that template:


env['api.tilt.template'] = 'foo/bar.json'

Then let's define a render method in Base resourсe:


class Base < Grape::API

  def self.inherited(subclass)

    super



    subclass.instance_eval do

      helpers do

        def render(template_name)

          env['api.tilt.template'] = template_name

        end

      end

    end

  end

end

Now we can use it in the following way:


class ContactsResource < Base

  desc 'Create contact', http_codes: [[201, 'Created']]



  post '/contacts' do

    contact = Contact.new(params[:contact])



    if contact.save

      render 'v1/contacts/show.json'

    else

      error!({ errors: contact.errors.full_messages }, 422)

    end

  end

end

TILMarch 05, 2019by Alexander Spitsyn

Append string to a route with a slash operator

If you need to append string to some route to build a path name you can use few ways:


Rails.root + 'foo/bar'

[Rails.root, 'foo/bar'].join('/')

"#{Rails.root}/foo/bar"

But also there is a Pathname#/ method, so you can append string to route using a slash:


Rails.root/'foo/bar' # => #<Pathname:/Users/alex/myproject/foo/bar>

Looks more readable!

TILFebruary 04, 2019by Alexander Spitsyn

Getting rid of inefficient constantize

Trying to improve performance of one controller I've found the following:


def application_klass

  @application_klass ||= "application/#{params[:type]}".classify.constantize

end

There are Application::Rental and Application::Bridge models in our rails app, so the method above was used in two places:

  1. application_klass.to_s – to get the model name as a string

  2. application_klass.model_name.human – to get a class name without module prefix

I've refactored method in the following way:


def application_klass

  @application_klass ||= "application/#{params[:type]}".classify

end 

And it's occurrences now looks like this:

  1. application_klass – just call this method to get a model name

  2. application_klass.demodulize – using demodulize to split a string with ::

I've got rid of unnecessary constantize method, which is quite inefficient: just imagine that your application tries to find a constant in your project with the name specified in the string. See the benchmarks below to evaluate performance gains:


counter = 100_000

type = 'Rental'



Benchmark.bm(30) do |x|

  x.report('demodulize: ')                   { counter.times { "application/#{type}".classify.demodulize } }

  x.report('constantize.model_name.human: ') { counter.times { "application/#{type}".classify.constantize.model_name.human } }

end



                                     user     system      total        real

demodulize:                      3.560000   0.760000   4.320000 (  4.381891)

constantize.model_name.human:    8.500000   0.070000   8.570000 (  8.601007)

TILDecember 28, 2018by Alexander Spitsyn

How to implement inheritance in Grape resources?

Grape uses specific DSL to define endpoints in API, that’s why you can’t use base class’ instance methods in descendant resources. But there’s one trick:

There’s an #inherited class method in Ruby which is triggered every time some class inherits from ancestor class. It passes one argument - descendant class. Calling descendant’s #instance_eval method we can place any useful stuff inside a block: methods, helpers, before-do’s, etc, in this way evaluating it in context of subclass.


class Base < Grape::API

  def self.inherited(subclass)

    super



    subclass.instance_eval do

      helpers do

        def current_user

          @current_user ||= User.find(params[:user_id])

        end

      end

      # ...

    end 

  end

end



class DocumentsResource < Base

  post '/documents' do

    @document = current_user.documents.build

    # ...

  end

end

Note that it’s not real inheritance because Base class has not methods defined inside subclass#instance_eval block.