Public
Snippet $634 authored by Jeremy

ELIXIR/PHOENIX - Uploading images locally (With ARC)

Edited
~/$ 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
~/$ cd imageer/
~/imageer$ mix ecto.create
==> connection
Compiling 1 file (.ex)
Generated connection app
==> fs (compile)
............................
The database for Imageer.Repo has been created
~/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
# 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
# web/templates/image/index.html.eex

<h4><%= link “Upload new image”, to: image_path(@conn, :new) %></h4>

And we’ll start implementing our image form:

# web/templates/image/new.html.eex

<h2>Upload a new image</h2>

<%= form_for @changeset, image_path(@conn, :create),

            [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 %>
%{“image” => %Plug.Upload{content_type: “image/png”, filename: “image.png”,

path: “/var/folders/jx/r8fqzstd19v3_jf62256gvt00000gn/T//plug-1474/multipart-190927–216841–2”}}
#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:

# web/templates/image/index.html.eex
<h4><%= link "Upload new image", to: image_path(@conn, :new) %></h4>
<%= for image <- @images do %>

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

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
......
15 Bytes
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment