San Francisco, USA

5214F Diamond Heights Blvd #553
San Francisco, CA 94131

Pune, India

203, Jewel Towers, 2nd Floor
Lane Number 5, Koregaon Park
Pune 411001, India

301 - 275 - 3997
hello@BigBinary.com

Ruby 2.5 adds Exception#full_message method

This blog is part of our Ruby 2.5 series.

Before Ruby 2.5, if we want to log a caught exception, we would need to format it ourselves.

class AverageService
  attr_reader :numbers, :coerced_numbers

  def initialize(numbers)
    @numbers = numbers
    @coerced_numbers = coerce_numbers
  end

  def average
    sum / count
  end

  private

  def coerce_numbers
    numbers.map do |number|
      begin
        Float(number)
      rescue Exception => exception
        puts "#{exception.message} (#{exception.class})\n\t#{exception.backtrace.join("\n\t")}"
        puts "Coercing '#{number}' as 0.0\n\n"

        0.0
      end
    end
  end

  def sum
    coerced_numbers.map(&:to_f).sum
  end

  def count
    coerced_numbers.size.to_f
  end
end

average = AverageService.new(ARGV).average
puts "Average is: #{average}"
$ RBENV_VERSION=2.4.0 ruby average_service.rb 5 4f 7 1s0
invalid value for Float(): "4f" (ArgumentError)
	average_service.rb:18:in `Float'
	average_service.rb:18:in `block in coerce_numbers'
	average_service.rb:16:in `map'
	average_service.rb:16:in `coerce_numbers'
	average_service.rb:6:in `initialize'
	average_service.rb:37:in `new'
	average_service.rb:37:in `<main>'

Coercing '4f' as 0.0

invalid value for Float(): "1s0" (ArgumentError)
	average_service.rb:18:in `Float'
	average_service.rb:18:in `block in coerce_numbers'
	average_service.rb:16:in `map'
	average_service.rb:16:in `coerce_numbers'
	average_service.rb:6:in `initialize'
	average_service.rb:37:in `new'
	average_service.rb:37:in `<main>'

Coercing '1s0' as 0.0

Average of [5.0, 0.0, 7.0, 0.0] is: 3.0

It was proposed that there should be a simple method to print the caught exception using the same format that ruby uses while printing an uncaught exception.

Some of the proposed method names were display, formatted, to_formatted_s, long_message, and full_message.

Matz approved the Exception#full_message method name.

In Ruby 2.5, we can re-write above example as follows.

class AverageService
  attr_reader :numbers, :coerced_numbers

  def initialize(numbers)
    @numbers = numbers
    @coerced_numbers = coerce_numbers
  end

  def average
    sum / count
  end

  private

  def coerce_numbers
    numbers.map do |number|
      begin
        Float(number)
      rescue Exception => exception
        puts exception.full_message
        puts "Coercing '#{number}' as 0.0\n\n"

        0.0
      end
    end
  end

  def sum
    coerced_numbers.map(&:to_f).sum
  end

  def count
    coerced_numbers.size.to_f
  end
end

average = AverageService.new(ARGV).average
puts "Average is: #{average}"
$ RBENV_VERSION=2.5.0 ruby average_service.rb 5 4f 7 1s0
Traceback (most recent call last):
	6: from average_service.rb:37:in `<main>'
	5: from average_service.rb:37:in `new'
	4: from average_service.rb:6:in `initialize'
	3: from average_service.rb:16:in `coerce_numbers'
	2: from average_service.rb:16:in `map'
	1: from average_service.rb:18:in `block in coerce_numbers'
average_service.rb:18:in `Float': invalid value for Float(): "4f" (ArgumentError)

Coercing '4f' as 0.0

Traceback (most recent call last):
	6: from average_service.rb:37:in `<main>'
	5: from average_service.rb:37:in `new'
	4: from average_service.rb:6:in `initialize'
	3: from average_service.rb:16:in `coerce_numbers'
	2: from average_service.rb:16:in `map'
	1: from average_service.rb:18:in `block in coerce_numbers'
average_service.rb:18:in `Float': invalid value for Float(): "1s0" (ArgumentError)

Coercing '1s0' as 0.0

Average of [5.0, 0.0, 7.0, 0.0] is: 3.0

Note that, Ruby 2.5 prints exception backtrace in reverse order if STDERR is unchanged and is a TTY as discussed in our previous blog post.


Ruby 2.5 prints backtrace and error message in reverse order

This blog is part of our Ruby 2.5 series.

Stack trace or backtrace is a sequential representation of the stack of method calls in a program which gets printed when an exception is raised. It is often used to find out the exact location in a program from where the exception was raised.

Before Ruby 2.5

Before Ruby 2.5, the printed backtrace contained the exception class and the error message at the top. Next line contained where in the program the exception was raised. Next we got more lines which contained cascaded method calls.

Consider a simple Ruby program.

class DivisionService
  attr_reader :a, :b

  def initialize(a, b)
    @a, @b = a.to_i, b.to_i
  end

  def divide
    puts a / b
  end
end

DivisionService.new(ARGV[0], ARGV[1]).divide

Let’s execute it using Ruby 2.4.

$ RBENV_VERSION=2.4.0 ruby division_service.rb 5 0

division_service.rb:9:in `/': divided by 0 (ZeroDivisionError)
	from division_service.rb:9:in `divide'
	from division_service.rb:13:in `<main>'

In the printed backtrace above, the first line shows the location, error message and the exception class name; whereas the subsequent lines shows the caller method names and their locations. Each line in the backtrace above is often considered as a stack frame placed on the call stack.

Most of the times, a backtrace has so many lines that it makes it very difficult to fit the whole backtrace in the visible viewport of the terminal.

Since the backtrace is printed in top to bottom order the meaningful information like error message, exception class and the exact location where the exception was raised is displayed at top of the backtrace. It means developers often need to scroll to the top in the terminal window to find out what went wrong.

After Ruby 2.5

Over 4 years ago an issue was created to make printing of backtrace in reverse order configurable.

After much discussion Nobuyoshi Nakada made the commit to print backtrace and error message in reverse order only when the error output device (STDERR) is a TTY (i.e. a terminal). Message will not be printed in reverse order if the original STDERR is attached to something like a File object.

Look at the code here where the check happens if STDERR is a TTY and is unchanged.

Let’s execute the same program using Ruby 2.5.

$ RBENV_VERSION=2.5.0 ruby division_service.rb 5 0

Traceback (most recent call last):
	2: from division_service.rb:13:in `<main>'
	1: from division_service.rb:9:in `divide'
division_service.rb:9:in `/': divided by 0 (ZeroDivisionError)

$

We can notice two new changes in the above backtrace.

  1. The error message and exception class is printed last (i.e. at the bottom).
  2. The stack also adds frame number when printing in reverse order.

This feature makes the debugging convenient when the backtrace size is a quite big and cannot fit in the terminal window. We can easily see the error message without scrolling up now.

Note that, the Exception#backtrace attribute still holds an array of stack frames like before in the top to bottom order.

So if we rescue the caught exception and print the backtrace manually

class DivisionService
  attr_reader :a, :b

  def initialize(a, b)
    @a, @b = a.to_i, b.to_i
  end

  def divide
    puts a / b
  end
end

begin
  DivisionService.new(ARGV[0], ARGV[1]).divide
rescue Exception => e
  puts "#{e.class}: #{e.message}"
  puts e.backtrace.join("\n")
end

we will get the old behavior.

$ RBENV_VERSION=2.5.0 ruby division_service.rb 5 0

ZeroDivisionError: divided by 0
division_service.rb:9:in `/'
division_service.rb:9:in `divide'
division_service.rb:16:in `<main>'

$

Also, note that if we assign STDERR with a File object, thus making it a non-TTY

puts "STDERR is a TTY? [before]: #{$stderr.tty?}"
$stderr = File.new("stderr.log", "w")
$stderr.sync = true
puts "STDERR is a TTY? [after]: #{$stderr.tty?}"

class DivisionService
  attr_reader :a, :b

  def initialize(a, b)
    @a, @b = a.to_i, b.to_i
  end

  def divide
    puts a / b
  end
end

DivisionService.new(ARGV[0], ARGV[1]).divide

we can get the old behavior but the backtrace would be written to the specified file and not to STDERR.

$ RBENV_VERSION=2.5.0 ruby division_service.rb 5 0

STDERR is a TTY? [before]: true
STDERR is a TTY? [after]: false

$ cat stderr.log

division_service.rb:14:in `/': divided by 0 (ZeroDivisionError)
	from division_service.rb:14:in `divide'
	from division_service.rb:18:in `<main>'

$

This feature has been tagged as experimental feature. What it means is that Ruby team is gathering feedback on this feature.


Deploying Ruby on Rails application using HAProxy Ingress with unicorn/puma and websockets

After months of testing we recently moved a Ruby on Rails application to production that is using Kubernetes cluster.

In this article we will discuss how to setup path based routing for a Ruby on Rails application in kubernetes using HAProxy ingress.

This post assumes that you have basic understanding of Kubernetes terms like pods, deployments, services, configmap and ingress.

Typically our Rails app has services like unicorn/puma, sidekiq/delayed-job/resque, Websockets and some dedicated API services. We had one web service exposed to the world using load balancer and it was working well. But as the traffic increased it became necessary to route traffic based on URLs/path.

However Kubernetes does not supports this type of load balancing out of the box. There is work in progress for alb-ingress-controller to support this but we could not rely on it for production usage as it is still in alpha.

The best way to achieve path based routing was to use ingress controller.

We researched and found that there are different types of ingress available in k8s world.

  1. nginx-ingress
  2. ingress-gce
  3. HAProxy-ingress
  4. traefik
  5. voyager

We experimented with nginx-ingress and HAProxy and decided to go with HAProxy. HAProxy has better support for Rails websockets which we needed in the project.

We will walk you through step by step on how to use haproxy ingress in a Rails app.

Configuring Rails app with HAProxy ingress controller

Here is what we are going to do.

  • Create a Rails app with different services and deployments.
  • Create tls secret for SSL.
  • Create HAProxy ingress configmap.
  • Create HAProxy ingress controller.
  • Expose ingress with service type LoadBalancer
  • Setup app DNS with ingress service.
  • Create different ingress rules specifying path based routing.
  • Test the path based routing.

Now let’s build Rails application deployment manifest for services like web(unicorn),background(sidekiq), Websocket(ruby thin),API(dedicated unicorn).

Here is our web app deployment and service template.

---
apiVersion: v1
kind: Deployment
metadata:
  name: test-production-web
  labels:
    app: test-production-web
  namespace: test
spec:
  template:
    metadata:
      labels:
        app: test-production-web
    spec:
      containers:
      - image: <your-repo>/<your-image-name>:latest
        name: test-production
        imagePullPolicy: Always
       env:
        - name: POSTGRES_HOST
          value: test-production-postgres
        - name: REDIS_HOST
          value: test-production-redis
        - name: APP_ENV
          value: production
        - name: APP_TYPE
          value: web
        - name: CLIENT
          value: test
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: registrykey
---
apiVersion: v1
kind: Service
metadata:
  name: test-production-web
  labels:
    app: test-production-web
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test-production-web

Here is background app deployment and service template.

---
apiVersion: v1
kind: Deployment
metadata:
  name: test-production-background
  labels:
    app: test-production-background
  namespace: test
spec:
  template:
    metadata:
      labels:
        app: test-production-background
    spec:
      containers:
      - image: <your-repo>/<your-image-name>:latest
        name: test-production
        imagePullPolicy: Always
       env:
        - name: POSTGRES_HOST
          value: test-production-postgres
        - name: REDIS_HOST
          value: test-production-redis
        - name: APP_ENV
          value: production
        - name: APP_TYPE
          value: background
        - name: CLIENT
          value: test
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: registrykey
---
apiVersion: v1
kind: Service
metadata:
  name: test-production-background
  labels:
    app: test-production-background
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test-production-background

Here is websocket app deployment and service template.

---
apiVersion: v1
kind: Deployment
metadata:
  name: test-production-websocket
  labels:
    app: test-production-websocket
  namespace: test
spec:
  template:
    metadata:
      labels:
        app: test-production-websocket
    spec:
      containers:
      - image: <your-repo>/<your-image-name>:latest
        name: test-production
        imagePullPolicy: Always
       env:
        - name: POSTGRES_HOST
          value: test-production-postgres
        - name: REDIS_HOST
          value: test-production-redis
        - name: APP_ENV
          value: production
        - name: APP_TYPE
          value: websocket
        - name: CLIENT
          value: test
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: registrykey
---
apiVersion: v1
kind: Service
metadata:
  name: test-production-websocket
  labels:
    app: test-production-websocket
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test-production-websocket

Here is API app deployment and service info.

---
apiVersion: v1
kind: Deployment
metadata:
  name: test-production-api
  labels:
    app: test-production-api
  namespace: test
spec:
  template:
    metadata:
      labels:
        app: test-production-api
    spec:
      containers:
      - image: <your-repo>/<your-image-name>:latest
        name: test-production
        imagePullPolicy: Always
       env:
        - name: POSTGRES_HOST
          value: test-production-postgres
        - name: REDIS_HOST
          value: test-production-redis
        - name: APP_ENV
          value: production
        - name: APP_TYPE
          value: api
        - name: CLIENT
          value: test
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: registrykey
---
apiVersion: v1
kind: Service
metadata:
  name: test-production-api
  labels:
    app: test-production-api
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test-production-api

Let’s launch this manifest using kubectl apply.

$ kubectl apply -f test-web.yml -f test-background.yml -f test-websocket.yml -f test-api.yml
deployment "test-production-web" created
service "test-production-web" created
deployment "test-production-background" created
service "test-production-background" created
deployment "test-production-websocket" created
service "test-production-websocket" created
deployment "test-production-api" created
service "test-production-api" created

Once our app is deployed and running we should create HAProxy ingress. Before that let’s create a tls secret with our SSL key and certificate.

This is also used to enable HTTPS for app URL and to terminate it on L7.

$ kubectl create secret tls tls-certificate --key server.key --cert server.pem

Here server.key is our SSL key and server.pem is our SSL certificate in pem format.

Now let’s Create HAProxy controller resources.

HAProxy configmap

For all the available configuration parameters from HAProxy refer here.

apiVersion: v1
data:
    dynamic-scaling: "true"
    backend-server-slots-increment: "4"
kind: ConfigMap
metadata:
  name: haproxy-configmap
  namespace: test

HAProxy Ingress controller deployment

Deployment template for the Ingress controller with at-least 2 replicas to manage rolling deploys.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: haproxy-ingress
  name: haproxy-ingress
  namespace: test
spec:
  replicas: 2
  selector:
    matchLabels:
      run: haproxy-ingress
  template:
    metadata:
      labels:
        run: haproxy-ingress
    spec:
      containers:
      - name: haproxy-ingress
        image: quay.io/jcmoraisjr/haproxy-ingress:v0.5-beta.1
        args:
        - --default-backend-service=$(POD_NAMESPACE)/test-production-web
        - --default-ssl-certificate=$(POD_NAMESPACE)/tls-certificate
        - --configmap=$(POD_NAMESPACE)/haproxy-configmap
        - --ingress-class=haproxy
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
        - name: stat
          containerPort: 1936
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace

Notable fields in above manifest are arguments passed to controller.

--default-backend-service is the service when No rule is matched your request will be served by this app.

In our case it is test-production-web service, But it can be custom 404 page or whatever better you think.

--default-ssl-certificate is the SSL secret we just created above this will terminate SSL on L7 and our app is served on HTTPS to outside world.

HAProxy Ingress service

This is the LoadBalancer type service to allow client traffic to reach our Ingress Controller.

LoadBalancer has access to both public network and internal Kubernetes network while retaining the L7 routing of the Ingress Controller.

apiVersion: v1
kind: Service
metadata:
  labels:
    run: haproxy-ingress
  name: haproxy-ingress
  namespace: test
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  - name: https
    port: 443
    protocol: TCP
    targetPort: 443
  - name: stat
    port: 1936
    protocol: TCP
    targetPort: 1936
  selector:
    run: haproxy-ingress

Now let’s apply all the manifests of HAProxy.

$ kubectl apply -f haproxy-configmap.yml -f haproxy-deployment.yml -f haproxy-service.yml
configmap "haproxy-configmap" created
deployment "haproxy-ingress" created
service "haproxy-ingress" created

Once all the resources are running get the LoadBalancer endpoint using.

$ kubectl -n test get svc haproxy-ingress -o wide

NAME               TYPE           CLUSTER-IP       EXTERNAL-IP                                                            PORT(S)                                     AGE       SELECTOR
haproxy-ingress   LoadBalancer   100.67.194.186   a694abcdefghi11e8bc3b0af2eb5c5d8-806901662.us-east-1.elb.amazonaws.com   80:31788/TCP,443:32274/TCP,1936:32157/TCP   2m        run=ingress

DNS mapping with application URL

Once we have ELB endpoint of ingress service, map the DNS with URL like test-rails-app.com.

Ingress Implementation

Now after doing all the hard work it is time to configure ingress and path based rules.

In our case we want to have following rules.

https://test-rails-app.com requests to be served by test-production-web.

https://test-rails-app.com/websocket requests to be served by test-production-websocket.

https://test-rails-app.com/api requests to be served by test-production-api.

Let’s create a ingress manifest defining all the rules.

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  namespace: test
spec:
  tls:
    - hosts:
      - test-rails-app.com
      secretName: tls-certificate
  rules:
    - host: test-rails-app.com
      http:
        paths:
          - path: /
            backend:
              serviceName: test-production-web
              servicePort: 80
          - path: /api
            backend:
              serviceName: test-production-api
              servicePort: 80
          - path: /websocket
            backend:
              serviceName: test-production-websocket
              servicePort: 80

Moreover there are Ingress Annotations for adjusting configuration changes.

As expected,now our default traffic on / is routed to test-production-web service.

/api is routed to test-production-api service.

/websocket is routed to test-production-websocket service.

Thus ingress implementation solves our purpose of path based routing and terminating SSL on L7 on Kubernetes.


Rails 5.2 adds default option to module and class attribute accessors

This blog is part of our Rails 5.2 series.

When DHH introduced support for specifying a default value for class_attribute, Genadi Samokovarov brought to notice that the module and class attribute accessor macros also support specifying a default value but using a block and not with a default option.

To have consistent and symmetrical behaviour across all the attribute extensions, it was decided to support specifying a default value using default option for all the module and class attribute macros as well.

mattr_accessor, mattr_reader and mattr_writer macros generate getter and setter methods at the module level.

Similarly, cattr_accessor, cattr_reader, and cattr_writer macros generate getter and setter methods at the class level.

Before Rails 5.2

Before Rails 5.2, this is how we would set the default values for the module and class attribute accessor macros.

module ActivityLoggerHelper
  mattr_accessor :colorize_logs
  mattr_writer :log_ip { false }

  self.colorize_logs = true
end

class ActivityLogger
  include ActivityLoggerHelper

  cattr_writer :logger { Logger.new(STDOUT) }
  cattr_accessor :level
  cattr_accessor :settings
  cattr_reader :pid { Process.pid }

  @@level = Logger::DEBUG
  self.settings = {}
end

After Rails 5.2

We can still set a default value of a module or class attribute accessor by providing a block. In this pull request, support for specifying a default value using a new default option has been introduced.

So instead of

cattr_writer :logger { Logger.new(STDOUT) }

or

cattr_writer :logger
self.logger = Logger.new(STDOUT)

or

cattr_writer :logger
@@logger = Logger.new(STDOUT)

we can now easily write

cattr_writer :logger, default: Logger.new(STDOUT)

Same applies to the other attribute accessor macros like mattr_accessor, mattr_reader, mattr_writer, cattr_accessor, and cattr_reader.

Note that, the old way of specifying a default value using the block syntax will work but will not be documented anywhere.

Also, note that if we try to set the default value by both ways i.e. by providing a block as well as by specifying a default option; the value provided by default option will always take the precedence.

mattr_accessor(:colorize_logs, default: true) { false }

Here, @@colorize_logs would be set with true as per the above precedence rule.

Here is a test which verifies this behavior.

Finally, here is simplified version using the new default option.

module ActivityLoggerHelper
  mattr_accessor :colorize_logs, default: true
  mattr_writer :log_ip, default: false
end

class ActivityLogger
  include ActivityLoggerHelper

  cattr_writer :logger, default: Logger.new(STDOUT)
  cattr_accessor :level, default: Logger::DEBUG
  cattr_accessor :settings, default: {}
  cattr_reader :pid, default: Process.pid
end

Rails 5.2 supports specifying default value for a class_attribute

This blog is part of our Rails 5.2 series.

It is very common to set a default value for a class_attribute.

Before Rails 5.2, to specify a default value for a class_attribute, we needed to write like this.

class ActivityLogger
  class_attribute :logger
  class_attribute :settings

  self.logger = Logger.new(STDOUT)
  self.settings = {}
end

As we can see above, it requires additional keystrokes to set a default value for each class_attribute.

Rails 5.2 has added support for specifying a default value for a class_attribute using default option.

class ActivityLogger
  class_attribute :logger, default: Logger.new(STDOUT)
  class_attribute :settings, default: {}
end

This enhancement was introduced in this pull request.


Ruby 2.5 added Hash#slice method

This blog is part of our Ruby 2.5 series.

Ruby 2.4

Let’s say that we have a hash { id: 1, name: 'Ruby 2.5', description: 'BigBinary Blog' } and we want to select key value pairs having keys name and description.

We can use Hash#select method.

irb> blog = { id: 1, name: 'Ruby 2.5', description: 'BigBinary Blog' }
  => {:id=>1, :name=>"Ruby 2.5", :description=>"BigBinary Blog"}

irb> blog.select { |key, value| [:name, :description].include?(key) }
  => {:name=>"Ruby 2.5", :description=>"BigBinary Blog"}

Matzbara Masanao proposed a simple method to take care of this.

Some of the names proposed were choice and pick.

Matz suggested the name slice since this method is ActiveSupport compatible.

Ruby 2.5.0

irb> blog = { id: 1, name: 'Ruby 2.5', description: 'BigBinary Blog' }
  => {:id=>1, :name=>"Ruby 2.5", :description=>"BigBinary Blog"}

irb> blog.slice(:name, :description)
  => {:name=>"Ruby 2.5", :description=>"BigBinary Blog"}

As we can see, now we can use a simple method slice to select key value pairs from a hash with specified keys.

Here is relevant commit and discussion.


Ruby 2.5 allows creating structs with keyword arguments

This blog is part of our Ruby 2.5 series.

In Ruby, structs can be created using positional arguments.

Customer = Struct.new(:name, :email)
Customer.new("John", "john@example.com")

This approach works when the arguments list is short. When arguments list increases then it gets harder to track which position maps to which value.

Here if we pass keyword argument then we won’t get any error. But the values are not what we wanted.

Customer.new(name: "John", email: "john@example.com")
=> #<struct Customer name={:name=>"John", :email=>"john@example.com"}, email=nil>

Ruby 2.5 introduced creating structs using keyword arguments. Relevant pull request is here.

However this introduces a problem. How do we indicate to Struct if we want to pass arguments using position or keywords.

Takashi Kokubun suggested to use keyword_argument as an identifier.

Customer = Struct.new(:name, :email, keyword_argument: true)
Customer.create(name: "John", email: "john@example.com")

Matz suggested to change the name to keyword_init.

So in Ruby 2.5 we can create structs using keywords as long as we are passing keyword_init.

Customer = Struct.new(:name, :email, keyword_init: true)

Customer.new(name: "John", email: "john@example.com")
=> #<struct Customer name="John", email="john@example.com">

Rails 5.2 allows mailers to use custom Active Job class

This blog is part of our Rails 5.2 series.

Rails allows sending emails asynchronously via Active Job.

Notifier.welcome(User.first).deliver_later

It uses ActionMailer::DeliveryJob as the default job class to send emails. This class is defined internally by Rails.

The DeliveryJob defines handle_exception_with_mailer_class method to handle exception and to do some housekeeping work.

def handle_exception_with_mailer_class(exception)
  if klass = mailer_class
    klass.handle_exception exception
  else
    raise exception
  end
end

One might need more control on the job class to retry the job under certain conditions or add more logging around exceptions.

Before Rails 5.2, it was not possible to use a custom job class for this purpose.

Rails 5.2 has added a feature to configure the job class per mailer.

class CustomNotifier < ApplicationMailer
  self.delivery_job = CustomNotifierDeliveryJob
end

By default, Rails will use the internal DeliveryJob class if the delivery_job configuration is not present in the mailer class.

Now, Rails will use CustomNotifierDeliveryJob for sending emails for CustomNotifier mailer.

CustomNotifier.welcome(User.first).deliver_later

As mentioned above CustomNotifierDeliveryJob can be further configured for logging, exception handling and reporting.

By default, deliver_later will pass following arguments to the perform method of the CustomNotifierDeliveryJob.

  • mailer class name
  • mailer method name
  • mail delivery method
  • original arguments with which the mail is to be sent
class CustomNotifierDeliveryJob < ApplicationJob

  rescue_from StandardError, with: :handle_exception_with_mailer_class

  retry_on CustomNotifierException

  discard_on ActiveJob::DeserializationError

  def perform(mailer, mail_method, delivery_method, *args)
    logger.log "Mail delivery started"
    klass = mailer.constantize
    klass.public_send(mail_method, *args).send(delivery_method)
    logger.log "Mail delivery completed"
  end

  def handle_exception_with_mailer_class(exception)
    if klass = mailer_class
      klass.handle_exception exception
    else
      raise exception
    end
  end
end

We can also simply inherit from the ActionMailer::DeliveryJob and override the retry logic.

class CustomNotifierDeliveryJob < ActionMailer::DeliveryJob
  retry_on CustomNotifierException
end

Rails 5.2 supports descending indexes for MySQL

This blog is part of our Rails 5.2 series.

An index is used to speed up the performance of queries on a database.

Rails allows us to create index on a database column by means of a migration. By default, the sort order for the index is ascending.

But consider the case where we are fetching reports from the database. And while querying the database, we always want to get the latest report. In this case, it is efficient to specify the sort order for the index to be descending.

We can specify the sort order by adding an index to the required column by adding a migration .

add_index :reports, [:user_id, :name], order: { user_id: :asc, name: :desc }

PostgreSQL

If our Rails application is using postgres database, after running the above migration we can verify that the sort order was added in schema.rb

create_table "reports", force: :cascade do |t|
  t.string "name"
  t.integer "user_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["user_id", "name"], name: "index_reports_on_user_id_and_name", order: { name: :desc }
end

Here, the index for name has sort order in descending. Since the default is ascending, the sort order for user_id is not specified in schema.rb.

MySQL < 8.0.1

For MySQL < 8.0.1, running the above migration, would generate the following schema.rb

create_table "reports", force: :cascade do |t|
  t.string "name"
  t.integer "user_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["user_id", "name"], name: "index_reports_on_user_id_and_name"
end

As we can see, although the migration runs successfully, it ignores the sort order and the default ascending order is added.

Rails 5.2 and MySQL > 8.0.1

MySQL 8.0.1 added support for descending indices.

Rails community was quick to integrate it as well. So now in Rails 5.2, we can add descending indexes for MySQL databases.

Running the above migration would lead to the same output in schema.rb file as that of the postgres one.

create_table "reports", force: :cascade do |t|
  t.string "name"
  t.integer "user_id"
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["user_id", "name"], name: "index_reports_on_user_id_and_name", order: { name: :desc }
end

Ruby 2.5 adds Hash#transform_keys method

This blog is part of our Ruby 2.5 series.

Ruby 2.4 added Hash#transform_values method to transform values of the hash.

In Ruby 2.5, a similar method Hash#transform_keys is added for transforming keys of the hash.

>> h = { name: "John", email: "john@example.com" }
=> {:name=>"John", :email=>"john@example.com"}

>> h.transform_keys { |k| k.to_s }
=> {"name"=>"John", "email"=>"john@example.com"}

The bang sibling of this method, Hash#transform_keys! is also added which changes the hash in place.

These two methods are already present in Active Support from Rails and are natively supported in Ruby now.

Rails master is already supporting using the native methods if supported by the Ruby version.


Rails 5.2 allows passing request params to Action Mailer previews

This blog is part of our Rails 5.2 series.

Rails has inbuilt feature to preview the emails using Action Mailer previews.

A preview mailer can be setup as shown here.

class NotificationMailer < ApplicationMailer
  def notify(email: email, body: body)
    user = User.find_by(email: email)
    mail(to: user.email, body: body)
  end
end

class NotificationMailerPreview < ActionMailer::Preview
  def notify
    NotificationMailer.notify(email: User.first.email, body: "Hi there!")
  end
end

This will work as expected. But our email template is displayed differently based on user’s role. To test this, we need to update the notify method and then check the updated preview.

What if we could just pass the email in the preview URL.

http://localhost:3000/rails/mailers/notification/notify?email=superadmin@example.com

In Rails 5.2, we can pass the params directly in the URL and params will be available to the preview mailers.

Our code can be changed as follows to use the params.

class NotificationMailerPreview < ActionMailer::Preview
  def notify
    email =  params[:email] || User.first.email
    NotificationMailer.notify(email: email, body: "Hi there!")
  end
end

This allows us to test our mailers with dynamic input as per requirements.


Ruby 2.5 enumerable predicates accept pattern argument

This blog is part of our Ruby 2.5 series.

Ruby 2.5.0 was recently released.

Ruby has sequence predicates such as all?, none?, one? and any? which take a block and evaluate that by passing every element of the sequence to it.

if queries.any? { |sql| /LEFT OUTER JOIN/i =~ sql }
  logger.log "Left outer join detected"
end

Ruby 2.5 allows using a shorthand for this by passing a pattern argument. Internally case equality operator(===) is used against every element of the sequence and the pattern argument.

if queries.any?(/LEFT OUTER JOIN/i)
  logger.log "Left outer join detected"
end

# Translates to:

queries.any? { |sql| /LEFT OUTER JOIN/i === sql }

This allows us to write concise and shorthand expressions where block is only used for comparisons. This feature is applicable to all?, none?, one? and any? methods.

Similarities with Enumerable#grep

This feature is based on how Enumerable#grep works. grep returns an array of every element in the sequence for which the case equality operator(===) returns true by applying the pattern. In this case, the all? and friends return true or false.

There is a proposal to add it for select and reject as well.


Rails 5.2 adds bootsnap to the app to speed up boot time

This blog is part of our Rails 5.2 series.

Rails 5.2 beta 1 was recently released.

If we generate a new Rails app using Rails 5.2, we will see bootsnap gem in the Gemfile. bootsnap helps in reducing the boot time of the app by caching expensive computations.

In a new Rails 5.2 app, boot.rb will contain following content:

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)

require 'bundler/setup' # Set up gems listed in the Gemfile.
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.

if %w[s server c console].any? { |a| ARGV.include?(a) }
  puts "=> Booting Rails"
end

This sets up bootsnap to start in all environments. We can toggle it per environment as required.

This works out of the box and we don’t have do to anything for the new app.

If we are upgrading an older app which already has bootsnap, then we need to make sure that we are using bootsnap >= 1.1.0 because new Rails apps ship with that version constraint.

If the app doesn’t contain the bootsnap gem already then we will need to add it manually since rails app:update task adds the bootsnap/setup line to boot.rb regardless of its presence in the Gemfile.


Ruby 2.5 requires pp by default

This blog is part of our Ruby 2.5 series.

Ruby 2.5.0-preview1 was recently released.

Ruby allows pretty printing of objects using pp method.

Before Ruby 2.5, we had to require PP explicitly before using it. Even the official documentation states that “All examples assume you have loaded the PP class with require ‘pp’”.

>> months = %w(January February March)
=> ["January", "February", "March"]

>> pp months
NoMethodError: undefined method `pp' for main:Object
Did you mean?  p
	from (irb):5
	from /Users/prathamesh/.rbenv/versions/2.4.1/bin/irb:11:

>> require 'pp'
=> true

>> pp months
["January",
 "February",
 "March"]
=> ["January", "February", "March"]

In Ruby 2.5, we don’t need to require pp. It gets required by default. We can use it directly.

>> months = %w(January February March)
=> ["January", "February", "March"]

>> pp months
["January",
 "February",
 "March"]
=> ["January", "February", "March"]

This feature was added after Ruby 2.5.0 preview 1 was released, so it’s not present in the preview. It’s present in Ruby trunk.


Array#prepend and Array#append in Ruby 2.5

This blog is part of our Ruby 2.5 series.

Ruby has Array#unshift to prepend an element to the start of an array and Array#push to append an element to the end of an array.

The names of these methods are not very intuitive. Active Support from Rails already has aliases for the unshift and push methods , namely prepend and append.

In Ruby 2.5, these methods are added in the Ruby language itself.

>> a = ["hello"]
=> ["hello"]
>> a.append "world"
=> ["hello", "world"]
>> a.prepend "Hey"
=> ["Hey", "hello", "world"]
>>

They are implemented as aliases to the original unshift and push methods so there is no change in the behavior.


Ruby 2.5 added yield_self

This blog is part of our Ruby 2.5 series.

Ruby 2.5 added a new method named yield_self. It yields the receiver to the given block and returns output of the last statement in the block.

irb> "Hello".yield_self { |str| str + " World" }
  => "Hello World"

How is it different from try in Rails ?

Without a method argument try behaves similar to yield_self. It would yield to the given block unless the receiver is nil and returns the output of the last statement in the block.

irb> "Hello".try { |str| str + " World" }
  => "Hello World"

Couple of differences to note are, try is not part of Ruby but Rails. Also try’s main purpose is protection against nil hence it doesn’t execute the block if receiver is nil.

irb> nil.yield_self { |obj| "Hello World" }
  => "Hello World"

irb> nil.try { |obj| "Hello World" }
  => nil

What about tap?

tap also is similar to yield_self. It’s part of Ruby itself. The only difference is the value that is returned. tap returns the receiver itself while yield_self returns the output of the block.

irb> "Hello".yield_self { |str| str + " World" }
  => "Hello World"

irb> "Hello".tap { |str| str + " World" }
  => "Hello"

Overall, yield_self improves readability of the code by promoting chaining over nested function calls. Here is an example of both the styles.

irb> add_greeting = -> (str) { "HELLO " + str }
irb> to_upper = -> (str) { str.upcase }

# with new `yield_self`
irb> "world".yield_self(&to_upper)
            .yield_self(&add_greeting)
  => "HELLO WORLD"

# nested function calls
irb> add_greeting.call(to_upper.call("world"))
  => "HELLO WORLD"

yield_self is part of Kernel and hence it’s available to all the objects.


Rails 5.2 implements fetch_values for HashWithIndifferentAccess

This blog is part of our Rails 5.2 series.

Ruby 2.3 added fetch_values method to hash.

By using fetch_values we are able to get values for multiple keys in a hash.

capitals = { usa: "Washington DC",
             china: "Beijing",
             india: "New Delhi",
             australia: "Canberra" }

capitals.fetch_values(:usa, :india)
#=> ["Washington DC", "New Delhi"]

capitals.fetch_values(:usa, :spain) { |country| "N/A" }
#=> ["Washington DC", "N/A"]

Rails 5.2 introduces method fetch_values on HashWithIndifferentAccess. We’ll hence be able to fetch values of multiple keys on any instance of HashWithIndifferentAccess class.

capitals = HashWithIndifferentAccess.new
capitals[:usa] = "Washington DC"
capitals[:china] = "Beijing"

capitals.fetch_values("usa", "china")
#=> ["Washington DC", "Beijing"]

capitals.fetch_values("usa", "spain") { |country| "N/A" }
#=> ["Washington DC", "N/A"]

Ruby 2.5 added delete_prefix and delete_suffix methods

This blog is part of our Ruby 2.5 series.

Ruby 2.4

Let’s say that we have a string Projects::CategoriesController and we want to remove Controller. We can use chomp method.

irb> "Projects::CategoriesController".chomp("Controller")
  => "Projects::Categories"

However if we want to remove Projects:: from the string then there is no corresponding method of chomp. We need to resort to sub.

irb> "Projects::CategoriesController".sub(/Projects::/, '')
  => "CategoriesController"

Naotoshi Seo did not like using regular expression for such a simple task. He proposed that Ruby should have a method for taking care of such tasks.

Some of the names proposed were remove_prefix, deprefix, lchomp, remove_prefix and head_chomp.

Matz suggested the name delete_prefix and this method was born.

Ruby 2.5.0-preview1

irb> "Projects::CategoriesController".delete_prefix("Projects::")
  => "CategoriesController"

Now in order to delete prefix we can use delete_prefix and to delete suffix we could use chomp. This did not feel right. So for symmetry delete_suffix was added.

irb> "Projects::CategoriesController".delete_suffix("Controller")
  => "Projects::Categories"

Read up on this discussion to learn more about how elixir, go, python, and PHP deal with similar requirements.


Ruby 2.5 introduces Dir.children and Dir.each_child

This blog is part of our Ruby 2.5 series.

Dir.entries is a method present in Ruby 2.4. It returns the output of shell command ls -a in an array.

 > Dir.entries("/Users/john/Desktop/test")
 => [".", "..", ".config", "program.rb", "group.txt"]

We also have method Dir.foreach which iterates and yields each value from the output of ls -a command to the block.

> Dir.foreach("/Users/john/Desktop/test") { |child| puts child }
.
..
.config
program.rb
group.txt
test2

We can see that the output includes the directives for current directory and parent directory which are "." and "..".

When we want to have access only to the children files and directories, we do not need the [".", ".."] subarray.

This is a very common use case and we’ll probably have to do something like Dir.entries(path) - [".", ".."] to achieve the desired output.

To overcome such issues, Ruby 2.5 introduced Dir.children. It returns the output of ls -a command without the directives for current and parent directories.

> Dir.children("/Users/mohitnatoo/Desktop/test")
 => [".config", "program.rb", "group.txt"]

Additionally, we can use Dir.each_child method to avoid yielding current and parent directory directives while iterating,

> Dir.each_child("/Users/mohitnatoo/Desktop/test") { |child| puts child }
.config
program.rb
group.txt
test2

As noted in the discussion the names were chosen to match with existing methods Pathname#children and Pathname#each_child.

These additions seem like simple features. Well the issue was posted more than two years ago.


Higher Order Component for rendering spinner in React Native app

In one of our previous blogs, we mentioned how recompose improves both the readability and the maintainability of the code.

We also saw how branch and renderComponent functions from recompose help us in deciding which component to render based on a condition.

We can use the code from renderComponent documentation to render a spinner component when the data is being fetched in a ReactJS application.

Initial Code

// PatientsList.js

import React, { Component } from 'react';
import LoadingIndicator from './LoadingIndicator';

export default class PatientsList extends Component {

  state = {
    isLoading: true,
    patientsList: [],
  }

  componentDidMount() {
    api.getPatientsList().then(responseData => {
      this.setState({
        patientsList: responseData,
        isLoading: false,
      })
    })
  }

  render() {
    const { isLoading } = this.state;
    if (isLoading) {
      return <LoadingIndicator isLoading={isLoading} />
    } else {
      return (
        <ScrollView>
          // Some header component
          // View rendering the patients
        </ScrollView>
      )
    }
  }

In the above code, when the PatientsList component mounts, it fetches the list of patients from the API. During this time, the isLoading state is true, so we render the LoadingIndicator component.

Once the API call returns with the response, we set the isLoading state to false. This renders ScrollView component, with our list of patients.

The above code works fine, but if our app has multiple screens, which show the loading indicator and fetch data, the above way of handling it becomes repetitive and hard to maintain.

Building a higher order component

Here’s where Higher Order Components(HOC) are very useful. We can extract the logic for the ability to show the loading indicator in a HOC.

// withSpinner.js

import React from 'react';
import { ScrollView } from 'react-native';
import LoadingIndicator from './LoadingIndicator';

const withSpinner = Comp => ({ isLoading, children, ...props }) => {
  if (isLoading) {
    return <LoadingIndicator isLoading={isLoading} />
  } else {
    return (
      <Comp {...props}>
        {children}
      </Comp>
    )
  }
};

export default withSpinner;

Here, we created a HOC component which accepts a component and the isLoading prop.

If isLoading is true, we show the LoadingIndicator. If isLoading is false, we show the supplied component with its children, and pass in the props.

Now, we can use the above HOC in our PatientsList.js file. The supplied component can be any React Native component based on the use case. Here in our case, its a ScrollView.

// PatientsList.js

import { ScrollView } from 'react-native';
import withSpinner from './withSpinner';
const ScrollViewWithSpinner = withSpinner(ScrollView);

export default class PatientsList extends Component {

  state = {
    isLoading: true,
    patientsList: [],
  }

  componentDidMount() {
    api.getPatientsList().then(responseData => {
      this.setState({
        patientsList: responseData,
        isLoading: false,
      })
    })
  }

  render() {
    const { isLoading } = this.state;
    return(
      <ScrollViewWithSpinner
        isLoading={isLoading}
        // other props
      >
        // Some header component
        // View rendering the patients
      </ScrollViewWithSpinner>
    )
  }

Conclusion

Because of the above extraction of logic to a HOC, we can now use the same HOC in all our components which render a loading indicator while the data is being fetched.

The logic to show a loading indicator now resides in a HOC. This makes the code easier to maintain and less repetitive.