1
votes

I'm trying to get a basic database set up in Phoenix, and want a model similar to the Video model from the Phoenix Ecto tutorial--basically using has_many--along with uploading similar to the File Uploads tutorial.

In my case I'm wanting to allow users to upload multiple .csv's, which are added to the db. Right now I'm limiting one .csv upload on creating a user to focus on getting that much working. I'm also not concerned what I'll store--right now the path to where the .csv resides just to start out simple. In the future I might want to directly store the csv data.

I am getting the %Plug.Upload in the user_params, and am able to write that to a file, but I'm not sure how to add the path from that into the db.

Latest error is in users/model (see below).

Here's what I have so far:

models/csv.ex

defmodule Test.CSV do
  use Test.Web, :model

  schema "csvs" do
    field :csv, {:array, :string}
    belongs_to :user, Test.User

    timestamps()
  end
  ...

models/user.ex

defmodule Test.User do
  use Test.Web, :model

  schema "users" do
    field :uname, :string
    field :group, :string

    has_many :csvs, Test.CSV
    timestamps()
  end
  def changeset(struct, params \\ %{}) do
    struct
    |> cast(params, [:uname, :group])
    # Wasn't sure what else to do here. Currently throws an error at this line with 'protocol Enumerable not implemented for %Plug.Upload{...filename...etc.}'. 
    # I figure I probably need to specify somewhere that I only want a certain field, like the path, but I'm not sure how to do that.
    |> cast_assoc([:csvs])
    |> validate_required([:uname, :group])
  end

user_controller.ex

...
def create(conn, %{"user" => user_params}) do
  changeset = User.changeset(%User{}, user_params)

  case Repo.insert(changeset) do
    {:ok, _user} ->
      conn
      |> put_flash(:info, "User created successfully.")
      |> redirect(to: user_path(conn, :index))
    {:error, changeset} ->
      render(conn, "new.html", changeset: changeset)
  end
end
...

user/forms.html.eex

<%= form_for @changeset, @action, [multipart: true], fn f -> %>
...
<%= inputs_for f, :csvs, fn i -> %>
  <div class="form-group">
  <label>CSV</label>
  <%= file_input i, :csv, class: "form-control" %>
  </div>
<% end %>
...
<% end %>

I also made sure that I did ecto.migrate and added the csvs table to the db.

def change do
  alter table(:users) do
    # Hmm, not sure if that should have been a :map.
    add :csvs, :map
  end
end

Then there's whether I should be using a nested association (the right term?) or not, or going the route of embedded schema. Needless to say, I am new to Phoenix, and would greatly appreciate any thoughts on how to solve this. Thanks!

1
After having similar Problems, that post gave me the proper background to help myself. blog.plataformatec.com.br/2015/08/… I think you should preload your csvs. Please show how your form beginning looks. I think it should be something like inputs_for f, :csvs, fn i -> - Joe Eifert
Well I've been poking around a lot of different articles and I'm unsure of the correct way I should go about it. I love a lot of the aspects of Phoenix, but this part seems like there's way too many potential solutions and it's hard to know which one to pursue. It'd be nice if there was an easy built-in way of handling %Plug.Upload{} that I'm overlooking. @Johannes, I tried using inputs_for (will edit my post to reflect this), but now the file_input form doesn't show up any more. Not sure how to proceed from there... maybe it's covered in that article you linked. - Aristoatle

1 Answers

0
votes

I'm really surprised how did you figure out to use such notation in schema {:array, :string}... The simplest solution for your case is to have has_many relation between User and CSV and this part you have valid.

Now it's up to you how you want to store informations about these CSVs in database- if you deal with Plug.Upload it's nothing more than file, but you would need to customize how Ecto would handle this type with Ecto.Type, because file is a complex structure.

Another option is to parse that CSV file and then put it into CSV table and associate it with User, propably the best way to do so is to use CSV plugin to read the content of the file - CSVLixir or any other - and combine it with put_assoc.

Personally I wouldn't store in the database whole file, but references to it eg. filename and content if needed.