sugar-framework / sugar

Modular web framework for Elixir
https://sugar-framework.github.io/
MIT License
430 stars 29 forks source link

How to do file upload with Sugar-Framework? #85

Closed CharlesOkwuagwu closed 8 years ago

CharlesOkwuagwu commented 8 years ago

Please can you provide a guide on how to do file upload with sugar-framework.

Also, a sample with other common / everyday use-cases would be appreciated.

Thanks.

YellowApple commented 8 years ago

I was able to get this to work as expected with a few tweaks from your original gist.

In mix.exs:

defmodule SugarTest.Mixfile do
  use Mix.Project

  def project do
    [app: :sugar_test,
     version: "0.0.1",
     elixir: "~> 1.2",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps]
  end

  def application do
    [applications: [:logger, :sugar],  # Note the inclusion of :sugar here
     mod: {SugarTest, []}]
  end

  defp deps do
    [{:sugar, "~> 0.4.10"}]
  end
end

In config/config.exs (pretty typical):

use Mix.Config

config :sugar_test, SugarTest.Repos.Main,
  adapter: Ecto.Adapters.Postgres,
  database: "sugar_test_main",
  username: "user",
  password: "pass",
  hostname: "localhost"

config :sugar, router: SugarTest.Router

config :sugar, SugarTest.Router,
  https_only: false,
  http: [port: 4000],
  https: false

In lib/sugar_test/router.ex:

defmodule SugarTest.Router do
  use Sugar.Router
  plug Sugar.Plugs.HotCodeReload

  if Sugar.Config.get(:sugar, :show_debugger, false) do
    use Plug.Debugger, otp_app: :sugar_test
  end

  plug Plug.Static, at: "/static", from: :sugar_test

  # Uncomment the following line for session store
  # plug Plug.Session, store: :ets, key: "sid", secure: true, table: :session

  # Note the use of Plug.Parsers, and the use of the actual parsing
  # module (Plug.Parsers.MULTIPART) instead of an atom (:multipart)
  plug Plug.Parsers, parsers: [ Plug.Parsers.MULTIPART ]

  # Define your routes here
  get "/", SugarTest.Controllers.Main, :index
  post "/upload", SugarTest.Controllers.Main, :upload  # Note the POST
end

In lib/sugar_test/controllers/main.ex:

defmodule SugarTest.Controllers.Main do
  use Sugar.Controller

  def index(conn, []) do
    conn |> render
  end

  def upload(conn, _) do
    require Logger
    Logger.debug(conn |> inspect)

    case conn.params["image"] do
      %Plug.Upload{filename: filename, path: path} ->
        {:ok, image} = File.read path
        {:ok, file} = File.open filename, [:write]
        IO.binwrite file, image
        File.close file

        conn |> render(filename: filename)  # Note use of render, not resp
      nil ->
        raw conn |> resp(400, "image was not attached!")
    end
  end
end

In lib/sugar_test/views/main/index.html.eex:

<!doctype html>
<html lang="en-US">
<head>
  <title>main/index - SugarTest</title>
</head>
<body>
  <!-- Note the use of multipart/form-data, since that's what the parser expects -->
  <form enctype="multipart/form-data" action="/upload" method="post">
    <input type="file" name="image">
    <input type="submit">
  </form>
</body>
</html>

In lib/sugar_test/views/main/upload.html.eex:

<html><body>image[<%= @filename %>] was saved!</body></html>

Needless to say, there's a lot that needs documenting. Some of it is in the scope of Sugar (render v. resp, as well as some troubleshooting of MIME types and headers). Most is in the scope of Plug (in particular, Plug.Parsers and Plug.Upload; some of the docs there aren't entirely accurate).

I'd like to spin up a "Tips and Tricks" section of the Sugar docs site for these sorts of things. @slogsdon, if you've got any other suggestions for such a section (I'm already thinking of covering file uploads, JS/CSS asset management techniques, login/session management, and Phoenix/Sugar controller compatibility), I'm all ears.

CharlesOkwuagwu commented 8 years ago

Thanks.

YellowApple commented 8 years ago

No problem. Closing for now (since this is now addressed).