How to generate initials avatar programmatically with MiniMagick and Active Storage

Posted on Fri Jan 17 2020

*Photo By Jon Tyson on Unsplash

Gravatar is an amazing service to quickly personalize your business users, but despite its power, it comes also with some disadvantages. The more Gravatars that you have on your page, the slower the page will load because your application will make a request for every gravatar to gravatar servers.


In this guide, we will generate Gmail-like avatars with the user's name initials, using the mini-magick gem then attaching users avatars to active storage.

Generating the avatar

Before getting serious, Let's first see a basic example of generating a new image. For this, we need to install MiniMagick gem.

gem install mini_magick

MiniMagick is a lightweight gem for image processing based on ImageMagick. So before using it, we need to install ImageMagick first. Depending on your system, you will find instructions on how to install it from their homepage.


Let's, for now, create an image with a gray background.

Create a avatar.rb file

# avatar.rb
require 'mini_magick'
def generate_image
MiniMagick::Tool::Convert.new do |image|
image.size '200x200'
image.xc 'gray'
image << 'avatar.jpg'
end
end
generate_image

run the script, you should see a new image with the name avatar.jpg having a gray background appears on the same directory.

Let's add more properties to the generated image:

# avatar.rb
require 'mini_magick'
def generate_image
MiniMagick::Tool::Convert.new do |image|
image.size '200x200'
image.gravity 'center'
image.xc '#561B8D'
image.pointsize 100
image.fill '#C8F7C5'
image.draw "text 0,0 ME"
image << 'avatar.jpg'
end
end
generate_image

Run the script again, you should see this image with centered "ME" letters.


avatar-sample


Now we have finished the image generation logic, the real question remains where to put this logic?


My first thought was to use active record callbacks, we can run the generate_avatar method with user name initials after creating a new user, but there is one problem with using callbacks, generate_avatar callback will create a dependency because creating the user and generating the default image are tightly coupled together.


Consider a scenario where you decided to add Omniauth authentication with Facebook and attach the user avatar to facebook image, this code will still run while we don't actually need it.


So instead we can move this logic into the registration controller which separates the concerns to only creating a user with full registration, but since controllers are only supposed to have request/response logic, we need a new design pattern.

Enter Service Objects

Putting all image generation logic in the registration controller breaks the more important principle in OOP, single responsibility. So what is the solution?


Service objects are just Plain Old Ruby Object (PORO), which should encapsulate and execute single action.


Our code should look like:

# registration_controller.rb
def create
# ... user creation logic
AvatarGenerator.call(user)
end

or if you are using devise gem for authentication, we need first to override the default create action in devise's registration_controller.

After generating devise registration controller let's customize the create action to generate the default avatar after creating the new user.


# registration_controller.rb
class Users::RegistrationsController < Devise::RegistrationsController
def create
super { AvatarGenerator.call(resource) }
end
# ...
end

What this will do is first will call the parent create action, and yield any block if any was given, in our case it will execute the avatar generation logic.

The new avatar generation class is now responsible for one thing, the registration controller is more readable and doesn't have logic that doesn't belong to it.

Adding MiniMagick/Image Processing gem

Now that we have finished with our design decision, let's add our AvatarGenerator class.

Before adding the code, we need to first install MiniMagick in our application.


To add mini_magick gem, you can install image_processing gem which is a wrapper for ImageMagick and libvips. Image Processing gem also provides variants for your images so that you can create thumbnails, fixed-size avatars, or any other image manipulations.

Open Gemfile and add this line:

gem 'image_processing'

Or uncomment it if is already there, then run bundle install to install the gem.


We are going to attach the generated image with Active Storage attachments, so before moving on make sure that active storage is installed

After you have added active storage, add this line to user model:

# user.rb
has_one_attached :avatar

We are ready now to add our AvatarGenerator class, let's move on.

Adding AvatarGenerator service object class

First, create a new folder inside the app directory called services which will hold all service objects that our application will have. Now, let's create avatar_generator.rb file.

This class will:

  • Generate initials avatar image for new registered user.
  • Attach the newly generated image to the user's avatar with active storage.
require 'image_processing/mini_magick'
class AvatarGenerator
attr_reader :user
AVATAR_COLORS = { '#561B8D' => '#C8F7C5',
'#DFF0D8' => '#468847',
'#F0E68C' => '#550055',
'#C8C8C8' => '#551700',
'#CD594A' => '#FFFFFF' }.freeze
def self.call(user)
new(user).call
end
def initialize(user)
@user = user
end
def call
generate_image
user.avatar.attach(io: File.open('default.jpg'),
filename: "#{user.username}-default.jpg",
content_type: 'image/jpg')
clean
end
private
def generate_image
rand = rand 0..4
MiniMagick::Tool::Convert.new do |image|
image.size '200x200'
image.gravity 'center'
image.xc AVATAR_COLORS.keys[rand]
image.pointsize 100
image.fill AVATAR_COLORS.values[rand]
image.draw "text 0,0 #{initial_letters}"
image << 'default.jpg'
end
end
def initial_letters
user.first_name.chr.capitalize + user.last_name.chr.capitalize
end
def clean
File.delete('default.jpg')
end
end

I have added some random color choosing functionality to generate different colors every time you generate an avatar.


In conclusion We have implemented a way to programtically generate Gmail-like avatars and used Service Objects design pattern to DRY fat controllers.


This concludes our detailed look into generating initials avatars in Rails. Hopefully, everything is working for you!

Resources:

© 2020 Muhammad Ebeid. This site is powered by Netlify