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.

7 posts by Dmitry Voronov @dmdropkick

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

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

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 10_000_000 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.

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

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

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

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

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

Install required Elixir

kiex install 1.7 # or another version

And then you can use any version

kiex use 1.7

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