Arc — CarrierWave for Phoenix

In this post I’ll try to explain how to set up basic image uploading using the arc package for Elixir. I’ve decided to divide this article into three parts:

  • Part I will cover the basic setup and local storage
  • Part II will provide you instructions how to upload files into AWS Simple Storage Service (S3)
  • Part III will cover using CDN services.

Before we start…

In this post I’m using Phoenix application from my previous blogpost, which already has user model in place (generated with coherence). I have also used Elixir v1.4.4 and Phoenix v1.3.0. If your versions differ and some commands do not seem to work, I recommend you to check official arc readme.

Please bare in mind, that your project name may vary from the one used here, so you should change your code accordingly.

Part I: Setting up uploaders

The first step we need to take in order to take advantages of arc package in our application, is to add arc into our dependencies list. If your app, just like most of Phoenix applications, rely on ecto, you may find it useful to also add arc_ecto package. Fire up the editor of your choice and edit deps section in mix.exs file to look like this:

Run mix deps.get from your project directory to download freshly added packages. Afterwards, add this few lines at the end of your config/config.exs to make sure that arc saves files locally:

You should also add :arc_ecto to your extra_applications in mix.exs:

Now, that we are ready to generate our first uploader, try running mix arc.g avatar command, If everything goes fine, you should see your new uploader in web/uploaders/avatar.ex directory. It might be worth to crack-open this file - you will find plenty of useful tips & tricks left for us by the generator in there.

Note: If you are using Phoenix version 1.3.0 or above, and your arc generator placed uploader under <project_name>/web/uploaders , while the rest of you app lives in <project_name>/lib/<project_name>_web , you should move your uploader to <project_name>/lib/<project_name>_web/uploaders . You can find more info about this issue under this link.

Assuming that we want to generate thumbnail versions of our avatar, or to validate what type of files our users try to upload, it might be necessary for us to apply some slight changes to the avatar we created:

What we are doing in here is:

  1. Defining versions of our image. We want to save original image and process it to make a thumbnail (aka thumb) version.
  2. Validating file extensions. We want to accept only jpg, jpeg and png files.
  3. Defining ImageMagick transformation for our thumb version (more examples available here)
  4. Defining filename — we want to save our uploaded files as original.png or thumb.png.
  5. Defining storage directory for our uploads. Taking in consideration that each model (e.g. user) has its unique uuid, we will save files under uploads/user/avatars/#{user.uuid} — this way we’ll be sure that one user can not override avatar image of another user.
  6. Specifying default avatar url. Please remember to attach such files under defined path. In this example, it will be default_original.png and default_thumb.png in static/assets/images/avatars directory.

Now it’s time to adjust our user model… run mix ecto.gen.migration add_avatar_to_user and fill new migration, so it will add two new fields to our user model: avatar (type: string) and uuid (type: string):

After that, please remember to run mix ecto.migrate task from your console, in order to apply changes to the database. If that command executes perfectly smooth, go to your user model - it’s time to make sure that our avatar field belongs to changeset and our uuid is in place:

Pay attention to few changes we have made in here:

  1. We used Arc.Ecto.Schema
  2. We added avatar and uuid fields to schema definition.
  3. We called private function check_uuid in our changeset, to make sure that our user has uuid assigned. It is also important to call this method after validations, so we won’t trigger additional calculations if our user is invalid anyway.
  4. We called cast_attachments in our changeset functions.

The very last thing we need to do in order to make all of this work, is adding avatar file field to user forms. Open your editor on user registration/edit form and add this fragment of code:

You should also remember that file uploading requires form to have multipart attribute set to true:

Now, when you fire up your server with mix phx.server and navigate to registration form, you should see that it has a nice, working file field.

Accessing uploaded images

Before we access our images on the front-end side, we need to reconfigure our phoenix application to serve static assets. To do so, edit your endpoint.ex file:

Now, if you want to show avatars to your users, add this part of code i.ex. to your registrations/show template:

Avatar.url helper takes as arguments tuple containing our user avatar, our user itself, and an optional parameter which is image version, so if we wanted to show a thumbnail version, it would look like this:

Worth mentioning:

  1. If you are using git or any other version control system, you should probably ignore your /uploads directory to avoid uploading images into your remote repository.
  2. arc_ecto will take care of adding version suffix to filenames of the uploaded file, so if you are using CDN or any other caching mechanism, your cache should get revalidated even if the new filenames are still the same.
  3. Remember that you can define different storage for arc lib using environment configs (dev.exs, production.exs etc.)
  4. It is highly recommended to keep dev and test uploads in local storage and production uploads on remote storage like AWS Simple Storage Service.

Part II: Uploading to AWS S3

In order to upload images to third-party storage service, like AWS S3, we will need few additional packages in our app. Add them to packages section in mix.exs file and install them with mix deps.get command:

Then, add new applications to extra_applications config in the same file:

The only thing left to upload images to AWS S3 instead of local storage is changing arc configuration, so it will look like this:

Of course depending on your AWS setup, you will need to change your region and export AWS secret_access_key, secret_access_key_id and bucket name to environment variables. With that config in place, arc will take care of uploading your images directly to the Simple Storage Service.

Part III: Using CDN service

Using CDN service is the easiest part of this tutorial. The only thing you will need to provide, is CDN hostname under asset_host configuration for arc:

This way, after providing CDN hostname as ASSET_HOST environment variable, and pointing your S3 bucket as source for your CDN, arc will automatically start generating assets urls basing on Content Delivery Network, which can provide your assets much faster than S3 server itself.

Conclusion

Arc package is a perfect match for all elixir applications requiring file upload. Thanks to its short setup, we are able to quickly introduce uploading mechanism to our application, and speed up development and testing process. For those who already have hands-on experience with Ruby on Rails, it would be hard not to see it’s similarities to CarrierWave gem, what makes even easier to switch from Rails to Phoenix.