Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ecto model is not yet saved, when Arc is saving the file, resulting in empty scope #7

Closed
MartinElvar opened this issue Sep 24, 2015 · 10 comments

Comments

@MartinElvar
Copy link

Using the local storage, i ran into a problem, where the folders would not be created upon model creation, my storage dir looks like this.

  # Override the storage directory:
  def storage_dir(_, {_, scope}) do
    module_name = scope.__info__(:module)
    |> Module.split
    |> List.last
    |> String.downcase
    "priv/static/images/#{module_name}/banner/#{scope.id}"
  end

As you can see i use the scope id, as suggested in the documentation; the problem is that Arc saves the file before the model i saved, which means that the id doesn't exist. This result in the files ending up in priv/static/images/#{module_name}/banner/ overwriting each other constantly.

This is what my saving proces looks like.

  def create(conn, %{"article" => article_params}) do
    # Add author.
    article_params = Dict.put_new(article_params, "admin_id", Plug.Conn.get_session(conn, :current_user))
    changeset = Article.changeset(%Article{}, article_params)

    case Repo.insert(changeset) do
      {:ok, _article} ->
        conn
        |> put_flash(:info, "Article created successfully.")
        |> redirect(to: admin_article_path(conn, :index))
      {:error, changeset} ->
        render(conn, "new.html", changeset: changeset)
    end
  end
@KingKongDan
Copy link

I had the same problem. Thanks for addressing this issue.

@stavro
Copy link
Owner

stavro commented Sep 24, 2015

If you need to upload a file before the model is saved, then you cannot use the id of that model.

You should either generate a UUID for the file name or wait until after the model is saved.

Can you explain a little more about the workflow and model? There's probably a better structure to the files that we can come up with.

@MartinElvar
Copy link
Author

Hallo @stavro,

It's a new/create action in Phoenix, almost standard code generated by Phoenix. Basically i enter my form, and select images to my form.

How would i wait for the model to be saved, isn't the image saved as a part of cast_attachments? So i would have to manually call store?

@stavro
Copy link
Owner

stavro commented Sep 24, 2015

Using option (2) above (waiting until after the model is saved), you could:

#
# Model
#
def creation_changeset(params) do
  %Article{} |> cast(params, ~w(name), ~w())
end

def avatar_changeset(article, params) do
  article |> cast_attachments(params, ~w(avatar), ~w())
end

#
# Controller
#
def create(conn, %{"article" => article_params}) do
  # Add author.
  article_params = Dict.put_new(article_params, "admin_id", Plug.Conn.get_session(conn, :current_user))
  changeset = Article.creation_changeset(%Article{}, article_params)

  case Repo.insert(changeset) do
    {:ok, article} ->
      article = Article.avatar_changeset(article, article_params) |> Repo.update

      conn
      |> put_flash(:info, "Article created successfully.")
      |> redirect(to: admin_article_path(conn, :index))
    {:error, changeset} ->
      render(conn, "new.html", changeset: changeset)
  end
end

If you need it all saved with one call to the database then cannot use the id in the storage directory.

@MartinElvar
Copy link
Author

Thank you @stavro, i think that is a reasonable solution.

Maybe we should add this example to the documentation, i can imagine other could run into the same wall, specially if you are comming from carrierwave in Rails 😃

@tispratik
Copy link

Thanks for the solution. This works!

@gitviola
Copy link

Thanks!!!!

@Bounga
Copy link

Bounga commented Apr 1, 2017

What if Article.avatar_changeset(article, article_params) |> Repo.update fails? There's no transaction and no rollback.

@vantran
Copy link

vantran commented Jun 26, 2017

@Bounga maybe try something like this

Repo.transaction(fn ->
  with {:ok, product} <- Product.changeset(%Product{}, params) |> Repo.insert,
       {:ok, product_with_photo} <- Product.photo_changeset(product, params) |> Repo.update
  do
    {:ok, product_with_photo}
  else
    {:error, changeset} ->
      Repo.rollback(changeset)
      {:error, changeset}
  end
end)

That said, it would be so nice if it works like Paperclip allowing you to add the attachment at the same time as creating new object.

@suprnova32
Copy link

This information should definitely be in the documentation. I just spent a couple of hours scratching my head and looking for a solution. I don't mind that it doesn't work like Paperclip, but this use case is the most common, specially if you want to reuse a generic Upload schema for e.g. User, Photo, etc. In that case, the scope will not exist before the file is uploaded.

I just realized that you could scope the file to an existing field of the struct, but in any case, this should be documented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants