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.

6 posts by Alexander Budchanov

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)

Force Downloading of File instead of Opening in Browser

When you go through the link, some files will be opened in the browser. Such behavior is typical for some content types (e.g., images, pdf, etc.)

However, you can force file downloading instead of opening when an user clicks on the link though.

1st way (frontend):

HTML attribute download allows you to do this.

<a href="/public/report.pdf" download="stat_report">

If the value of the attribute is omitted, the original filename would be used. However, be careful. This attribute isn’t supported in some old browsers without HTML5 support. Renaming does not work if the given file stored on another host.

2nd way (backend):

You can set HTTP header Content-disposition.

for Nginx:

location ~* /public/(.+\.pdf)$ {
    add_header Content-disposition "attachment; filename=$1";
}

for Apache:

<IfModule mod_headers.c>
    <FilesMatch "\.(?i:pdf)$">
        ForceType application/octet-stream
        Header set Content-Disposition "attachment"
    </FilesMatch>
</IfModule>

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}

Keep clean your git repos!

Many developers don’t keep their local repositories clean. There is a way how to automate it though. Let’s look at a few useful commands:

To clean refs to nonexistent branches in the remote:

$ git fetch --prune

--prune before fetching, remove any remote-tracking references that no longer exist on the remote.

To estimate how many branches merged into dev:

$ git branch --merged dev | wc -l

--merged option can filter list to branches that you have merged into the given branch. Squash and rebase merges usually aren’t detected by --merged.

List of branches merged into dev:

$ git branch --merged dev

List of remote branches merged into dev:

$ git branch --merged dev --remote

If you are courageous then:

$ git branch --merged dev | egrep -v "(^\*|master|dev)" | xargs git branch -d

It removes all local branches that merged into dev (except dev and master). This is a potentially damaging operation. It can delete branches actually needed. So if you use the different approach to work with Git, you could remove some of branches manually instead. I hope you do not store all old branches, do you?