Hello. It’s been a while since my last post.

Did you have a nice summer? I sure did. Me and my better half went on a long motorcycle vacation through the west side of Norway. The nature is really amazing, Highly recommended.

Anyways, back to coding, right?

I figured I’ll show you a way to upload images to your Phoenix application. We will start with uploading to a local folder, and look into uploading to S3/Google in another post.

As always, we’ll start with a new project. I will call mine “Imageer”:

~/$ mix phoenix.new imageer

* creating imageer/config/config.exs

* creating imageer/config/dev.exs

* creating imageer/config/prod.exs

................................

Fetch and install dependencies? [Yn] y

After a stupid long wait, (at least for me. Does fetching dependencies for a new project take several minutes for you?) we have ourself a fresh new application.

We’ll cd into our app and create our new database:

~/$ cd imageer/

~/imageer$ mix ecto.create

==> connection

Compiling 1 file (.ex)

Generated connection app

==> fs (compile)

............................

The database for Imageer.Repo has been created

Sweet. Let’s start with the C and R of a CRUD app. we’ll fire up the model generator:

~/imageer$ mix phoenix.gen.model Image images image:string

.....

~imageer$ mix ecto.migrate

.....

09:49:36.323 [info] == Migrated in 0.0s

Perfect. Next up, we’ll create our controller, routes, views, templates

# web/controllers/image_controller.ex defmodule Imageer.ImageController do

use Imageer.Web, :controller

alias Imageer.Image

alias Imageer.Repo def index(conn, _) do

render(conn, “index.html”)

end def new(conn, _) do

changeset = Image.changeset(%Image{})

render(conn, “new.html”, changeset: changeset)

end def create(conn, %{"image" => image_params}) do

IO.inspect image_params

end

end

This is pretty basic, and we’ve covered this in previous blog posts. Notice that in the create function, we’ll inspect the content of our image_params, which we get once we submit a form from the new action.

Standard view for our images:

# web/view/image_view.ex defmodule Imageer.ImageView do

use Imageer.Web, :view

end

And we’ll just set up a nice resource in our router:

# web/router.ex

...... scope “/”, Imageer do

pipe_through :browser # Use the default browser stack get “/”, PageController, :index

resources “/images”, ImageController

end

We’ll create a new image folder in our templates folder for our view templates, and two new files. index.html.eex and new.html.eex. If we go to http://localhost:4000/images, we can finally see our empty views.

For the sake of making things easy, let’s populate our index view with a link to uploading new images:

And we’ll start implementing our image form:

# web/templates/image/new.html.eex <h2>Upload a new image</h2>

[multipart: true], fn f -> %>

<div class=”form-group”>

<label>Image</label>

<%= file_input f, :image %>

<%= error_tag f, :image %>

</div>

<div class=”form-group”>

<%= submit “Submit”, class: “btn btn-default” %>

</div>

<% end %> @changeset , image_path( @conn , :create),[multipart: true], fn f -> %> Image

Sweet. We got ourself a form. For the sake of it, let’s try and upload a new image. The app is going to blow up, however, if we look in server console, we’ll see what our image_params consist of:

%{“image” => %Plug.Upload{content_type: “image/png”, filename: “image.png”,

path: “/var/folders/jx/r8fqzstd19v3_jf62256gvt00000gn/T//plug-1474/multipart-190927–216841–2”}}

Cool. So we can see what type the file we’re posting, the filename, and a temporary file path.

Now, let’s do so we actually save the file. We need to add a bit more to our create action:

#web/controllers/image_controller.ex

...... def create(conn, %{“image” => image_params}) do

IO.inspect image_params

changeset = Image.changeset(%Image{}, image_params) case Repo.insert(changeset) do

{:ok, image} ->

conn

|> put_flash(:info, “Image was added”)

|> redirect(to: image_path(conn, :index))

{:error, changeset} ->

conn

|> put_flash(:error, “Something went wrong”)

|> render(“new.html”, changeset: changeset)

end

end

And we’ll finally bring in Arc. We’ll add it to our dependencies function. In our mix.exs file, in deps:

# mix.exs

........

defp deps do

[{:phoenix, “~> 1.2.1”},

{:phoenix_pubsub, “~> 1.0”},

{:phoenix_ecto, “~> 3.0”},

{:postgrex, “>= 0.0.0”},

{:phoenix_html, “~> 2.6”},

{:phoenix_live_reload, “~> 1.0”, only: :dev},

{:gettext, “~> 0.11”},

{:cowboy, “~> 1.0”},

{:arc, “~> 0.5.2”}, #add this

{:arc_ecto, “~> 0.4.4”}] #and this

end

..........

We also need to add :arc_ecto to our application function, so that it gets run when we start our app:

# mix.exs

......

def application do

[mod: {ImageUpload, []},

applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy,

:logger, :gettext, :phoenix_ecto, :postgrex,

:arc_ecto]]

end

......

We’ll then run mix deps.get, and restart our application. Now, we need to generate a new uploader with arc:

imageer$ mix arc.g image_uploader

* creating web/uploaders/image_uploader.ex

Cool. So the generator created a new file for us. Let’s do some minor changes to it. first, according to the Arc documentation, we need to import another using macro, which will provide a set of functions to ease integration with Arc and Ecto. We also define local storage:

# web/uploaders/image_uploader.ex defmodule Imageer.ImageUploader do

use Arc.Definition

use Arc.Ecto.Definition def __storage, do: Arc.Storage.Local

.......

Now, in our schema (You’ll find it in our model file), we’ll add another using macro, and we’ll specify the type of the column in our schema with Imageer.ImageUploader.Type, and use Arc’s own cast function, cast_attachments/3:

# web/models/image.ex defmodule Imageer.Image do

use Imageer.Web, :model

use Arc.Ecto.Schema schema “images” do

field :image, Imageer.ImageUploader.Type timestamps()

end def changeset(struct, params \\ %{}) do

struct

|> cast(params, [:image])

|> cast_attachments(params, [:image]) end

end

Cool. If we now try and upload an image, we’ll get a nice flash:

Now, let’s open up iEX and verify that it actually got stored in our database:

~/imageer$ iex -S mix

iex(1)> alias Imageer.Image

iex(2)> alias Imageer.Repo

iex(3)> Repo.all(Image)

[debug] QUERY OK source=”images” db=1.0ms decode=3.4ms

SELECT i0.”id”, i0.”image”, i0.”inserted_at”, i0.”updated_at” FROM “images” AS i0 []

[%Imageer.Image{__meta__: #Ecto.Schema.Metadata<:loaded, “images”>, id: 1,image: %{file_name: “image.jpg”,

updated_at: #Ecto.DateTime<2016–09–18 10:01:34>},

inserted_at: #Ecto.DateTime<2016–09–18 10:01:34>,

updated_at: #Ecto.DateTime<2016–09–18 10:01:34>}]

Would you look at that! The image file_name is stored in our database. If you have a look in imageer/uploads, you’ll find our newly uploaded file. Now, let’s figure out how to actually display the image.

First of all, we need to gather all images from the repo, and make them available in our index view:

# web/controllers/image_controller.ex

...... def index(conn, _) do

images = Repo.all(Image)

render(conn, “index.html”, images: images)

end

......

In our index template, we’ll iterate over all images made available for us. We’ll use Arc’s image_uploader function:



<h4><%= link "Upload new image", to: image_path(

<%= for image <-

<img src="<%= Imageer.ImageUploader.url({image.image, image})%>"><br>

<% end %> # web/templates/image/index.html.eex @conn , :new) %> @images do %>



What? Shouldn’t that show us pictures? Well. Phoenix isn’t exactly serving our uploads folder by default, so we need to explicitly tell it to. In endpoint.ex, add a new Plug.Static, underneath the one that’s already there:

# lib/imageer/endpoint.ex

...... plug Plug.Static, #Leave this as it is.

at: “/”, from: :image_upload, gzip: false,

only: ~w(css fonts images js favicon.ico robots.txt) plug Plug.Static,

at: “/uploads”, from: Path.expand(‘./uploads’), gzip: false

......

And refresh your application:

Tada! We’ve got cats!

Next time, we’ll expand this app, discussing how to upload files to cloud storages like AWS S3. You can find the next post here

That’s all for now, thanks for reading!

Until next time

Stephan Bakkelund Valois