TIL from the Rocketship

TILAugust 28, 2019by Ilia Kriachkov

Ruby double splat (**) operator cheatsheet

The operator ** is useful as an options hash.


def one_method(**options);end

This form is completely similar to the following:


def another_method(options = {});end

In addition, you can strictly define the set of required keys for the method.


def one_strict_method(first_name:, last_name: , **options)

  puts "options: #{options}"

  greeting = "Hello #{first_name} #{last_name}"

  puts options[:upcase] ? greeting.upcase : greeting

end


pry(main)> one_strict_method(upcase: true)

ArgumentError: missing keywords: first_name, last_name



pry(main)> one_strict_method(first_name: 'John', last_name: 'Doe', upcase: true)

options: {:upcase=>true}

HELLO JOHN DOE

=> nil

Another advantage of double splat literal is that it works like #merge for Ruby Hash


class Contact::ShowRepresenter #:nodoc:

  def call(contact)

    {

      contact: {

        **base_info(contact),

        **legal_info(contact),

        # You can add something more complex here.

        # **GeoLocaionRepresenter.new.(contact)

      }

    }

  end



  private



  def base_info(contact)

    {

      id: contact.id,

      first_name: contact.first_name,

      last_name: contact.last_name,

      email: contact.email,

      phone: contact.phone

    }

  end



  def legal_info(contact)

    {

      legal_name: contact.legal_name,

      legal_type: contact.legal_type,

      mailing_address: contact.mailing_address

    }

  end

end


pry(main)>Contact::ShowRepresenter.new.(Contact.last)

=> {

  :contact=>{

    :id=>51986,

    :first_name=>"Ilia",

    :last_name=>"Kriachkov",

    :email=>"ilia.kriachkov@jetrockets.ru",

    :phone=>"+79000000000",

    :legal_name=>"JetRockets",

    :legal_type=>"LLC",

    :mailing_address=>"15 Let Oktyabrya Street, #10b, Tver, Russian Federation 170008"

  }

}

In conclusion, I want to demonstrate some benchmark results.

As you can see, the ** operator is a bit faster than Hash#merge.


require 'benchmark'

n = 50_000

Benchmark.bm(2) do |x|

  x.report('merge:             ') { n.times { merge } }

  x.report('double_splat_merge:') { n.times { double_splat_merge } }

end



def merge

  hash = { a: 'a' }

  { b: 'b' }.merge(hash)

end



def double_splat_merge

  hash = { a: 'a' }

  { b: 'b', **hash }

end


                     user      system      total        real

merge:               0.109247   0.088652   0.197899 (  0.204470)

double_splat_merge:  0.079480   0.003590   0.083070 (  0.083642)

TILAugust 26, 2019by Dmitry Voronov

How to store large JSON in PostgreSQL with Rails Attributes API

If you store large objects in the database (such as JSON), for example, data for big reports, then this can take up a lot of space. To reduce the size of data, you can compress and store in binary form.

PostgreSQL has a bytea field type for storing such data. You can add bytea column in Rails using migration


add_column :reports, :data, :binary

For binary field operations, you can use the Rails Attributes API and add a new BinaryHash data type


# app/types/binary_hash.rb



class BinaryHash < ActiveRecord::Type::Binary

  def serialize(value)

    super value_to_binary(value.to_json)

  end



  def deserialize(value)

    super case value

          when NilClass

            {}

          when ActiveModel::Type::Binary::Data

            value_to_hash(value.to_s)

          else

            value_to_hash(PG::Connection.unescape_bytea(value))

          end

  end



  private



  def value_to_hash(value)

    JSON.parse(

      ActiveSupport::Gzip.decompress(value),

      symbolize_names: true

    ) || {}

  end



  def value_to_binary(value)

    ActiveSupport::Gzip.compress(value)

  end

end

Register new type in initializers


# config/initializers/types.rb



ActiveRecord::Type.register(:binary_hash, BinaryHash)

And add to binary type attribute in model


# app/models/snapshot.rb



class Reports < ApplicationRecord

  attribute :data, :binary_hash

end

Tests show that data size is reduced by almost 3 times


Run time with 100000 width JSON

                           user     system      total        real

Compress JSON          0.008671   0.001535   0.010206 (  0.010885)

Decompress JSON        0.001357   0.000095   0.001452 (  0.001509)



json size       95450 bytes

binary size   33868 bytes

~ 2.82 times compression

TILAugust 26, 2019by Dmitry Voronov

A simple way to distribute jobs in Sidekiq queues

This option implies that jobs of one context are executed sequentially in one queue, and jobs of different contexts in parallel in different queues.

Let's look at the following example.

There are investment funds for which we want to make time-consuming reporting calculations. Jobs for calculation within the same fund are carried out sequentially so that there are no errors in the calculations, jobs of different funds are performed in parallel.

We automate the distribution of jobs in queues so as not to specify a queue manually.

Specify the queues in the sidekiq.yml configuration file:


:queues:

  - fund_processor_0

  - fund_processor_1

  - fund_processor_2

  - fund_processor_3

  - fund_processor_4

It is important that the queues are numbered from 0.

Now, when starting the worker, we indicate in which queue we will set the job depending on the fund. To do this, we use the operation of obtaining the remainder from dividing the fund ID and the count of queues. So we get the queue number.


# 5 queues

# fund ID % count of queues = queue number

# 1 % 5 => 1

# 2 % 5 => 2

# 3 % 5 => 3

# 4 % 5 => 4

# 5 % 5 => 0

def queue_name(fund_id)

  queue_number = fund_id % 5

  "fund_processor_#{queue_number}"

end

Start the worker, indicating to him the received queue name.

This can be done using the Sidekiq API


Sidekiq::Client.push(

  'queue' => queue_name(fund_id),

  'class' => Fund::ReportCalculator,

  'args' => [fund_id]

)

TILAugust 23, 2019by Eugene Komissarov

Rubyists life made easier with composition operators.

If you write Ruby code and wandered into FP world you might just started writing those little tiny methods inside your classes/modules. And that was awesome to write code like this:


class Import

  # Some code goes here...



  def find_record(row)

    [ Msa.find_or_initialize_by(region_name: row[:region_name], city: row[:city], state: row[:state], metro: row[:metro] ), row ]

  end



  # record is one of:

  # Object - when record was found

  # false - when record was not found

  def update_record(record, attributes)

    record.attributes = attributes

    record

  end



  # record is one of:

  # false

  # ZipRegion

  def validate_record(record)

    case record

    when false

      [:error, nil]

    else

      validate_record!(record)

    end

  end



  # record is ZipRegion object

  def validate_record!(record)

    if record.valid?

      [:ok, record]

    else

      error(record.id, record.errors.messages)

      [:error, record]

    end

  end



  def persist_record!(validation, record)

    case validation

    when :ok

      record.save

    when :error

      false

    end

  end

end

Yeah, I know there is YARD, and argument types are somewhat weird but at the time of coding, I was fascinated with Gregor Kiczales's HTDP courses (that was a ton of fun, sincerely recommend for every adventurous soul).

And next comes dreadful composition:


def process(row, index)

    return if header_row?(row)



    success(row[:region_name], persist_record!(*validate_record(update_record(*find_record(parse_row(row))))))

  end

The pipeline is quite short but already hard to read. Luckily, in Ruby 2.6 we now have 2 composition operators: Proc#>> and its reverse sibling Proc#<<.

And, with a bit of refactoring composition method becomes:


def process(row, index)

    return if header_row?(row)



    composition = method(:parse_row) >>

                             method(:find_record) >>

                             method(:update_record) >>

                             method(:validate_record) >>

                             method(:persist_record!) >>

                             method(:success).curry[row[:region_name]]



    composition.call(row)

Much nicier, don't you think? Ruby just became one step closer to FP-friendly languages family, let's hope there'll be more!

TILAugust 02, 2019by Maxim Romanov

How to blur a screen in React Navigation

Screens overlap each other in stackNavigator. React Navigation provides us not only with changing the background color of these screens, but also controlling their transparency.

To make the screen background blur, we first need to make the screens transparent.


import { createStackNavigator } from 'react-navigation';



export default createStackNavigator(

  {

    HomeStack,

    BlurModal,

  },

  {

    ...NAVIGATOR_CONFIG,

    mode: 'modal',

    cardStyle: { opacity: 1 },

    transparentCard: true,

  },

);

And then use blurView as background.


import React from 'react';

import { BlurView } from '@react-native-community/blur';

import Styled from 'styled-components';



function BlurModal() {

  return (

    <BlurWrapper blurType="light" blurAmount={20}>

      <Text>Modal with blur background</Text>

    </BlurWrapper>

  );

}



const BlurWrapper = Styled(BlurView)`

  position: absolute;

  top: 0;

  left: 0;

  bottom: 0;

  right: 0;

`;

TILJuly 31, 2019by Igor Alexandrov

Migrate tags in Rails to PostgreSQL array from ActsAsTaggableOn

ActsAsTaggableOn is a swiss army knife solution if you need to add tags to your ActiveRecord model.

Just by adding one gem to your Gemfile and acts_as_taggable to the model you get everything you need: adding tags, searching for a model by tag, getting top tags, etc. However, sometimes you don't need all these.

In our project, we used acts_as_taggable to store tags for Note model. Then we displayed a list of notes on several pages with assigned tags and had autocompleted input for tags on Note form. Everything worked well, but since we use PostgreSQL, I decided to store tags as an array in Note model.

First of all, I added tags Array<String> column to Note, after this migrated actsastaggable tags to notes table with migration.


class MigrateNoteTags < ActiveRecord::Migration[5.2]

  def change

    execute <<-SQL

    UPDATE notes 

    SET tags = grouped_taggings.tags_array 

    FROM

      (

      SELECT

        taggings.taggable_id,

        ARRAY_AGG ( tags.NAME ) tags_array 

      FROM

        taggings

        LEFT JOIN tags ON taggings.tag_id = tags.ID 

      WHERE

        taggable_type = 'Note' 

      GROUP BY

        taggings.taggable_id 

      ) AS grouped_taggings 

    WHERE

      notes.ID = grouped_taggings.taggable_id

    SQL

  end

end

To have backward compatibility, I added Note#tag_list method:


def tag_list

  tags.join(', ')

end

The last thing is to add the ability to search for tags. Since there about 500k records in the Notes table, I decided to create an SQL view:


CREATE OR REPLACE VIEW note_tags AS



SELECT UNNEST

  ( tags ) AS name,

  COUNT ( * ) AS taggings_count 

FROM

  notes 

GROUP BY

  name 

That's it! It takes from 100ms to 150ms to search for tags in this view, which is fine for me.

If you have more significant data sets, then the best would be to create tags table and add triggers to notes table that will update tags on INSERT/UPDATE/DELETE.

TILJuly 27, 2019by Alexander Ivlev

Request Api Adapter

When developing client applications, it is often necessary to send requests to the server.


// ...

client({ url: "/users.json", method: "GET" }).then(...)



We can make our lives a little easier. A convenient abstraction is apiAdapter


// apiAdapter.js

function getUsers() {

  return { url: "/users.json", method: "GET" };

}



const apiAdapter = createAdapter(client, {}, {

  getUsers,  

})

By defining a request in one place, you can now simply call the adapter method you want.


import apiAdapter from './apiAdapter'



apiAdapter.getUsers().then(...)

It is also a useful option to specify basic settings for all requests, as well as handling errors and successful requests.


const apiAdapter = createAdapter(

  client,

  { withCredentials: true },

  {

    getUsers,  

  },

  successHandler,

  errorHandler, 

)

Github

Live example

TILJuly 22, 2019by Alexey Belousov

Networking and thermal conditions debugging in Xcode 11

Xcode 11 introduces tool for networking and thermal conditions debugging. This feature requires device running iOS 13. Previously network conditioning feature was available within Additional Tools for Xcode.

See also

TILJuly 17, 2019by Maxim Romanov

How to handle 401 unauthorized error in a Redux React application

In response to a client request, the server may return a 401 Unauthorized error. You must correctly catch it, for example, clear the state and redirect to the authorization page. To solve this problem, we wrote a custom Middleware which, in our opinion, is the best solution.


import * as actions from 'actions';



const authInterceptor = ({ dispatch }) => (next) => (action) => {

  if (action.status === 401) {

    dispatch(actions.removeJwt());

  } else {

    next(action);

  }

};

TILApril 22, 2019by Dmitry Sokolov

Integrate Drawer inside Stack Navigation in React Native

I wanted to use more than one navigation in a React Native app.

The documentation didn’t give a clear way to implement this.

Here is what you can do on purpose to add several types of navigation in your React Native app:




import { createStackNavigator, createDrawerNavigator } from 'react-navigation';

import * as Screens from './screens/index';

import Drawer from './components/DrawerMenu';

import getDrawerWidth from './utils/scale';



const AppStackNavigator = createStackNavigator({

  home: {

    screen: Screens.MainScreen,

  },

  about: {

    screen: Screens.AboutScreen,

  },

});



const AppNavigator = createDrawerNavigator(

  {

    home: {

       screen: AppStackNavigator,

    },

  },

  {

    contentComponent: Drawer,

    drawerWidth: getDrawerWidth,

    headerMode: 'none',

  }

);



export default AppNavigator;



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

TILMay 14, 2019by Dmitry Sokolov

JS: Parsing URL

So far, I have used the indexOf operator or regular expressions to get query string values. Today I learned how to make it easier using the URLSearchParams API:




// Assuming "?post=1234&action=edit"



let urlParams = new URLSearchParams(window.location.search);



console.log(urlParams.has('post')); // true

console.log(urlParams.get('action')); // "edit"

console.log(urlParams.getAll('action')); // ["edit"]

console.log(urlParams.toString()); // "?post=1234&action=edit"

console.log(urlParams.append('active', '1')); // "?post=1234&action=edit&active=1"



Important: Does not work in IE

TILMay 01, 2019by Suhomozgy Andrey

Typeahead async requests throttling

As everybody else have some legacy code (who does not have it?), and yesterday we faced with problem.

Our internal app have search panel where users could write and get related results. But search was written with typeahead and without any throttling. So it's triggered full text search on backend side on every input change which leads to DB connection pool exhaustion.

We didn't found any ready to use solutions, so we added following fix:


$input.typeahead(

  {

    ...

  },

  {

        ...

    async: true

    source: (query, syncResults, asyncResults) ->

      href = self.href

      # Added getAsyncResults call to throttle requests:

      getAsyncResults(

        () ->

          $.ajax(

            url: href

            data:

              query: query

            dataType: 'json'

          ).success(

            (data, textStatus, jqXHR) ->

              asyncResults(data)

          )

      )

  })

where getAsyncResults defined before typeahead init:


# throttle is lodash function

getAsyncResults = _.throttle(((fn) ->

    fn()

  ), 300,

  { ... })

Would be happy if someone find this simple solution helpfull.

TILApril 23, 2019by Suhomozgy Andrey

Trick: filter falsy values out of an array?

Lets say you need to filter all falsy


0, undefined, null, false, ''(empty string), NaN

values from array.

Of course you can use following construnction:




myArray

    .map(item => {

        // ...

    })

    // Get rid of falsy values

    .filter(item => item);





Since the values we don't want aren't truthy, the filter above removes those falsy items.

Did you know there's a clearer way with Boolean?




myArray

    .map(item => {

        // ...

    })

    // Get rid of falsy values

    .filter(Boolean);





TILApril 17, 2019by Suhomozgy Andrey

Add conditional properties in JS objects

How conditionaly add properties to object?

As it turned out, it's pretty simple:




const user = { id: 1, name: 'Suhomozgy Andrey' }

const position = 'Frontend Developer'

const userWithPosition = {

  ...user,

  id: 2,

  ...(position && { position })

}



userWithPosition //=> { id: 2, name: 'Suhomozgy Andrey', position: 'Frontend Developer' }





TILMarch 28, 2019by Dmitry Voronov

Create factory with an uploaded file in Rails rspec

To create a factory with an uploaded file in Rails rspec, needs to be included ActionDispatch::TestProcess module in the rails_helper.rb file to use the #fixture_file_upload method in the factories.


# spec/rails_helper.rb

include ActionDispatch::TestProcess

In a factory, when setting the attribute value, use the method #fixture_file_upload, specifying the path and file type.


# spec/factories/import_file.rb

FactoryBot.define do

  factory :import_file, class: ImportFile do

    data { fixture_file_upload 'spec/fixtures/test_file.pdf', 'application/pdf' }

  end

end

TILMarch 20, 2019by Suhomozgy Andrey

Get most performant function

Let's say you need to determine which function is execute faster.

Write a function that take array of functions and iterates each of them a certain number of times.

Will use the difference in performance.now() values before and after to get the total time in milliseconds each iteration running.

Play with iterations argument to get more or less reliable results.


const mostPerformantFunction = (fns, iterations = 10000) => {

  const times = fns.map(fn => {

    const before = performance.now();

    for (let i = 0; i < iterations; i++) fn();

    return performance.now() - before;

  });

  console.log(times);

  return times.indexOf(Math.min(...times));

};


mostPerformantFunction([

  () => {

    [1, 2, 3, 4, 5, 6, 7, 8, 9, '10'].every(el => typeof el === 'number');

  },

  () => {

    [1, '2', 3, 4, 5, 6, 7, 8, 9, 10].every(el => typeof el === 'number');

  }

]); // 1

TILMarch 20, 2019by Alexander Blinov

How to download files with Axios

A simple example using the Blob() constructor:


export function someFunction(values) {

  return (dispatch) => {

    ...

    const method = 'GET';

    const url = 'http://go.api/download_file';

    ...

    axios

      .request({

        url,

        method,

        responseType: 'blob', //important

      })

      .then(({ data }) => {

        const downloadUrl = window.URL.createObjectURL(new Blob([data]));

        const link = document.createElement('a');

        link.href = downloadUrl;

        link.setAttribute('download', 'file.zip'); //any other extension

        document.body.appendChild(link);

        link.click();

        link.remove();

      });

  };

}

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!