Latest from the Rocketship

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

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.

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

TILJanuary 12, 2019by Dmitry Voronov

Manage Elixir versions like RVM & Rbenv

You can install different versions of the Elixir with help of Kiex, like in Ruby with a RVM and Rbenv.

Download and install Kiex


\curl -sSL https://raw.githubusercontent.com/taylor/kiex/master/install | bash -s

```bash



In .bashrc (or .zshrc if you use z shell), add the following

```bash

[[ -s "$HOME/.kiex/scripts/kiex" ]] && source "$HOME/.kiex/scripts/kiex"

```bash



Install required Elixir 

```bash

kiex install 1.7 # or another version

```bash



And then you can use any version

```bash

kiex use 1.7

```bash
TILJanuary 07, 2019by Dmitry Voronov

How to create zip files on the fly w/o Tempfile

There are many articles about how to archive files from the server and send a zip-file to a client without persisting it on the server. But usually they don't literally do it, because they use temporary files.

There is a simple way to do it without creating any file though. You just have to put files directly to Zip::OutputStream and then read from it. Btw pay attention: you must rewind the stream before reading it.


# some files objects

def download(files)

  zip_stream = Zip::OutputStream.write_buffer do |zip|

    files.each.with_index(1) do |file, index|

     # file name must be uniq in archive

      zip.put_next_entry("#{file.name}--#{index}.#{file.extension}")

      zip.write(file.read.force_encoding('utf-8'))

    end

  end

  # important - rewind the steam

  zip_stream.rewind

  send_data zip_stream.read, 

            type: 'application/zip', 

            disposition: 'attachment', 

            filename: 'files-archive.zip'

end

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.