I have two models, Product and Images and I want to upload multiple images with product nested form using arc package,
Here is my Product model
defmodule FileUpload.Product do
use FileUpload.Web, :model
schema "products" do
field :name, :string
field :category, :string
has_many :images, FileUpload.Image
timestamps()
end
@doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:name, :category])
|> validate_required([:name, :category])
end
end
And my Image model
defmodule FileUpload.Image do
use FileUpload.Web, :model
use Arc.Ecto.Schema
schema "images" do
field :image, FileUpload.ImageUploader.Type
belongs_to :product, FileUpload.Product
timestamps()
end
@doc """
Builds a changeset based on the `struct` and `params`.
"""
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:image])
|> cast_attachments(params, [:image])
end
end
My arc uploader for image model
defmodule FileUpload.ImageUploader do
use Arc.Definition
use Arc.Ecto.Definition
# Include ecto support (requires package arc_ecto installed):
# use Arc.Ecto.Definition
@versions [:original]
# To add a thumbnail version:
@versions [:original, :thumb]
# Whitelist file extensions:
def validate({file, _}) do
~w(.jpg .jpeg .gif .png) |> Enum.member?(Path.extname(file.file_name))
end
# Define a thumbnail transformation:
def transform(:thumb, _) do
{:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png}
end
# Override the storage directory:
def storage_dir(version, {file, scope}) do
"uploads/product/images/#{scope.id}"
end
end
My ProductController code for new and create action
defmodule FileUpload.ProductController do
use FileUpload.Web, :controller
alias FileUpload.Product
def index(conn, _params) do
products = Repo.all(Product)
render(conn, "index.html", products: products)
end
def new(conn, _params) do
changeset = Product.changeset(%Product{images: [%FileUpload.Image{}]})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"product" => product_params}) do
changeset = Product.changeset(%Product{}, product_params)
case Repo.insert(changeset) do
{:ok, _product} ->
conn
|> put_flash(:info, "Product created successfully.")
|> redirect(to: product_path(conn, :index))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
end
My new product form
<%= form_for @changeset, @action, [multipart: true], fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= label f, :name, class: "control-label" %>
<%= text_input f, :name, class: "form-control" %>
<%= error_tag f, :name %>
</div>
<div class="form-group">
<%= label f, :category, class: "control-label" %>
<%= text_input f, :category, class: "form-control" %>
<%= error_tag f, :category %>
</div>
<%= inputs_for f, :images, fn imf -> %>
<div class="form-group">
<%= label imf, :image, class: "control-label" %>
<%= file_input imf, :image, class: "form-control" %>
<%= error_tag imf, :image %>
</div>
<% end %>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
And the mix.ex application and deps block
# Type `mix help compile.app` for more information.
def application do
[mod: {FileUpload, []},
applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
:phoenix_ecto, :postgrex, :arc_ecto, :ex_aws, :hackney, :poison]]
end
# Specifies your project dependencies.
#
# Type `mix help deps` for examples and options.
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.6.0-rc3"},
{:arc_ecto, "~> 0.5.0-rc1"},
{:ex_aws, "~> 1.0.0-rc3"},
{:hackney, "~> 1.5"},
{:poison, "~> 2.0"},
{:sweet_xml, "~> 0.5"}
]
end
I can clearly see the images are passed to params
Parameters: %{"_csrf_token" => "BFwuXTIsfSAKZSRvQRUWNDM3HwsEEAAAhof1YzKxl3WVypRsdFhyrA==", "_utf8" => "✓", "product" => %{"category" => "Shrestha", "images" => %{"0" => %{"image" => %Plug.Upload{content_type: "image/jpeg", filename: "levis_jeans_25.jpg", path: "/tmp/plug-1481/multipart-466112-497804-1"}}}, "name" => "Ramita"}}
But only product is being saved, not the images. Is there something I am missing in my product controller create action. I have watched a nested form video in youtube and there it does nothing in create action.
Please tell me what and where I am missing something.
Thanks
You'll need to add a call to
cast_assoc
aftercast
so thatImage.changeset/2
is called for each image inProduct
: