Creating Resources

We have a form that submits the data for a new user. It’s time to surface this new feature in our controller. This work should happen in our context. Let’s extend Accounts to create users. Open up lib/rumbl/accounts.ex and key this in:

 def​ create_user(attrs \ %{}) ​do
  %User{}
  |> User.changeset(attrs)
  |> Repo.insert()
 end

Take a look at the new short create_user function. We save our controller from this tiny bit of complexity. Our function has a short pipeline that starts with an empty user, applies a changeset, and then inserts it into the repository. The controller shouldn’t care about these short persistence details, but neither should the schema. We isolate change policy to a single place.

With the context ready, we can plug the changes into the controller. The question is where this work should happen. Recall our changes to the router.ex file, when we added the resources "/users" macro to router.ex to build a set of conventional routes. One new route maps posts to "/users" to the UserController.create action. Add a create function to UserController:

 def​ create(conn, %{​"​​user"​ => user_params}) ​do
  {​:ok​, user} = Accounts.create_user(user_params)
 
  conn
  |> put_flash(​:info​, ​"​​#{​user.name​}​​ created!"​)
  |> redirect(​to:​ Routes.user_path(conn, ​:index​))
 end

This pattern of code should be getting familiar to you by now. We keep piping functions together until the conn has the final result that we want. Each function does an isolated transform step. We call into our context first, registering our user in the application. Then, we take the connection and transform it twice, adding a flash message with the put_flash function, and then add a redirect instruction with the redirect function. Both of these are simple plug functions that we use to transform the connection, one step at a time.

Let’s examine one tiny detail here first. In some places, we’re going to need to refer to specific routes in the application. Generally, these get automatically generated, and you can access them from the YourApplication.Router.Helpers module. That’s a lot to type each time you need a route. In the auto-generated rumbl_web file, you’ll find the following snippet:

 def​ controller ​do
 quote​ ​do
 use​ Phoenix.Controller, ​namespace:​ RumblWeb
 
 import​ Plug.Conn
 import​ RumblWeb.Gettext
  alias RumblWeb.Router.Helpers, ​as:​ Routes
 end
 end

That’s the ticket. The line alias RumblWeb.Router.Helpers, as: Routes gives us exactly what we need. Here’s why.

Phoenix automatically generates the Helpers inside your router which contains named helpers to help developers generate and keep their routes up to date. Routes is a simple alias for Router.Helpers. That’s why you can get any route through Routes.some_path!

Getting back to work, we can try out our shiny new create action. Go visit http://localhost:4000/users/new:

images/src/ecto/new_user_form.png

And when we click Create User, we should be sent back to the users index page to see our inserted user as shown in the figure.

images/src/ecto/new_user_added.png

We still have work to do, though. Type a username that’s too long, and you’re greeted with Phoenix’s debug error page with the error “no match of right hand side value.”

We were expecting a result of the shape {:ok, user} but got {:error, %Ecto.Changeset{}}. Our validations failed, throwing an error page. To fix this problem, let’s check for both :ok and :error outcomes, showing validation errors upon failure. First we need to update our UserController to react to an invalid changeset:

 def​ create(conn, %{​"​​user"​ => user_params}) ​do
 case​ Accounts.create_user(user_params) ​do
  {​:ok​, user} ->
  conn
  |> put_flash(​:info​, ​"​​#{​user.name​}​​ created!"​)
  |> redirect(​to:​ Routes.user_path(conn, ​:index​))
 
  {​:error​, %Ecto.Changeset{} = changeset} ->
  render(conn, ​"​​new.html"​, ​changeset:​ changeset)
 end
 end

Easy enough. We insert the new user record and then match on the return code. On :ok, we add a flash message to the conn and then redirect to the user_path. That route takes us to the index action. On error, we simply re-render new.html, passing the conn and the changeset with the failed validations. We’ll use the Phoenix input fields to handle the problem.

Show the validation errors for each form input field in lib/rumbl_web/templates/user/new.html.eex, like this:

 <%=​ ​if​ @changeset.action ​do​ ​%>
  <div class=​"alert alert-danger"​>
  <p>Oops, something went wrong! Please check the errors below.</p>
  </div>
 <%​ ​end​ ​%>
 
 <div>
 <%=​ text_input f, ​:name​, ​placeholder:​ ​"​​Name"​ ​%>
 <%=​ error_tag f, ​:name​ ​%>
 </div>
 <div>
 <%=​ text_input f, ​:username​, ​placeholder:​ ​"​​Username"​ ​%>
 <%=​ error_tag f, ​:username​ ​%>
 </div>

The :action field of a changeset indicates an action we tried to perform on it, such as :insert. When we build a new changeset, the field is nil. If Phoenix renders our form with any action, we know the form action had validation errors. In our code, we first check for the existence of @changeset.action. If it’s present, we show a validation notice at the top of the form. Next, we use the error_tag function defined in lib/rumbl_web/views/error_helpers.ex to display an error tag next to each form input with the validation error for each field.

Now try again to submit your form with invalid fields:

images/src/ecto/user_validation_errors.png

Presto!

If you’ve not yet appreciated the Ecto strategy for changesets, this code should help. The changeset had all validation errors because the Ecto changeset carries the validations and stores this information for later use. In addition to validation errors, the changesets also track changes!

Remember, we don’t have to compromise our context by letting Ecto persistence details bleed through. We’re actually surfacing Phoenix form details because changesets implement the Phoenix.HTML.FormData protocol.

Let’s see how that works. Crack open IEx. If you have an old window already open, you can just recompile the current project:

 iex>​ recompile()
 iex>​ alias Rumbl.Accounts.User
 iex>​ changeset = User.changeset(%User{​username:​ ​"​​eric"​, ​name:​ ​"​​Eric"​}, %{})
 %Ecto.Changeset{changes: %{}, ...}
 
 iex>​ ​import​ Ecto.Changeset
 Ecto.Changeset
 
 iex>​ changeset = put_change(changeset, ​:username​, ​"​​ericmj"​)
 %Ecto.Changeset{changes: %{username: "ericmj"}, ...}
 
 iex>​ changeset.changes
 %{username: "ericmj"}
 
 iex>​ get_change(changeset, ​:username​)
 "ericmj"

Now you have a more complete picture. Ecto is using changesets as a bucket to hold everything related to a database change, before and after persistence. You can use this information to do more than see what changed. Ecto lets you write code to do the minimal required database operation to update a record. If a particular change must be checked against a database constraint, such as a unique index, changesets do that. If Ecto can enforce validations without hitting the database, you can do that too. You’ll explore the broader changeset API, validations, and strategies as we build out the rest of our application.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.47.218