Dmitry Voronov

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

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

LongreadsJanuary 29, 2018by Dmitry Voronov

Dry-rb and Trailblazer Reform

Why do we use dry-rb and Trailblazer Reform on the backend?
Read more