Latest from the Rocketship

We like writing about our personal experiences, our challenges and our take on programming and management. Enjoy!

TILFebruary 20, 2019by Vitaly Platonov

Elixir code formatting in IDE (with Atom set up example)

Today I learned that Elixir has a great feature that allows you to format your code.

Here are a few reasons you may want to do that (borrowed from The Go Blog):

  • easier to write: never worry about minor formatting concerns,

  • easier to read: when all code looks the same you need not mentally convert others' formatting style into something you can understand.

  • easier to maintain: diffs show only the real changes.

  • uncontroversial: never have a debate about spacing or brace position ever again

In fact, you actually want to format your Elixir code as you make it, meaning there is no need to format all of your project's code every time.

The following bash command does it well on a single file:

cd $projectPath && mix format $filePath

Where: $projectPath - full path to your project's root directory; $filePath - full path to a file you're saving

My IDE of choice happened to be Atom and I'll talk briefly on setting it up.

The Process Palette package so far is the best for this task.

My Shell Command field (with variables) in Process Palette looks the following cd {projectPath} && mix format {fileAbsPath}

TILFebruary 20, 2019by Suhomozgy Andrey

How to display numbers with currency formatting in JS?

Use Intl.NumberFormat to enable country / currency sensitive formatting.


const toCurrency = (n, curr, LanguageFormat = undefined) =>

  Intl.NumberFormat(LanguageFormat, { style: 'currency', currency: curr }).format(n);


toCurrency(123456.789, 'EUR'); // €123,456.79  | currency: Euro | currencyLangFormat: Local

toCurrency(123456.789, 'RUB'); // RUB 123,456.79  | currency: Ruble | currencyLangFormat: Local

toCurrency(123456.789, 'RUB', 'Ru-ru') // 123 456,79 ₽  | currency: Ruble | currencyLangFormat: Russian

toCurrency(123456.789, 'USD', 'en-us'); // $123,456.79  | currency: US Dollar | currencyLangFormat: English (United States)

toCurrency(123456.789, 'USD', 'fa'); // ۱۲۳٬۴۵۶٫۷۹ ؜$ | currency: US Dollar | currencyLangFormat: Farsi

toCurrency(322342436423.2435, 'JPY'); // ¥322,342,436,423 | currency: Japanese Yen | currencyLangFormat: Local

toCurrency(322342436423.2435, 'JPY', 'fi'); // 322 342 436 423 ¥ | currency: Japanese Yen | currencyLangFormat: Finnish

TILFebruary 19, 2019by 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.

TILFebruary 19, 2019by Alexander Budchanov

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)

TILFebruary 18, 2019by Alexander Budchanov

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>

TILFebruary 12, 2019by Dmitry Voronov

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

TILFebruary 11, 2019by Alexander Budchanov

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}

TILFebruary 06, 2019by Alexander Budchanov

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?

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)

TILFebruary 03, 2019by Dmitry Voronov

Use hash or case-statement in Ruby?

Often, when we need to get a value based on the other one, we're using a case-statement. Like this


def realizing_trade_type(realizable_trade_type)

  case realizable_trade_type

  when 'buy'

    'sell'

  when 'short'

    'cover'

  when 'buy_contract'

    'sell_contract'

  when 'short_contract'

    'cover_contract'

  end

end

But, if the conditions and the results are simple values, why don't we use hash for this? We can :)


REALIZING_TRADE_TYPES = {

  'buy'            => 'sell',

  'short'          => 'cover',

  'buy_contract'   => 'sell_contract',

  'short_contract' => 'cover_contract'

}.freeze

Here is the benchmark of both options, executed 10000000 times. It shows that a hash is faster in times for such the kind of usage.


>> require 'benchmark'

true

>> Benchmark.bm(15) do |x|

  x.report('hash') { 10_000_000.times { REALIZING_TRADE_TYPES['buy'] } }

  x.report('case-statement') { 10_000_000.times { realizing_trade_type 'buy' } }

  x.report('empty') { 10_000_000.times {} }

end

                      user     system      total        real

hash              0.990423   0.003412   0.993835 (  1.057612)

case-statement    1.752263   0.004531   1.756794 (  1.762030)

empty             0.380810   0.000728   0.381538 (  0.382153)

So, it's better to use a hash when you are just retrieving some values (like in the example above). If there is additional logic to execute, a case-statement is still a way to go.

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 29, 2019by Suhomozgy Andrey

Passing events between browser tabs without WebSockets

What if need to pass some event from one browser tab to each others? And we don't have WebSockets...

It turns out that localStorage raises events 😍

Namely, events are triggered when an item is added, deleted, or changed in another context. In essence, this means that when you change localStorage in one tab, other tabs can find out about this by listening to the storage event of the global window object. For example:


  window.addEventListener('storage', function (event) {

    console.log(event.key, event.newValue);

  });

Of course, there are some restrictions on use (you can read about them here https://www.w3.org/TR/webstorage/), but for simple cases it is match perfect.

TILJanuary 29, 2019by Alexander Blinov

Rename the key name in the javascript object

How to rename the key name in the javascript object? That’s easy!

Lets create function to do that:


const renameKey = (object, key, newKey) => {

  const clonedObj = clone(object);

  const targetKey = clonedObj[key];



  delete clonedObj[key];

  clonedObj[newKey] = targetKey;

  return clonedObj;

};

Here is clone function:


const clone = (obj) => Object.assign({}, obj);

Example:


let contact = { 

    id: 1, 

    name: "contact name"

};



contact = renameKey(contact, 'id', 'value');

contact = renameKey(contact, 'name', 'label');



console.log(contact); // { value: 1, label: "contact name" };

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 26, 2019by Andrey Morozov

Command for create zip archive without gem's 📁


class CreateZipCommand

  def call(files)

    # Create temp directory for files 

    tmp_dir = Dir.mktmpdir

    tmp_zip_path = File.join(tmp_dir, "files.zip")



    # Move files to the temporary folder you created above

    files.map do |file|

      download_file(file, tmp_dir)

    end



    # Go to the folder and archive the entire contents

    `cd #{tmp_dir} && zip #{tmp_zip_path} ./*`



    # Return zip path

    tmp_zip_path

  end

end



> CreateZipCommand.new.call(files)

=> "/var/folders/bk/0c864z710654sx555jpdpx9c0000gn/T/d20190126-7447-d27fpl/files.zip")



Most gems for working with archives eat a lot of memory when working with large files. This solution does not have these problems.

Make sure that the zip utility is installed on your computer - it don't work without it

TILJanuary 24, 2019by Alexander Blinov

Reset values in React Final Form w/ keepDirtyOnReinitialize

How to reset values with keepDirtyOnReinitialize in React Final Form after submitting.

The Issue

If keepDirtyOnReinitialize is applied to your form, then form.reset() no longer able to remove field's value.

The Solution

The Solution is simple— if the form submitted successfully: first change keepDirtyOnReinitialize to false -> perform form reset form.reset() -> and change keepDirtyOnReinitialize back to true.


<Form

  onSubmit={onSubmit}

  keepDirtyOnReinitialize

  render={({ handleSubmit, form }) => (

    <form

       onSubmit={(event) => {

       const promise = handleSubmit(event);

       promise && promise.then(() => {

         form.setConfig('keepDirtyOnReinitialize', false);

         form.reset();

         form.setConfig('keepDirtyOnReinitialize', true);

       })

      return promise;

      }}

    >

    ...

   </form>

}/>

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 23, 2019by Andrey Morozov

Webhook integration in development with Ngrok 🚀

All begin when I’m using the Pipedrive (is a sales management tool designed to help small sales teams manage intricate or lengthy sales processes) webhook.

The solution was easy I just create an ngrok tunnel


> ngrok http 3000 



Session Status                online

Account                       Andrey (Plan: Free)

Version                       2.2.8

Region                        United States (us)

Web Interface                 http://127.0.0.1:4040

Forwarding                    http://b1256cb6.ngrok.io -> localhost:3000

Forwarding                    https://b1256cb6.ngrok.io -> localhost:3000

and then sent the webhook to the generated address and it works. In local development I can user webhook from another api! It's work 🚀

To use it you need to do 4 steps:

  1. Register on ngrok

  2. Download ngrok from site

  3. Connect your account

  4. Run it 🚀


ps

In addition, this service has a ngrok-tunel gem that allows you to fully integrate it with your application. But that's another story 💎

TILJanuary 22, 2019by Alexander Blinov

Reset values in React Final Form

How to reset values in React Final Form after submitting

At first I wrote the following code:


<Form

  onSubmit={onSubmit}

  render={({ handleSubmit, form }) => (

    <form

      onSubmit={event => {

        handleSubmit(event).then(() => {

          form.reset();

        })

      }}

    >

    ...

    </form>

  }/>

If we implement a function like this, we will get an error "Uncaught TypeError: Cannot read property 'then' of undefined”, when we try to submit invalid form.

Invalid forms — form with validation errors.

The solution

To avoid this error, we need to place handleSubmit(event) to a variable, and if the variable is not undefined call .then().


onSubmit={(event) => {

  const promise = handleSubmit(event);

  promise && promise.then(() => {

    form.reset();

  })

  return promise;

}}

TILJanuary 21, 2019by Suhomozgy Andrey

Zip two arrays repeating values of the smaller one

So, we have two arrays and we need map each element of the first array to an element from the second. The length of the second array may be less or equal than the first one. What’s if we need to repeat values of the smaller one?

For example, there are two arrays:


const numbers = [1, 2, 3, 4, 5, 6, 7, 8]

const letters = ['a', 'b', 'c']



And we need to get something like:


[ { number: 1, letter: 'a' },

  { number: 2, letter: 'b' },

  { number: 3, letter: 'c' },

  { number: 4, letter: 'a' },

  { number: 5, letter: 'b' },

  { number: 6, letter: 'c' },

  { number: 7, letter: 'a' },

  { number: 8, letter: 'b' } ]

You can use Google or your math skills 😎 and finally will come to this:


const getNormalizedIndex = (index, array) => ((index + array.length) % array.length) % array.length



const numbers_letters = numbers.map((item, index) => {

  return {

    number: item,

    letter: letters[getNormalizedIndex(index, letters)]

  }

})

Run it and you’ll get the expected result.