Goal
ユーザーの更新を実装する。
Dev-Environment
OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
PostgreSQL: postgres (PostgreSQL) 9.4.4
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.4
Phoenix Framework: v0.13.1
PostgreSQL: postgres (PostgreSQL) 9.4.4
Wait a minute
ここまでやってきた皆さんなら、
特に難しいことは何もない内容です。
特に難しいことは何もない内容です。
既に、どういったことをすべきか、
頭の中で予想ができているのではないでしょうか?
頭の中で予想ができているのではないでしょうか?
それではやってきましょう~
Index
UserUpdate
|> Add edit action
|> Create input form template
|> Add settings link
|> Add update action
|> Sharing input form template
|> Extra
|> Add edit action
|> Create input form template
|> Add settings link
|> Add update action
|> Sharing input form template
|> Extra
Add edit action
UserControllerへeditアクションを追加します。
ファイル: web/controllers/user_controller.ex
以下のアクション関数を追加して下さい。
以下のアクション関数を追加して下さい。
def edit(conn, %{"id" => id}) do
user = Repo.get(SampleApp.User, id)
user = Map.put(user, :password, SampleApp.User.decrypt(user.password_digest))
changeset = SampleApp.User.changeset(user)
render(conn, "edit.html", user: user, changeset: changeset)
end
Description:
passwordにはvirtual属性を付けているのでDBに値が入っていません。
そのためpassword_digestからパスワードを復号化して値を入力しています。
passwordにはvirtual属性を付けているのでDBに値が入っていません。
そのためpassword_digestからパスワードを復号化して値を入力しています。
テンプレートで入力されている値は、userの方になる。
なので、changesetの値を変更しても反映されない。
なので、changesetの値を変更しても反映されない。
ファイル: web/models/user.ex
暗号化している部分を修正します。
暗号化している部分を修正します。
defp encrypt(password) do
Safetybox.encrypt(password, :default)
end
Caution:
パスワードの暗号化ですが、利用する関数を間違えていたようです。
なので、暗号化する関数に修正を入れます。
パスワードの暗号化ですが、利用する関数を間違えていたようです。
なので、暗号化する関数に修正を入れます。
変更自体は、:defaultを加えただけです。
これは、デフォルトのSecretKeyとSaltKeyを利用して暗号化してくれます。
これを付けないと復号化の段階で:errorが返ってきてしまい復号化できませんでした。
これは、デフォルトのSecretKeyとSaltKeyを利用して暗号化してくれます。
これを付けないと復号化の段階で:errorが返ってきてしまい復号化できませんでした。
ファイル: web/controllers/session_controller.ex
defp authentication(user, password) do
case user do
nil -> false
_ ->
password == Safetybox.decrypt(user.password_digest)
end
end
Caution:
暗号化している関数を変えるとSafetybox.is_decrypted/2で評価できないため、
authentication/2の評価部分も変更します。
暗号化している関数を変えるとSafetybox.is_decrypted/2で評価できないため、
authentication/2の評価部分も変更します。
ファイル: web/models/user.ex
復号化する関数を追加します。
復号化する関数を追加します。
def decrypt(password) do
Safetybox.decrypt(password)
end
Create input form template
さて、edit.html.eexがないため現状だと表示ができません。
作成をしましょう。
作成をしましょう。
ファイル: web/templates/user/edit.html.eex
<h2>Edit UserProfile</h2>
<%= form_for @changeset, user_path(@conn, :update, @user), fn f -> %>
<%= if f.errors != [] do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<label>Name</label>
<%= text_input f, :name, class: "form-control" %>
</div>
<div class="form-group">
<label>Email</label>
<%= text_input f, :email, class: "form-control" %>
</div>
<div class="form-group">
<label>Password</label>
<%= text_input f, :password, class: "form-control" %>
</div>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
どこかで見たようなテンプレートですね・・・
えぇそうです。気のせいではありません。
えぇそうです。気のせいではありません。
new.html.eexとほぼ同一の内容になります。
(異なるのは指定しているアクションくらいですね)
(異なるのは指定しているアクションくらいですね)
そのため、後にeditとnewのテンプレートでフォーム部分を共通化させます。
さて後は、画面に表示したいのですが、
リンクがないのでURLを直接叩くしかないですね。
リンクがないのでURLを直接叩くしかないですね。
Add settings link
リンクを追加してしまいましょう。
ファイル: web/templates/user/show.html.eex
<h2>
<div class="gravatar" style="float: left; margin-right: 10px;">
<img src="<%= get_gravatar_url(@user) %>" class="gravatar">
</div>
<p><%= @user.name %></p>
<%= link "Edit", to: user_path(@conn, :edit, @user), class: "btn btn-default btn-xs" %>
</h2>
現在のテンプレートだと、非常にダサい表示になっていますね。
自分の投稿を表示する段になった時、キチンと修正しますのでご安心を・・・
何にせよ、これで入力フォームが表示できましたね。
自分の投稿を表示する段になった時、キチンと修正しますのでご安心を・・・
何にせよ、これで入力フォームが表示できましたね。
Add update action
入力した内容で更新するようにupdateアクションを実装します。
ファイル: web/controllers/user_controller.ex
以下のアクション関数を追加。
以下のアクション関数を追加。
def update(conn, %{"id" => id, "user" => user_params}) do
user = Repo.get(SampleApp.User, id)
changeset = SampleApp.User.changeset(user, user_params)
if changeset.valid? do
Repo.update(changeset)
conn
|> put_flash(:info, "User updated successfully!!")
|> redirect(to: user_path(conn, :show, id))
else
conn
|> put_flash(:error, "UserProfile updated is failed!! name or email or password is incorrect.")
|> redirect(to: user_path(conn, :edit, id))
end
end
ファイル: web/models/user.ex
before_updateの追加。
(これがないとパスワードが更新されない)
before_updateの追加。
(これがないとパスワードが更新されない)
before_update :set_password_digest
Description:
before_insertと動作は同じ。
こちらはupdate時に動作する。
before_insertと動作は同じ。
こちらはupdate時に動作する。
Sharing input form template
今回の最後は入力フォーム部分を共通テンプレートにすることです。
フォームテンプレートを作成します。
ファイル: web/templates/user/input_form.html.eex
ファイル: web/templates/user/input_form.html.eex
<%= form_for @changeset, @action, fn f -> %>
<%= if f.errors != [] do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below:</p>
<ul>
<%= for {attr, message} <- f.errors do %>
<li><%= humanize(attr) %> <%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="form-group">
<label>Name</label>
<%= text_input f, :name, class: "form-control" %>
</div>
<div class="form-group">
<label>Email</label>
<%= text_input f, :email, class: "form-control" %>
</div>
<div class="form-group">
<label>Password</label>
<%= text_input f, :password, class: "form-control" %>
</div>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
ファイル: web/templates/user/new.html.eex
<h1>Sign up</h1>
<%= render "input_form.html", changeset: @changeset,
action: user_path(@conn, :create) %>
ファイル: web/templates/user/edit.html.eex
<h2>Edit UserProfile</h2>
<%= render "input_form.html", changeset: @changeset,
action: user_path(@conn, :update, @user) %>
大分すっきりしました。
Extra
構造体の値を変更する方法・・・
ちょっと詰まったので方法をまとめておく。
ちょっと詰まったので方法をまとめておく。
iexの起動。
>iex -S mix
iex(1)> alias SampleApp.User
nil
構造体を定義。
(今回はUserModelの構造体を利用)
(今回はUserModelの構造体を利用)
iex(2)> user = %User{}
%SampleApp.User{__meta__: %Ecto.Schema.Metadata{source: "users", state: :built},
email: nil, id: nil, inserted_at: nil, name: nil, password: nil,
password_digest: nil, updated_at: nil}
構造体へのアクセス。
(値は入ってないですが・・・)
(値は入ってないですが・・・)
iex(3)> user.id
nil
失敗する値の変更。
iex(4)> user[:name] = "hoge"
** (CompileError) iex:5: cannot invoke remote function Access.get/2 inside match
(elixir) src/elixir_translator.erl:234: :elixir_translator.translate/2
(elixir) src/elixir_clauses.erl:26: :elixir_clauses.match/3
(elixir) src/elixir_translator.erl:18: :elixir_translator.translate/2
成功する値の変更。
しかしこれだと、nameに値を入れて再定義しているのと変わらない・・・
(他に値がある状態でこれをやるとnameにしか値が入らない。他はnil)
しかしこれだと、nameに値を入れて再定義しているのと変わらない・・・
(他に値がある状態でこれをやるとnameにしか値が入らない。他はnil)
iex(4)> user = %User{name: "hoge"}
%SampleApp.User{__meta__: %Ecto.Schema.Metadata{source: "users", state: :built},
email: nil, id: nil, inserted_at: nil, name: "hoge", password: nil,
password_digest: nil, updated_at: nil}
次の方法で行えば部分的に値を変更できる。
(Map.put/3を利用する)
(Map.put/3を利用する)
iex(5)> user = Map.put(user, :password, "hogehoge")
%SampleApp.User{__meta__: %Ecto.Schema.Metadata{source: "users", state: :built},
email: nil, id: nil, inserted_at: nil, name: "hoge", password: "hogehoge",
password_digest: nil, updated_at: nil}
大した内容ではないですが・・・
Speaking to oneself
さて、私の経験値/知識不足で余計な変更を強いてしまいました。
本当にすいません。
本当にすいません。
次の記事では、今回実装した更新処理に対して、
edit、updateへの画面遷移前に認可処理を実行するようにします。
edit、updateへの画面遷移前に認可処理を実行するようにします。
少々、面倒くさい内容ですが、
懲りずに付き合ってくだせー。
懲りずに付き合ってくだせー。