Today I Learned

This project exists to catalogue the sharing & accumulation of knowledge as it happens day-to-day. Posts have a 200-word limit, and posting is open to any Rocketeer as well as selected friends of JetRockets. We hope you enjoy learning along with us.

13 posts about #rails

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

Safe Navigation vs Try in Rails (Part 2: Performance)

This Note is an extension of Safe Navigation vs Try in Rails (Part 1: Basic Differences)

Method try() comes from Active Support component of Ruby on Rails. Safe Navigation Operator is Ruby native feature.

Let’s check performance!

require 'benchmark'

class Foo
  attr_accessor :name
end

foo = Foo.new
bar = nil

Benchmark.bm(35) do |x|
  x.report('Successful access: try(...): ')         { 1_000_000.times { foo.try(:name) } }
  x.report('Successful access: &.: ')               { 1_000_000.times { foo&.name } }
  x.report('Successful access: control sample: ')   { 1_000_000.times { foo.name } }
  x.report('Failed access: try(...): ')             { 1_000_000.times { bar.try(:nonexistent) } }
  x.report('Failed access: safe navigation: ')      { 1_000_000.times { bar&.nonexistent } }
end;nil
                                          user     system      total        real
Successful access: try(...):          0.498216   0.005748   0.503964 (  0.530010)
Successful access: &.:                0.062146   0.000943   0.063089 (  0.069714)
Successful access: control sample:    0.062411   0.001098   0.063509 (  0.069603)
Failed access: try(...):              0.172535   0.004374   0.176909 (  0.194386)
Failed access: safe navigation:       0.054141   0.001029   0.055170 (  0.065502)

Safe navigation is about 7 times faster than method try() for successful navigation and 3 times faster for unsuccessful.

Safe Navigation vs Try in Rails (Part 1: Basic Differences)

There are some ways of preventing errors like undefined method for nil:NilClass.

  • Rails Method try(...)
  • Safe Navigation Operator (&.)
  • Logical operator && (AND)

Here is how these options look like:

user.try(:company).try(:name)
user&.company&.name
user && user.company && user.company.name

But there are some differences.

1. If model User hasn’t relation compppany (it may be just a typo or renamed model relation/attribute):

user.try(:compppany).try(:name)
=> nil

You will receive nil and never been know about this typo.

user&.compppany&.name
=> NoMethodError: undefined method `compppany' for #<User:0x000000123456789>

and

user && user.compppany && user.compppany.name
=> NoMethodError: undefined method `compppany' for #<User:0x000000123456789>

Looks better!

2. If model User has relation company, but company is false. User.new(company: false):

user.try(:company).try(:name)
=> nil
user&.company&.name
=> NoMethodError: undefined method `name' for false:FalseClass

Safe Navigation recognized false. Awesome!

user && user.company && user.company.name
=> false

Hmmm, it does not look like we want.

3. Performance Read the second part Safe Navigation vs Try in Rails (Part 2: Performance)

How to delete polymorphic models cascade

If you use a polymorphic model in your Rails application, like in example

class Trade < ActiveRecord::Base
  has_many :gl_entries, as: :source, dependent: :destroy
end

class GlEntry < ActiveRecord::Base
  belongs_to :source, polymorphic: true
end

You will not be able to add the usual foreign keys for cascading delete records. But this can be implemented using a database.

To do this, you need to write a trigger in the database that will run the delete function for each record.

CREATE FUNCTION deleteGlEntriesOfTrade()
  RETURNS TRIGGER
  SET SCHEMA 'public'
  LANGUAGE plpgsql 
  AS $$
  BEGIN
    DELETE FROM gl_entries WHERE source_id = OLD.id AND source_type = 'Trade';
    RETURN OLD;   
  END;
  $$;
      
CREATE TRIGGER deleteTradesGlEntriesTrigger 
  BEFORE DELETE ON trades
  FOR EACH ROW EXECUTE PROCEDURE deleteGlEntriesOfTrade();

Create a migration and use :)

Sidekiq Stats in Rails

If you use Sidekiq in your Rails project, you can watch tasks statistics through the web interface. Usually, you can find it here: https://yourdomain.com/sidekiq (but in your project path may be different). If you don’t have access there, you can get the same data in Rails console.

To get statistics (number of tasks) by each queue:

Sidekiq::Stats.new.queues
=> {"local_cache"=>0, "default"=>0, "ts_delta"=>0, "mailchimp"=>0, "recorder"=>0}

Global statistics are available this way:

Sidekiq::Stats.new.fetch_stats!
=> {:processed=>61390,
 :failed=>3220,
 :scheduled_size=>0,
 :retry_size=>0,
 :dead_size=>4,
 :processes_size=>1,
 :default_queue_latency=>0,
 :workers_size=>0,
 :enqueued=>0}

The same stats are available separately:

stats = Sidekiq::Stats.new
stats.processed # number of processed tasks
stats.failed # number of failed tasks
stats.enqueued # number of enqueued tasks

and etc.

Also Sidekiq::Stats allows to look historical data. You can specify period. Something like:

s = Sidekiq::Stats::History.new(2, Date.parse('2019-02-05'))
  • first argument: number of days,
  • second argument: date until which return stats (default value is today and can be omitted)
s.processed
=> {"2019-02-05"=>0, "2019-02-04"=>0}
s.failed
=> {"2019-02-05"=>0, "2019-02-04"=>0}

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)

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

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

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

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

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')

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.