Ruby / Rails Tips

Sean Bolton,programming

A collection of ruby / rails tips I’ve picked up over the years

Using Find Each

PaymentOrders.all.find_each(batch_size: 1000) do |po| puts po.id end

This method (opens in a new tab) runs through the collection in batches of a specified batch size (default 1000) which is more efficient than using .each as it does not need to hold all the objects in memory while looping.

Memoized Instance Variables

#counterparty_exporter.rb

def counterparty @counterparty ||= ledger_record.ledgerable end

These are used when the variable value involves a database query. In order to prevent duplicate queries we set an instance variable which then gets called after the initial call. Note: Rubocop requires the variable name to be the same as the method name.

Use .compact to remove hash items with nil values

fruits = { apples: 2, oranges: nil, bananas: 3 }

fruits.compact

{ apples: 2, bananas: 3 }

Use .iso8601 to convert a date to the ISO8601 format YYYY-MM-DD

date = Date.today

date.iso8601

"2020-05-31"

date = Time.now

date.iso8601

"2020-05-31T13:35:53-07:00"

Use find_by instead of where(...).first

Post.find_by name: 'Spartacus', rating: 4

This method (opens in a new tab) finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself. If no record is found, returns nil.

Use insert_all / upsert_all to do bulk db writes

A common pattern in web apps, particularly in refresh jobs is loop over records, make updates and save them. It is easy to fall into the trap of writing to the db once for each record, which leads to some nasty traces. A solution to this is to keep the records in memory and write them all to the db in one go.

upserts = records_from_ledger.map do |record| record.data["name"] = name record end

Accounting::LedgerEntity.upsert_all(upserts.map(&:attributes))

Avoid using .where/.find_by on associations which cause more db reads

def existing_records @existing_records||= Record.where(first_name: "sean") end

existing_records.where(last_name: "bolton")

existing_records.to_a.find { |r| r.last_name == "bolton" }

Create an ActiveStorage Blob locally

file_name = "*OR0063*.txt.20191239092219603"

blob = ActiveStorage::Blob.create_after_upload!(io: File.open(Rails.root.join("tmp", "ach", file_name)), filename: file_name)

Attach a file to an object locally

image = open("https://via.placeholder.com/150 (opens in a new tab)")

organization.email_logo_file.attach(io: image , filename: "foo.jpg")

organization.save!

Use `.max_by` to returns the object in enum that gives the maximum value from the given block.

link to docs (opens in a new tab)

a = %w(albatross dog horse)

a.max_by {|x| x.length } #=> "albatross"

Querying Nested JSONB data

-> returns a json object

->> returns the text value

IncomingPaymentDetail.where("incoming_payment_details.data -> 'batch_header_record' ->> 'standard_entry_class_code'= ?", "CIE")

Use .partition to split an array

ar = %w(a b a a b c d) as, rest = ar.partition { |i| i == "a" }

[7] pry(main)> as [ [0] "a", [1] "a", [2] "a" ]

[8] pry(main)> rest [ [0] "b", [1] "b", [2] "c", [3] "d" ]

.index_by

Link to docs (opens in a new tab)

apples = Apple.all ap apples.index_by(&:id)

{ "id_1": { Apple Object 1 }, "id_2": { Apple Object 2 } }

© Sean Bolton.RSS