Elixir Phoenix models with images.

Today I’m going to talk about elixir and phoenix framework. It’s my first post about them and today I would like to tell you how did I solve quite common problem for web development – serving images for your domain models.

Problem:

In my app – users have avatars,  companies have logos and any other model can have referring image. I need to have ability to save images for my models. I want to have ability to save them to AWS S3 and locally(in future I want to add new providers).

I want to get: 

  • model refers to image (user.image_id => image.id company.image_id => image.id). See image below.

image_refs

  • I need to implement AWS S3 and local adapters to serve images.
  • I want to have ability to get image url from model user.image_url(:avatar_id) App.User.image_url(user, :image_id)
  • I want to have ability to get resized thumbs App.user.image_url user, :image_id, width: 200, height: 250 (this will be in part #2)

Let’s go. 

Let’s started from upload manager. I need a component that will allow to manage my images. As I mentioned below I want to have ability to upload my images to different storages. So I need some interface for such components. In elixir we don’t have pure interfaces but we have @behaviour-s. Here’s what I need – save!, exists?, delete!, url.

Local upload manager.

Ok next step is real implementation of upload manager and let’s start from local file storage.

We use elixir File to manage file locally we need to specify base path for our uploads that will be available for web server.

AWS S3 upload manager.

For AWS we need to add some deps to our code.

{:ex_aws, “~> 1.0.0-beta0”},
{:sweet_xml, “~> 0.6.0”},

All that code looks really simple when it’s ready – LOL. At this point we also implement some basic operations with ex_aws – create, update, url. Nothing really serious. Note that we use :public_read ACL by default. You can change it and then return signed URL-s. For example you want to upload some private documents or other media.

Ok we have upload managers now we need to add functionality that will keep info about images. BUT we missed one detail. Before we go further we need to find a way how we gonna choose between upload manager adapters. We need some IoC container. I found pact for this purpose.

Image model.

  • :type – will keep type of related model. For example example for user avatar I can save “user” sting or “cmopany_square_logo”. Type is actually redundant. We don’t need really need it but I keep it to easily distinguish images for other models.
  • :path – will keep unique path/key for images – for S3 it’s s3 key for local storage it’s just path in your file system.
  • :mime – image mime type (image/png, image/gif)
  • :size – image file size. Could be useful to get some stats.
  • :width, :height – image size. We will need it when we want to get thumbs.
  • :parent_id – we don’t need it in this part. But I’m going to use this field to keep resized copies of images.

Let’s add some functionality image. I want to save image by URL with our upload managers. And have ability to return URL for saeed image.

A few notes for image model. I used HTTPoison http client to fetch image.  I’m not sure how much it’s effective to save image like this. I’d dig about streams. But not in this part :). We get mime and size using http response headers. Also I generate path like “#{type}/#{uniq_hash}/#{basename}”  unique hash helps us to save image before main model (image/company/whatever is saved). Also it’s more string in terms of security. Could be useful for private media.

Ok we are almost done, but we need some function that we could inject into other models like user or company that will return saved image.

I’m still working on this module. Not we can use App.ModelHelpers.HasImageHelper in user/company models and then get url to saved image

url = User.get_image_url(user, :image_id)
assert url == “http://localhost:4000/media/#{image_path}”

To be continued…

Leave a Reply

Your email address will not be published. Required fields are marked *