#rails

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)

TILJanuary 31, 2019by Maksim Agafonov

Form_for and Rails attributes API

Attributes API allows you to have complex object with specified structure in fields. But how to make forms with this fields?

The answer is pretty simple: you work with them like with nested attributes.

Let’s say, we have class Provider with attribute address.


class Provider < ApplicationRecord

  attribute :address, Provider::Address::Type.new, default: Provider::Address.new

Address has following attributes:


class Provider::Address

  extend Dry::Initializer



  option :country, optional: true

  option :zip, optional: true

  option :city, optional: true

  option :address, optional: true

Form will look like this:


= form_for resource_provider, provider_url do |f|

  = f.fields_for :address do |ff|

      = ff.label 'Country'

      = ff.text_field :country, value: @provider.billing_address.country



      = ff.label 'Zip'

      = ff.text_field :zip, value: @provider.billing_address.zip



      = ff.label 'City'

      = ff.text_field :city, value: @provider.billing_address.city



      = ff.label 'Address'

      = ff.text_field :address, value: @provider.billing_address.address

In controller you have to permit parameters:


def provider_params

    params.require(:provider).permit(address: [:city, :zip, :country, :address])

end

And that’s all. Now, in action method you can assign attributes like this:


resource_provider.attributes = provider_params

resource_provider.save

TILJanuary 28, 2019by Andrey Morozov

Use gem Recorder for db log 💪

Gem Recorder tracks changes of your Rails models

For start use this library in Rails 5.2 you need add gem to the Gemfile:


gem 'recorder', github: 'jetrockets/recorder', branch: 'rails_5_2'

Next:


> bundle install

> rails g recorder:install

> rails db:migrate

And final step -- add to model:


include ::Recorder::Observer

recorder async: false

And it's all 🎉

For an example - logs can be issued in the representer:


Recorder::Revision.where(item: object).order(id: :DESC).limit(10).map do |revision|

  {

    revision_id: revision.id,

    userId: revision.user_id,

    userEmail: revision.user&.email,

    change: changes_type(revision)

   }

end 



def changes_type(revision)

  if revision.data['changes'].include?('drive_file_id') && revision.data['changes'].size > 4 && revision.data['changes']['drive_file_id'].present?

    'Object updated & Document generated'

  elsif revision.data['changes'].include?('drive_file_id') && revision.data['changes'].size == 4

    'Document generated'

  elsif revision.data['changes'].include?('number') && revision.data['changes']['number'].nil?

    'Object created'

  else

    'Object updated'

  end

end

But of course this is an abstract example with my data 😏


https://github.com/jetrockets/recorder

TILJanuary 23, 2019by Roman Smirnov

How to add index to a large working app on Rails?

Well known fact is that PostgreSQL and many other RDBMS lock write access on the table while the index is being created. It is not acceptable when your project is large enough to allow a downtime for such the small adjustment like a new index.

There is a way to avoid the write-lock though. You can create the index concurrently. But how to do it with Rails Migrations?

First of all you can pass :algorithm option:


class AddIndexOnBatchIdToFundTrades < ActiveRecord::Migration[5.0]

  def change

    add_index :fund_trades, :batch_id, algorithm: :concurrently

  end

end

But when you try to run such the migration, you would get the following error:


PG::ActiveSqlTransaction: ERROR:  CREATE INDEX CONCURRENTLY cannot run inside a transaction block

: CREATE  INDEX CONCURRENTLY "index_fund_trades_on_batch_id" ON "fund_trades"  ("batch_id")

That's because any migration by default is executed inside a transaction. Thankfully there is a way to pass it through - use disable_ddl_transaction! to run your migration without a transaction:


class AddIndexOnBatchIdToFundTrades < ActiveRecord::Migration[5.0]

  disable_ddl_transaction!



  def change

    add_index :fund_trades, :batch_id, algorithm: :concurrently

  end

end

TILJanuary 21, 2019by Dmitry Voronov

Render and combine PDF files into one

Render PDF files from HTML templates in Rails can be done using WickedPDF.

First you need to use ActionView to render HTML to a string:


def render_to_string(data)

  action_view = ActionView::Base.new

  action_view.view_paths = ActionController::Base.view_paths



  action_view.class_eval do

    include ApplicationHelper

    include PDFHelper

    # or other helpers

  end



  action_view.render template: 'pdf/template.html',

                     layout: 'layout/pdf.html',

                     locals: { data: data }

end

Then you can render pdf with the desired settings like this:


# first pdf file with some view settings and values from data1...

pdf1 = WickedPdf.new.pdf_from_string(

        render_to_string(data1), {

            pdf: 'report1',

            page_size: 'Letter',

            orientation: 'Portrait'

        })

# ...and second pdf file with some view settings and values from data2

pdf2 = WickedPdf.new.pdf_from_string(

        render_to_string(data2), {

            pdf: 'report2',

            page_size: 'Letter',

            orientation: 'Landscape'

        })

And now you can combine it with CombinePDF gem, that provide you parse method to get PDF content and to_pdfmethod to render the result back to PDF.


combiner = CombinePDF.new

combiner << CombinePDF.parse(pdf1)

combiner << CombinePDF.parse(pdf2)

combiner.to_pdf

TILJanuary 16, 2019by Dmitry Voronov

Use image files from S3 in WickedPdf

If you need to use image files from S3 in your generated pdf file using WickedPdf, then you need first to download the image. You can create method that does this and add it to the helper.


require 'open-uri'



module PdfHelper

  def embed_remote_image(url, content_type)

    asset = open(url, "r:UTF-8", &:read)

    base64 = Base64.encode64(asset.to_s).gsub(/\s+/, "")

    "data:#{content_type};base64,#{Rack::Utils.escape(base64)}"

  end

end

And use image_tag instead of wicked_pdf_image_tag


= image_tag embed_remote_image(file.logo_url, 'image/jpeg')

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.