Technology

Eleni Chappen

Using Active Storage with Active Model Serializers

Jul. 6, 2018

Attachment is the root of all suffering. If you’re a web developer, attachments are the root of all suffering.

Paperclip, a longtime go-to gem for managing file attachments, has been deprecated in favor of Active Storage, which is now officially part of Ruby on Rails as of version 5.2. So for a recent greenfield app, I took the dive into Active Storage.

Switching out dependable gems for new ones can be anxiety-inducing, but that’s web developer life. While Active Storage ended up being a breath of fresh air for many reasons, it came with a few of it’s own gotchas, particularly when dealing with querying data.

Active Storage and N+1 queries

Let’s say a user has one avatar. While Paperclip would add a few columns to your users table to store the avatar’s file data, file type, etc., Active Storage will create new tables that are solely responsible for keeping track of asset data and associating this data to your records.

These tables are called blobs and attachments. Blobs represent the actual metadata of the files, whereas Attachments are what join blobs (file data) to your application’s records (users). For every file you upload, a blob is created and associated to your record through an attachment.

These relationships are created when you attach a file through Active Storage using the has_one_attached shorthand:

class User < ApplicationRecord  
  has_one_attached :avatar

It’s important to understand these relationships between blobs and attachments when dealing with record queries that may have attachments.

For has_one_attached relationships, you can avoid N+1 queries by adding the with_attached_X method that Active Storage generates for you based on your attachment name:

User.with_attached_avatar.where(active: true)  

This is basically saying:

User.includes(avatar_attachment: :blob).where(active: true)  

I had to take special care of these relationships when using Active Model Serializers. Normally, when dealing with child records in a serializer, you can define a relationship using has_one or has_many:

class GameSerializer < ActiveModel::Serializer

  attributes :title

  has_many :users

If my User Serializer is using the avatar in any way, this would create an N+1 scenario:

class UserSerializer < ActiveModel::Serializer

  attributes :avatar_url

  def avatar_url
    ...object.avatar_url...

In order to avoid N+1 queries here, I had to ditch the built-in has_many and create a custom users method for the Game Serializer:

class GameSerializer < ActiveModel::Serializer

  attributes :title, :users

  def users
    User.with_attached_avatar.where(game_id: object.id)

Finding the right asset URL for serialization

When passing image urls through serializers, I had to search through some Active Storage Github issues and documentation to find the right url to use.

Creating images of smaller sizes is common for real-life use of your assets. Active Storage calls these variants. You’ll most likely use variants rather than the original asset in serializers.

If you want a url of the asset variant, include the Rails.application.routes.url_helpers module in your serializer, then use rails_representation_url in your url method:

class UserSerializer < ActiveModel::Serializer  
  include Rails.application.routes.url_helpers

  attributes :avatar_url

  def avatar_url
    variant = object.avatar.variant(resize: "100x100")
    return rails_representation_url(variant, only_path: true)
  end

If you want to use the original asset, use rails_blob_path instead:

return rails_blob_path(object.avatar, only_path: true)  

So there’s a little extra work when serialization is concerned, but overall I really enjoyed working with Active Storage and appreciate what a great thing this is for the Rails community.

Eleni Chappen

What People Are Reading

Technology

11/20/17

Cross-Platform mobile development is cost-effective and faster to launch

Two years ago, if a person approached us for mobile app development, the first question would be what platform to launch on first - iOS or Android. It was almost double the cost to deploy on both and double the maintenance.

Fast forward to today, we can deploy on iOS and Android quicker before, at the same time, and with the expected native quality.

So what has changed? Technology has gotten a lot better. We particularly use React Native, an open source framework from Facebook, that allows developers to develop in a common language, Javascript, and deploy on both iOS and Android.

And it is native! Previous technologies for cross platform mobile apps would create a wrapper of a web view. Think of the older experience as using the browser on your phone for a site with a few more bells and whistles. It was close but the difference is noticeable when switching to native applications, from scrolling to the overall experience.

How does this impact everything now?

Cost is no longer double for both platforms. It can even cost less due to wide spread community support developing open source libraries.

Maintenance is a lot easier. A majority of the issues can be fixed in one code base and of the same language.

Recruitment and team knowledge is easier. Your team only need to know one language and most developers know Javascript. iOS is written in Objective C or Swift and Android is written in Java.

Reusable code for desktop web views. With React Native, we are able to share over 50% of the code with the web view, saving all of the above even more.

Stay tuned, as I’ll be going more into the technologies and other benefits.

Operations

8/29/16

Watch us eat our own dog food

20spokes is trying a new diet, so to speak. If you’re unfamiliar with the phrase “eating your own dog food,” it’s a common expression in the software world referring to a developer’s practice of using their own products. It’s said to have originated from 1970s television advertisements for Alpo dog food, where the owner of the company would make a point of feeding Alpo to his own dogs. So in its broader interpretation, “practice what you preach” would be an appropriate alternative. Either way, if we can help others build great products, we want to show that we can build our own great products as well.

It’s something we’ve thought about for a while, and now we’re finally taking the steps to make it happen. Client work will still always be our primary focus, but we have the team, the experience, and the aspirations; why wouldn't we work on our own ideas too? Like our clients, though, we don't want to jump into these ventures haphazardly only to end up with a well built app that nobody else wants. So we're putting these ideas through the same process of discovery, validation, and planning that we would with anyone who came into our office. In a way, by becoming clients of our own process, we’re getting our first helping of dog food!

Over the last week or so, we’ve adopted the mindset of a founder with a vision, and taken one of our ideas through the first steps of conception. Working through this process as the “founder” has already given us some great new insight, and we're excited to share this journey with you. So stay tuned for the next several weeks as we document all the steps we take and lessons we learn along the way; we're going to find out just how good our dog food tastes.

Trying to get your own product idea to market? Contact us to learn more about our process and how we can help.