Logo

TODAY I LEARNED

11 posts about #ruby

Safe access for nested key values in a Hash - dig method

If you are using Ruby 2.3.0 or above

Now instead of doing this:

result.try(:[], 'avatar').try(:[], 'model').try(:[], 'raw_attributes').try(:[], 'signup_state')
# or
result && result['avatar'] && result['avatar']['model'] && result['avatar']['model']['raw_attributes'] && result['avatar']['model']['raw_attributes']['signup_state']

Now you can easily do the same with dig:

result.dig('avatar', 'model', 'raw_attributes', 'signup_state')

Learned by heriberto-perez on Apr 28, 2021

973983 adforcodereview v3 0211021 c02 021121

use bundle open gemname

You can take a peek at a locally installed gem's code very easily in ruby mine.
If you are using another IDE it would require a few extra steps:
First, set BUNDLER_EDITOR in ~/.bashrc or ~/.zshrc (or whatever shell you are using)
export BUNDLER_EDITOR="code"
(code to use VS code)
Then, simply run $ bundle open gemname and voilá! Look at whatever gem you want

Learned by Eduardo Gutiérrez on Apr 6, 2021

973983 adforcodereview v3 0211021 c02 021121

Ruby 2.7 introduced numbered parameters for blocks

Since Ruby 2.7, it is possible to use numbered parameters in blocks additionally to named parameters.

This is an example of a block with named parameters:

my_array.each { |element| element.do_something }

And this is the same line with numbered parameters:

my_array.each { _1.do_something }

This works with multiple parameters too:

# named parameters
my_hash.each_pair { |key, value| puts "#{key}: #{value}" }

# numered parameters
my_hash.each_pair { puts "#{_1}: #{_2}" }

Learned by kevin-perez on Mar 30, 2021

973983 adforcodereview v3 0211021 c02 021121

Creating an infinite loop in Ruby

If you need to iterate over a collection that its size is unknown, you can create an infinite loops in ruby easily, but if you need an index, then there's this option:

(1..).each do |page|
  results = ExternalService.get('/endpoint', page: page)
  parsed = JSON.parse(response.body)
  break if parsed['data'].empty?

  process_data(parsed['data'])
end

Learned by Edwin Cruz on Mar 16, 2021

973983 adforcodereview v3 0211021 c02 021121

More about randomness

If you want to generate random but predictable sequences of numbers, then the rand command and the srand are not enough, you have to use a trick to save the state of the variable. Fibers are primitives for implementing light weight cooperative concurrency in Ruby. Basically they are a means of creating code blocks that can be paused and resumed, much like threads. The main difference is that they are never preempted and that the scheduling must be done by the programmer and not the VM.

require 'rspec'

#i = pseudo_random 10
#p i.resume => 37
#p i.resume => 12
#p i.resume => 72
#
def pseudo_random num
  srand 1

  fiber = Fiber.new do
    num.times do
      Fiber.yield rand 100
    end
  end
end


describe 'Pseudo Random number generator' do
  it 'creates the same sequence of random numbers' do
    random_sequence = pseudo_random 3
    expect(random_sequence.resume).to eq(37)
    expect(random_sequence.resume).to eq(12)
    expect(random_sequence.resume).to eq(72)
  end
end

Learned by enrique-meza on Mar 10, 2021

973983 adforcodereview v3 0211021 c02 021121

Quasi-Random numbers in Ruby

I was interested in random sequence because I was in need to test the Montecarlo Method for getting Pi digits.

One method to estimate the value of π (3.141592…) is by using a Monte Carlo method. This method consists of drawing on a canvas a square with an inner circle. We then generate a large number of random points within the square and count how many fall in the enclosed circle. Pi

So, if you need a random sequence, you can use Sobol for quasi-random numbers.


require 'gsl'

q = GSL::QRng.alloc(GSL::QRng::SOBOL, 2)
v = GSL::Vector.alloc(2)
for i in 0..1024 do
  q.get(v)
  printf("%.5f %.5f\n", v[0], v[1])
end

Learned by enrique-meza on Mar 10, 2021

973983 adforcodereview v3 0211021 c02 021121

The slice_when method in Ruby

Creates an enumerator for each chunked elements. The beginnings of chunks are defined by the block.

This method splits each chunk using adjacent elements, elt_before, and elt_after, in the receiver enumerator. This method split chunks between elt_before and elt_after where the block returns true.

The block is called the length of the receiver enumerator minus one.

The result enumerator yields the chunked elements as an array. So each method can be called as follows:

enum.slice_when { |elt_before, elt_after| bool }.each { |ary| ... }

For example:

Return adjacent elements from this array [1, 2, 3, 5, 6, 9, 10] in chunked elements as an array.

[1, 2, 3, 5, 6, 9, 10].slice_when {|i, j| i+1 != j }.to_a
=> [[1, 2, 3], [5, 6], [9, 10]]

Learned by Victor Velazquez on Mar 5, 2021

973983 adforcodereview v3 0211021 c02 021121

The each_cons method in Ruby

Iterates the given block for each array of consecutive <n> elements. If no block is given, returns an enumerator.

irb(main):001:0> [1,2,3,4].each_cons(2).to_a
=> [[1, 2], [2, 3], [3, 4]]

irb(main):002:0> [1, 2, 3, 5, 6, 9, 10].each_cons(2).to_a
=> [[1, 2], [2, 3], [3, 5], [5, 6], [6, 9], [9, 10]]

Print any two adjacent words in a given text:

def print_adjacent_words(phrase)
   phrase.split.each_cons(2) do |words|
     puts adjacent_word = words.join(" ")
   end
end
irb(main):025:0> print_adjacent_words("Hello Darkness my old friend")
Hello Darkness
Darkness my
my old
old friend
=> nil

Learned by Victor Velazquez on Mar 5, 2021

973983 adforcodereview v3 0211021 c02 021121

Ruby Double star (**)

def hello(a, *b, **c)
  return a, b, c
end

a is a regular parameter. *b will take all the parameters passed after the first one and put them in an array. **c will take any parameter given in the format key: value at the end of the method call.

See the following examples:

One parameter

hello(1)
# => [1, [], {}]

More than one parameter

hello(1, 2, 3, 4)
# => [1, [2, 3, 4], {}]

More than one parameter + hash-style parameters

hello(1, 2, 3, 4, a: 1, b: 2)
# => [1, [2, 3, 4], {:a=>1, :b=>2}]

Learned by Victor Velazquez on Mar 4, 2021

973983 adforcodereview v3 0211021 c02 021121

Measuring memory utilization

Ruby comes with the cool module Benchmark, but the only downside is that it only provides the time taken for each task.

A simple approach to get how many memory the ruby process is consuming is to call: ps -o rss:

memory_before = `ps -o rss= -p #{$$}`.to_i
Utils.heavy_memory_consumming_process
memory_after = `ps -o rss= -p #{$$}`.to_i

puts "The process took #{memory_after - memory_before} KB"

Learned by Edwin Cruz on Mar 1, 2021

973983 adforcodereview v3 0211021 c02 021121

Connecting Ruby to AWS IoT Core using MQTT client

If you need to use Ruby to connect to Aws Iot Core, this is all you need:

require 'aws-sdk-iot'
require 'aws-sdk-secretsmanager'
require 'json'
require 'mqtt'

secrets_manager = Aws::SecretsManager::Client.new(
    region: ENV["IOT_AWS_REGION"],
    access_key_id: ENV["IOT_AWS_ACCESS_KEY"],
    secret_access_key: ENV["IOT_AWS_SECRET_ACCESS_KEY"]
    )

client = Aws::IoT::Client.new(
    region: ENV["IOT_AWS_REGION"],
    access_key_id: ENV["IOT_AWS_ACCESS_KEY"],
    secret_access_key: ENV["IOT_AWS_SECRET_ACCESS_KEY"]
    )

# Creates new ssl certificate
cert = client.create_keys_and_certificate(set_as_active: true)

# A policy named iot-mqtt needs to exist with permissions to publish and read
# any topic names
client.attach_policy(policy_name: "iot-mqtt", target: cert.certificate_arn)

# Stores the certificate in aws secrets manager
secrets_manager.create_secret(name: "iot_cert_pem", secret_string: cert.certificate_pem)
secrets_manager.create_secret(name: "iot_private_key", secret_string: cert.key_pair.private_key)

# Reads the certificate from aws secrets manager
cert_pem = secrets_manager.get_secret_value(secret_id: "iot_cert_pem").secret_string
private_key = secrets_manager.get_secret_value(secret_id: "iot_private_key").secret_string

# Connects to aws iot core endpoint using mqtts
mqtt_client = MQTT::Client.new(ENV["IOT_AWS_ENDPOINT"])
mqtt_client.cert = cert_pem
mqtt_client.key = private_key
mqtt_client.connect(MQTT::Client.generate_client_id("my-awesome-app-"))

# Publishes a message
message = { desired: { speed_limit: 35 } }
mqtt_client.publish("$aws/things/sensor_home/shadow/update", { state: message }.to_json)

# Listens to all accepted shadow updates
mqtt_client.get("$aws/things/+/shadow/+/accepted") do |topic, message|
    payload = JSON.decode(message)
    puts "Got #{topic}"
    puts "With #{payload}"
end

Learned by Edwin Cruz on Feb 11, 2021

973983 adforcodereview v3 0211021 c02 021121