phoenix 使用 arc 使用嵌套形式上传文件

phoenix upload file using nested form using arc

我有两个模型,产品和图像,我想使用 arc 包以产品嵌套形式上传多张图像,

这是我的产品型号

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

还有我的图片模型

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

我的图片模型 arc 上传器

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

用于新建和创建操作的我的 ProductController 代码

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

我的新产品形式

<%= 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 %>

以及 mix.ex 应用程序和 deps 块

# 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

我可以清楚地看到图像传递给了参数

  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"}}

但是只保存了产品,没有保存图像。我的产品控制器创建操作中是否缺少某些内容。我在 youtube 上观看了一个嵌套表单视频,但它在创建操作中没有任何作用。

请告诉我什么地方遗漏了什么。

谢谢

您需要在 cast 之后添加对 cast_assoc 的调用,以便为 Product 中的每个图像调用 Image.changeset/2:

defmodule FileUpload.Product do
  ...
  def changeset(struct, params \ %{}) do
    struct
    |> cast(params, [:name, :category])
    |> cast_assoc(:images)
    |> validate_required([:name, :category])
  end
end