Upgrade Paperclip and AWS SDK

While building a new docker based staging server for a legacy rails 5.0 app, I started getting the following error:

Your bundle is locked to mimemagic

Clearly the gem is not found on rubygems.org. I recently read on ruby weekly about licencing issue so I need to upgrade the mimemagic gem to make progress.

Our app is using gem paperclip 4.3.7 which depends on mimemagic 0.3.0. So we have no way but to upgrade the paperclip to fix the mimemagic issue. As such paperclip upgrade is easy but can become complicated in some instances but we took a call and decided to upgrade it to the highest version of paperclip 6.1.0 and so the AWS-SDK as well.

I strongly recommed to check the below video found in README showing how to migrate from Paperclip 4.x to 5.x and aws-sdk 1.x to 2.x before you migrate the paperclip.

Here is our upgrade process:

1. Change the Gemfile and bundle

Here is a comparison of the changes made:

Gemfile comparision

2. Change the config/initializers/aws.rb

AWS.config({
  region: 'eu-central-1',
  access_key_id: Settings.aws_access_key,
  secret_access_key: Settings.aws_secret_key
})

to

require 'aws-sdk-core'

Aws.config.update(
  region: 'eu-central-1',
  credentials: Aws::Credentials.new(Settings.aws_access_key, Settings.aws_secret_key)
)

Notice the difference between AWS to Aws.

3. Change the paperclip config to add the s3_region

Paperclip config comparision

And that worked!

If you are using aws-sdk for uploading files then you need to make changes there as well. We were saving a couple of autogenerated files to S3. Here are simplified snippets of a file upload code before and after migration.

Before
filename = ...
local_file = '/path/to/your/file'

bucket = AWS::S3.new.buckets['bucket name']
bucket.objects[filename].write(file: local_file, acl: :public_read)
After
filename = ...
local_file = '/path/to/your/file'

object = Aws::S3::Resource.new.bucket('bucket name').object(filename)
File.open(local_file, 'rb') do |file|
  object.put(body: file, acl: 'public-read', content_type: 'text/xml')
end

Hope this helps!

TIL - Use Struct over OpenStruct for performance

TL;DR Struct is much faster than OpenStruct.

I learned this while contributing to Gitlab.

There has already been written about Struct and OpenStruct. But this post focuses on the performance implications of using one over other.

Developers have tendency to use OpenStruct over Struct generally because with OpenStruct, we can arbitrarily set & access attributes. However, in case of Struct, we can only set & access attributes defined at the time of Struct declaration.

e.g.,

Here is a quick benchmark report performed on MacOS taken from Gabriel Mazetto’s comment on Gitlab for quick reference:

Ruby 2.7.2

Warming up --------------------------------------
      openstruct new   138.338k i/100ms
   openstruct access     6.255k i/100ms
          struct new   300.884k i/100ms
      struct acccess   240.079k i/100ms
Calculating -------------------------------------
      openstruct new      1.409M (± 2.1%) i/s -      7.055M in   5.008396s
   openstruct access     62.552k (± 3.6%) i/s -    312.750k in   5.007322s
          struct new      3.032M (± 1.8%) i/s -     15.345M in   5.063488s
      struct acccess      2.397M (± 1.4%) i/s -     12.004M in   5.008534s

Comparison:
          struct new:  3031505.6 i/s
      struct acccess:  2397159.8 i/s - 1.26x  (± 0.00) slower
      openstruct new:  1409319.1 i/s - 2.15x  (± 0.00) slower
   openstruct access:    62551.7 i/s - 48.46x  (± 0.00) slower

Ruby 3.0.0

Warming up --------------------------------------
      openstruct new     5.654k i/100ms
   openstruct access     4.880k i/100ms
          struct new   298.290k i/100ms
      struct acccess   237.080k i/100ms
Calculating -------------------------------------
      openstruct new     57.811k (± 1.7%) i/s -    294.008k in   5.087150s
   openstruct access     49.124k (± 2.1%) i/s -    248.880k in   5.068713s
          struct new      2.972M (± 1.7%) i/s -     14.914M in   5.020725s
      struct acccess      2.363M (± 3.0%) i/s -     11.854M in   5.022310s

Comparison:
          struct new:  2971520.7 i/s
      struct acccess:  2362714.6 i/s - 1.26x  (± 0.00) slower
      openstruct new:    57811.0 i/s - 51.40x  (± 0.00) slower
   openstruct access:    49123.9 i/s - 60.49x  (± 0.00) slower

You can see here OpenStuct is 60 times slower than Struct.

Even OpenStuct can be slower than Hash in some cases. From the Ruby doc,

Creating an open struct from a small Hash and accessing a few of the entries can be 200 times slower than accessing the hash directly.

Go for Struct whenever possible over OpenStruct.

Ruby on Rails, Gmail and Net::SMTPAuthenticationError: 535-5.7.8 Username and Password not accepted

Are you getting following error while using Gmail SMTP?

Net::SMTPAuthenticationError: 535-5.7.8 Username and Password not accepted

The solution is simple.

  1. Go to https://www.google.com/settings/security/lesssecureapps
  2. Enable the switch marked below and you are done!

Allow Less Secure Apps

Dotenv : Are the environment variables not loading in the `production` environment of your Rails application?

You have configured your .env for all common environment variables across environments but they are not loading in production environment.

Here is a catch in the intallation doc.

could not fetch specs from https://rubygems.org/

Here you can see that the document mentions to install the gem only in development and test environments and you just copied and pasted as is in your Gemfile !

The fix is simple. Just remove groups: [:development, :test] or put the dotenv-rails gem outside of all environment like ohter gems required in all environment.

CanCanCan : Use differnt column to `load_and_authorize_resource` in inherited controllers

Recently I came across a situation where there was a need to use differnt column for load_and_authorize_resource in an inherited controller.

By default CanCanCan, use id to find resources in load_and_authorize_resource

If you want to use different column, you can use :find_by option:

load_and_authorize_resource :find_by => :slug

But this will apply on all the controller inheriting it.

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base

  load_and_authorize_resource

end
# app/controllers/posts_controller.rb
class PostsController < ApplicationController

  # for this controller, resource will be load using `id` 

end

If you try to put load_and_authorize_resource :find_by => :slug in posts_controller.rb hoping it will override the behaviour then you are wrong. It doesn’t work.

To make it working, you need to use :prepend option.

# app/controllers/posts_controller.rb
class PostsController < ApplicationController

  load_and_authorize_resource :find_by => :slug, prepend: true

end

Hope this helps!