まさ@ブログ書き込み中

まさ@ブログ書き込み中

まさの旅、英語、プログラミング、プライベートについて、色々記録しています。

ついにRESTアクションを完成させるRailsチュートリアル10章

 

皆さんこんばんみ。「こんばんみ」的なワードを言う人にはロクな人がいないという偏見を持っているまさです。

 

僕にとっては「あるある」すぎてどうしようもないことの一つに、「今日終わらす」って言ってRailsチュートリアルを終われない日々が続いています。例えば、これが証拠です。

このツイートからわかるように、僕は頑張って8月31日に10章を終わらせると皆さん予想するでしょう。

 

おっ。イキってるイキってる。

 

 

では皆さん、お待ちかねの夜12時前のまさのツイートをどうぞ。

 

 

 

 

やっぱ簡単には10章倒せませんでした。

 

 

Railsチュートリアルはどんなに簡単そうに見える章でも全然簡単なことはなく、一章あたり2〜3時間かかるということはもう数少ないこの世の中の真理だと思っています。

 

さて、今回の10章は今まで実装してきたRESTアクションの残りの4つ、edit、update、index、destroyアクションを実装していきます。

 

また、これら4つのアクションとは別に認可システムについてまとめていきたいと思います。

 

 

編集して更新する(edit & update)

編集する

まずはeditアクションから。以下のように、editアクションが呼ばれたら編集すべきユーザーのIDをブラウザ側から受け取り、対応する編集画面をビューで表示させます。

app/controllers/users_controller.rb

  def edit
    @user = User.find(params[:id])
  end

 

 

更新する

次にupdateアクションです。以下のように、該当ユーザーを探し出し、update_attributesでそれぞれの値を更新し、最後にユーザープロフィールが画面にリダイレクトしています。もしうまくいかなければ、editページを表示します。

app/controllers/users_controller.rb

  def update
    @user = User.find(params[:id])
    if @user.update_attributes(user_params)
      flash[:success] = "Profile updated"
      redirect_to @user
    else
      render 'edit'
    end
  end

 

ちなみに、Userモデルのバリデーションとエラーメッセージのパーシャルにより、更新に失敗した場合はレンダリングされたeditページにしっかりエラーメッセージが表示されます。

 

 

すべてのユーザーを表示する(index)

次は全ユーザーを表示するindexアクションについてまとめていきます。といっても、indexアクションはとてもシンプルで、全てのユーザーをデータベースから取って来てビューで表示するだけです。

app/controllers/users_controller.rb

  def index
    @users = User.all
  end

 

「ビューで表示するだけ」と簡単に言ってのけましたが、ここで工夫できるポイントを本章では教えてくれます。はじめは、僕は「全てのユーザーが入った@usersをeach文で回しながら一つひとつ表示して行けばいい」と単純に思っていたのですが、Railsの自動変換機能をうまく活用して、リファクタリング(動作を変えずにコードを整理すること)できるようです。

 

例えば、元のコードが以下のようになっていたとします。

app/views/users/index.html.erb

<ul class="users">
  <% @users.each do |user| %>
    <li>
      <%= gravatar_for user, size: 50 %>
      <%= link_to user.name, user %>
    </li>
  <% end %>
</ul>

 

このコードの<li>タグの部分をrender呼び出しに置き換えて、パーシャルを作成します。

<ul class="users">
  <% @users.each do |user| %>
    <%= render user %>
  <% end %>
</ul>

 

このrender呼び出しはパーシャル(ファイル名の文字列)に対してではなく、user変数に対して実行しています。同時に、Rails_user.html.erbという名前のパーシャルを探しに行くので、そのパーシャルも作成しておきます。

 

実は@users.each do |user|の部分もまたリファクタリングできます。以下のようにまとめることができるのです。

<ul class="users">
  <%= render @users %>
</ul>

 

随分すっきりしましたね。本章によるとこのコードで

Railsは@users をUserオブジェクトのリストであると推測します。さらに、ユーザーのコレクションを与えて呼び出すと、Railsは自動的にユーザーのコレクションを列挙し、それぞれのユーザーを_user.html.erbパーシャルで出力します。

 

そういうことらしいです。いやあ、便利便利。

 

 

ユーザーを削除する(destroy)

さて、RESTアクションの最後をかざるのは、削除(destroy)です。このアクションもシンプルなものですが、その前に一つやらなければならないことがあります。

 

管理ユーザーだけが削除できるようにする

誰でも簡単にユーザーが削除されるなんてことはあってはいけません。

そのためにも、今までのユーザーが持つ属性に、もう一つ「admin」という管理者かどうかを判断するための属性を付け加えます。

 

まずはadminというカラムをブーリアン(trueかfalseを取る論理値)型で作成します。

$ rails generate migration add_admin_to_users admin:boolean

 

そして次に先ほどのユーザー一覧ページに、管理者からのみ削除リンクが見えるようにします。

app/views/users/_user.html.erb

<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
  <% if current_user.admin? && !current_user?(user) %>
    | <%= link_to "delete", user, method: :delete,
                                  data: { confirm: "You sure?" } %>
  <% end %>
</li>

 

destroyアクションを定義する

最後に、実際に動作するdestroyアクションを定義します。

  def destroy
    User.find(params[:id]).destroy
    flash[:success] = "User deleted"
    redirect_to users_url
  end

 

 

さて、これで10章で実装すべきRESTアクション(edit, update, index, destroy)についてのまとめは完了です。

 

ただし、これだけでは不十分なことがあります。それは、このままではedit, update, destroyアクションをどんなユーザーでもできてしまうということです。

 

編集(edit)に関しては/users/1/editとURLに打ち込んでしまえばあるユーザーの編集ページができてしまいますし、そこからフォームを送信すれば情報は更新(update)されてしまいます。また、ユーザーを削除(destroy)するのも本章によれば

ある程度の腕前を持つ攻撃者なら、コマンドラインでDELETEリクエストを直接発行するという方法でサイトの全ユーザーを削除してしまうことができるでしょう。

といった方法で実行できるみたいです・・・。

 

正直に言って、そんな方法知りませんでした。と言うことは認可システムを作らなくてもdestroyアクションに関しては俺みたいなレベルが悪意を持っても何もできないのですね。なんだろう・・・この気持ちは。悲しい

 

この悲しさを打ち消すように、どんな悪意のあるユーザーでもサイトをいじくりまわせないように、認可システムをはりきって実装していきます!

 

 

認可

まず、認可システムの「認可」の説明から。

ウェブアプリケーションの文脈では、認証 (authentication) はサイトのユーザーを識別することであり、認可 (authorization) はそのユーザーが実行可能な操作を管理することです。

 

つまり本項では、あるユーザーはそのユーザーの情報しか編集・更新できないようにしたり、管理者権限のあるユーザーしかユーザーを削除できないようにするためのお話をするということです。

 

ログイン必須にさせる

認可システムを使ってページを保護するには、beforeフィルターを使います。beforeフィルターとは「before_actionメソッドを使って何らかの処理が実行される直前に特定のメソッドを実行する仕組みです」。

 

具体的にはusers_controller.rbに以下のように書くのです。

before_action :logged_in_user, only: [:index, :edit, :update, :destroy]

logged_in_userメソッドは以下のように定義しています。

    # ログイン済みユーザーかどうか確認
    def logged_in_user
      unless logged_in?
        flash[:danger] = "Please log in."
        redirect_to login_url
      end
    end

 

つまりindex, edit, update, destroyアクションをする前に、ログイン済みユーザーかどうかを確認し、そうでなければログインページにリダイレクトさせているわけです。

 

正しいユーザーを要求する

もちろん、これだけでは十分ではありません。ログインしたらなんでもOKになってしまうなら「まさ」としてログインして「おーたつ」のデータを編集・更新してしまうことができてしまいます。

 

その対策として、二つ目のberofre_actionメソッドにcorrect_userメソッドを呼び出すようにします。

app/controllers/users_controller.rb

  before_action :correct_user,   only: [:edit, :update]
    # 正しいユーザーかどうか確認
    def correct_user
      @user = User.find(params[:id])
      redirect_to(root_url) unless @user == current_user
    end

 

こうすることで、ログイン済みかつ正しいユーザーのみ編集作業や更新作業ができるようになりました。

 

また、ユーザー削除に関しても管理者権限を持つユーザーか確かめる工夫をしなければなりません。先ほども述べましたが、本章によれば「ある程度の腕前を持つ攻撃者なら、コマンドラインでDELETEリクエストを直接発行するという方法でサイトの全ユーザーを削除してしまうことができる」のです。

 

よって、ここではadmin_userアクションをbeforeアクションとして設定します。

app/controllers/users_controller.rb

  before_action :admin_user,     only: :destroy
    # 管理者かどうか確認
    def admin_user
      redirect_to(root_url) unless current_user.admin?
    end

 

以上で認可システムは完了です!これで全くの素人レベルから、僕レベル、腕利きまで全てのユーザーが悪意を持って各アクションを操作できないようになりました。

 

 

感想

これまでもそうだったように、このまとめは完璧ではありません。本当は相当な量のテストや、フレンドリーフォワード機能、ページネーションについての説明がされています。なので、興味がある方はRailsチュートリアルの10章をしっかり学んだ方がいいと思います。

 

最終章まであと4章ありますが、一つひとつ分量は多いので、まだまだ道のりは長い気がします。一歩ずつ学んではまとめていきたいと思います。ではまた。