チーム開発の難しさ

おはようございます。

世間は今日からGWですね。

今日はチームでの開発について思うことを書いてみました。

1.この記事を書こうと思ったきっかけ

僕は一昨日まで社外の研修でシステム開発における要件定義から内部設計までをやっていました。

僕のチームは大学時プログラミングをやっていた人も多く、レベルは決して低くありませんでした。

しかし、いざ終わって各チームの発表を見てみると「あれ、うちらのチーム一番ひどくね?」という感想を抱きました。

個々のレベルは決っして低くないのに何故こんな結果になったのだろうと言う疑問がこの記事を書くきっかけになりました。

2.情報共有の大切さ

1つ目の理由としましては僕たちのチームは情報の共有が出来ていなかったのかなと言うのが思うところです。

今回のチーム開発は時間制限があったので各々で役割分担をして取り組むことにしました。

それは決して悪いことだとは思わないのですが、僕たちはゴールの共有を出来ていなかったのです。

その結果、出来たものを繋ぎ合わせてもあまり整合性が取れていないことがありました。

また、最初にやることを決めその後進捗確認をしなかったため、結果お互いが今どこまでやっているのかが分からないこともありました。

まだ終わってないことをやっていたら「私もそれ今やってるよ」と言われたこともありました(笑)

(もちろん確認していない僕にも非は十分ありました。)

3.優先順位

次に大切だと感じたことは優先順位です。

今回のチーム開発では先ほども申し上げましたが、時間制限がありました。

学生の時は時間は無限かと錯覚するほどあったので思いついたこと全てをやることができたのですが時間制限があるとそういう訳にもいきません。

一回の講義につき2個以上の成果物を求められるのですが、1つ目の成果物の細部に拘りすぎるあまり2個目の成果物が中途半端になってしますことがありました。

まず、すべての成果物の大枠を完成させそれから細部を詰めて行くべきでした。

4.まとめ

チーム開発を通じて、ゴールの共有、互いの進捗状況の確認、やるべきことの優先順位付けの大切さを学ぶことができました。

また、これらはチーム開発でもなく上司⇄僕自身、クライアント⇆僕自身でも必要になってくると思うので意識していきたいです。

webページってどうやって表示されるの?

おはようございます。

今日はwebページがどういった流れで僕たちの普段使ってるブラウザに表示されているか述べていきたいと思います。

1.見たいサイトのURLを打ち込む

これはおそらく誰しもがやっていることだと思います。

これをしないと始まりません。

urlはhttps://~となっていますが、このhttpsと言うのがプロトコルと言うものでして、簡単に言いますと「今から送るURLのHTMLを頂戴」と言うリクエストを表しています。

2.ドメイン名を基にIPアドレスを特定する

httpリクエストを送り、これから対象のHTMLファイルを探しに行きますが、ファイルがあるWEBサーバーは世界にたくさんあります。

その中からどうやって特定するのかと言うとURLのドメイン名から特定します。

(https://~の~から最初のスラッシュです)

このドメイン名は住所みたいなものです。一意です。

一意ならそのまま探せばいいんじゃないの?と思われる方もいるかもしれませんが、コンピューターと言うのは全てを0と1で表しますよね?

つまり、このドメイン名をコンピューターが読み取れるように変換する必要があります。

その役目を果たしてくれるのがDNSサーバーです。

3.webサーバーからファイルをもらい壁画する

そうして対象のwebサーバーにたどり着いたら後は対象のファイルをもらい帰るだけです。

ファイルをもらったらブラウザがHTMLの内容を読み込み、表示します。

以上です。

ありがとうございました!!!!

Rails tutorial 14章 要約 〜ユーザーをフォローする〜

おはようございます!!

春は出会いと別れの季節ですね!!!!どんな出会いがあるか今からワクワクです!

さて、今日はrails tutorial最終章をまとめたいと思います。

ぜひ、最後まで見ていってください

今回の章では本家Twitterでも実装されているフォロー機能を実装していきます。

今回のように多対多の場合は中間テーブルというのを作成します。

(前回やっていた投稿機能は1対多でした。)

なぜかといいますと、わざわざfollowerモデルを作り、そこに再びユーザーの情報を保存するのは無駄が多いからです。

その為、フォローしたユーザーのid、フォローされたユーザーのidのみを保存するテーブルを作成します。

これをもとにモデルを作成します。

rails g model Relationship follower_id:integer followed_id:integer
rails db:migrate

 続いてuser/relationshipsの関連付けを行います。

投稿機能の時は以下のように記述しました。

user.rb

has_many :microposts

micropost.rb

belongs_to :user

しかし、今回は作成したモデル名がrelationshipsである為、active_relationshipと記述してもrailsは見つけてくれません。

その為、クラス名を明示する必要があります。

また、belongs_toも投稿機能の時はmicropostにuser_idが含まれていたのであのような記述で大丈夫でしたが、今回はフォローするユーザーをfollowed_idから特定しなければならないのでこれも明示する必要があります。

以上より、下記のようになります。

user.rb

has_many :active_relationships, class_name: "Relationship",
                 :forien_key "followed_id",
                 dependent: :destroy 

relationship.rb

belongs_to :follower :class_name: "User"
belongs_to :followed :class_name: "User"

次にfollowingとfollowerの実装をします。

これには、has_many throughを使います。

user.rb

has_many :following through: :active_relationship, source: :followed

followedでなく、followingを使ったのは複数形にした時違和感が出ないようにする為です。

フォロワーについても同じように実装します。

user.rb

has_many :passive_relationships, class_name: "Relationship",
                  foreign_key: "folloed_id",
                  dependent: :destroy
has_many :follower through: :passive_relationship, source: :follower

次にインターフェイスを作成していきます。

followingとfollowerのそれぞれの一覧を表すページを作ります。

ルーティングは以下のようになります。

routes.rb

resources :users do
  members do 
    get :followers、 :following
  end
end

usersの内側に入れることによってusers/1/followerのようなurlを作成することができます。

次にfollowing,followerの数を表示するパーシャルを作成します。

_stat.html.erb

<% @user ||= current_user %>
<div class="stat>
  <a href="<%= following_user_path(@user)%>">
    <strong id="following" class="stat">
      <%= @user.following.count %>
    </strong>
    following
  </a>
  <a href="<%= follower_user_path(@user)%>">
    <strong id="followers" class="stat">
      <%= @user.followers.count %>
    </strong>
    followers
  </a>
</div>

@user.following(followers).countでそれぞれの数をカウントしています。

このパーシャルをホームページに差し込みます。

次にfollow,unfollowボタンの実装をします。

_folow_form.html.erb

<% unless current_user(@user) %>
  <div id="follow_form>
    <% if current_user.following? %>
      <%= render 'unfollow' %>
    <% else %>
      <%= render 'follow' %>
    <% end %>
  </div>
<% end %>

また、followとunfollowの壁画するには新たなルーティングを実装する必要が出てきます。

routes.rb

resources :relationships、    only:[:create、destory]

次にパーシャルを作成します。

_follow.html.erb

<%= form_for(current_user.active_relationships.build) do |f| %>
  <div><%= hidden_field_taag :followed_id, @user.id %></div>
  <%= f.submit"Follow", class:"btn btn-primary" %>
<% end %>

_unfollow.html.erb

<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id)
              html: {method: :delete} do |f| %>
  <%= f.submit "Unfollow", class:"btn" %>
<% end %>

_follow.html.erbは新たなリレーションシップを作成し、_unfollow.html.erbはfollowed_idからフォローしているユーザーを探し出し、削除しています。

次にfollowing,followersアクション,ビューを実装していきます。

users_controller.rb

before_action :logged_in_user, only:[...:following,:followers]
.
.
.
def following
  @title = "Following"
  @user = User.find(params[:di])
  @users = @user.following.page(params[:page])
  render 'show_follow'
end

def followers
  @title = "Followers"
  @user = User.find(params[:id])
  @users = @user.followers.page(params[:page])
  render 'show_follow'
end

show_follow.html.erb

<% provide(:title, @title) %>
<div class="row">
  <aside class="col-md-4">
    <section class="user_info">
      <%= gravatar_for @user %>
      <h1><%= @user.name %></h1>
      <span><%= link_to "view my profile", @user %></span>
      <span><b>Microposts:</b> <%= @user.microposts.count %></span>
    </section>
    <section class="stats">
      <%= render 'shared/stats' %>
      <% if @users.any? %>
        <div class="user_avatars">
          <% @users.each do |user| %>
            <%= link_to gravatar_for(user, size: 30), user %>
          <% end %>
        </div>
      <% end %>
    </section>
  </aside>
  <div class="col-md-8">
    <h3><%= @title %></h3>
    <% if @users.any? %>
      <ul class="users follow">
        <%= render @users %>
      </ul>
      <%= paginate %>
    <% end %>
  </div>
</div>

followers、followingの大まかな枠組みは同じであるため作成するviewは1つで変数で表示を変えています。

最後にホームページに自身とフォロワーのマイクロポストを表示するようにします。

上記を満たすsql文は以下のようになります。

SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>

whereの後に続く文はフォロワーに含まれているか、自分自身のidという意味です。

よって次のようになります。

Micropost.where("user_id IN (?) OR user_id = ?", following_ids,id)

しかし、これだとデータが膨大になった時に動作が遅くなってしまうので、リファクタリングする必要があります。

疑問符に挿入する値を予め明記することを意識したリファクタリングすると以下のようになります。

following_ids = "SELECT followed_id from relationships
                 WHERE followed_id = :user_id"
Micropost.where("user_id IN (#{following_ids}) 
OR user_id = :user_id, user_id:id")

これで完成です。

また、フェードを実装したのでrails tutorial自体終わりました!!!!

これからはtutorialをやってみて、自分に足りないところがわかったのでそこを埋めていきたいと思います!

時間の使い方について

おはようございます!!!

kesuikeです。

今日は時間の使い方について僕なりの考え方について述べていきたいと思います。

1.時間は有限なので節約できるものはする

僕が最近意識しているのは上記のことです。

つい最近社会人になったばかりですが、思った以上に時間がなくてびっくりしています。

そこで僕は今下記のことを実践して時間を節約しています。

具体的には

・ご飯は基本的には作らないで買う。

・洗濯をなるべくしないようにワイシャツや下着を一週間分用意

また、通勤も無駄だと感じているので会社の近くに引っ越すことも考えています。

2.時間は有限なのでやることの優先順位をつける

次に優先順位をつけることについてです。

節約してもやはり時間は限られています。

そこで僕は優先順位をつけるようにしています。

具体的には

仕事 → 勉強 → 趣味

と大まかに順位をつけここから更に細分化してタイムスケジュールに落としています。

以上が僕の時間についての考え方です。

いや〜社会人になったばかりですけど、本当に大変ですね。

仕事しながら勉強されている方には尊敬しかありません。

では、失礼します〜

Rails tutorial 13章 ~Micropost実装~

おはようございます。

keuikeです。

今日から待ちに待ったRails tutorial 13章 マイクロポストの実装に入っています。

ようやく、Twitterっぽくなってきてテンション上がってます。笑

では、今日もいつも通り要約をまとめたのでご覧ください。

1.Micropostモデル

早速モデルから作成していきます。

Micropostモデルには投稿内容を表すcontent属性とuser_id属性の2つを実装します。

Rails g model Micropost content:text user:references

このuser:referencesがポイントでこの方のおかげで自動的にインデックスと外部キー参照付きのuser_idが追加され、UserとMicropotが関連つけする準備ができました。

それではmigrateをしましょう。

次にバリデーションんを実装します。

以下の2つです。

1.user_idが空になっていない。

2. contentが空でないかつ文字数が140字以内である

これらをMicropost.rbに書いていきます。

以上でバリデーションは完了です。

次にモデル間の関係について説明しようと思います。

今回のアプリでは一人のユーザーがたくさんの投稿をできるようになっています。

そういった場合、各モデルには次のように表す必要があります。

User.rb

has_many :microposts

Micropost.rb

belongs_to :user

これで関連付けは完了です。

また、関連付けが完了したので

micropost.create
micropost.new

と言った記述は

user.microposts.create
user.microposts.build

と言う感じに直す必要があります。

次にMicropostを新しい順に並び替えます。

その為に「desc」というものを使います。

これは降順(descending)の意を用い新しいものから並べます。

micropost.rb

default_scope -> { order(created_at: :desc) }

また、userが削除された時投稿も削除されていなければなりません。

User.rb

has_many :microposts, dependent: :destroy

ここまで出来ましたらUserの詳細ページに投稿を載せていきます。

ここで気をつけることは、users_controllerのshowアクションでmicropostインスタンスを取得することです。

Users_controller.rb

def show
  @microposts = @user.microposts.page(params[:page])
end

users/show.html.erb

<div class="col-md-8>
  <% if @user.microposts.any? %>
    <h3>Microposts(<%= @user.microposts.count %>)</h3>
    <ol class="microposts>
      <%= render @microposts %>
    </ol>
    <%= paginate @microposts %>
  <% end %>
</div>

_micropost.html.erb

<li id="micropost-<%= micropost.id %>">
  <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
  <span class="user"><%= link_to micropost.user.name, micropost.user %></span>
  <span class="content><%= micropost.content %></span>
  <span class="timestamp">
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
  </span>
</li>

<%= render ‘microposts’ %>で各投稿を表示するパーシャルを呼び出します。

最後にホーム画面に投稿を表示させるようにします。

その為にやるべきことは以下の3つです。

1.Userモデルにfeedメソッドを書く。

2.feed_itemsのインスタンス変数を加える

3.フィード用のパーシャルを作成する。

次のようになります。

User.rb

def feed
  Micropsost.where("user_id = ?", user.id)

static_pages_controller.rb

def home
  if logged_in?
    @microposts = current_user.microposts.build
    @feed_items = current_user.feed.page(params[:page])
  end
end

_feed.html.erb

<% if @feed_items.any? %>
  <ol class="microposts>
    <%= render @feed_items %>
  </ol>
  <%= paginate @feed_items %>
<% end %>

これでフィードも作成完了しました。

ふぅ〜。

疲れました笑

最後まで見て頂きありがとうございました!!!

ここ違うよ、もっとこうした方がいいよなどの意見があればぜひコメントお願いします!!!!

Rails tutorial 12章 ~パスワードリセット~

こんにちは。

社会人一年目のkesuikeです。

相も変わらずtutorialを進めております。

大学も卒業したので、近々学生の間にやったほうがいいことをまとめてブログにUPしようと思います。

さてさて、今日はRails tutorial 12章の要約を載せていきたいと思います。

正直僕はここはすごい嫌いですが、ここを乗り越えれば楽しい楽しい投稿やいいねなどのよりTwitterっぽい実装に入れるので頑張ります。

パスワードリセットの大まかな手順

1.ユーザーがパスワード再設定をリクエストすると送られてきたメールアドレスからユーザーを特定する。

2.再設定用のトークンとリセットダイジェストを作成する

3.リセットダイジェストはDBに保存し、トークンはユーザーに送るメールのリンクに仕込む

4.ユーザーがリンクをクリックしたら、メールアドレスをキーにユーザーを探し、リセットダイジェストと比較。

5.認証されたら再設定用のフォームを表示する

この流れで実装していきたいと思います。

最初にPassword_resetのモデリングからやります。

今回はパスワードを設定するためのモデルが必要なのでnew,editの2つのアクションを作成します。

rails g controller PasswordResets new edit --no-test-framework

次にルーティングの設定も行います。

routes.rb

resources :password_resets, only:[:new, :create, :edit, :update]

これで名前付きルートも使えるようになります。

ログインフォームにパスワードを忘れた時用のリンクを作ります。

sessoins/new.html.erb

<%= f.label :password %>
      <%= link_to "(forgot password)", new_password_reset_path %>
      <%= f.password_field :password, class: 'form-control' %>

これでパスワード再設定画面へのリンクの作成が完了しました。

次にメールアドレス有効化の時と同じようにトークンとダイジェストの設定を行います。

今回はダイジェストだけでなくメールを送信した時間を記憶するreset_sent_atも追加します。

リンクへのアクセスには時間制限を設けたいからです。

カラムの追加のコマンドは何回も紹介しているので割愛します。

実際にメールアドレスを入力するフォームを作成します。

password_resets/new.html.erb

<% provide(:title, "Forget password" %>
<h1>Forget password</h1>

<div class="row>
  <div class="col-md-3 col-md-offset-3">
    <%= form_for(:password_resets, url:password_resets_path) do |f| %>
      <%= f.email %>
      <%= f.email_field:email, class:'form-control' %>

      <%= f.submit"Submit" class:"btn btn-primary" %>
    <% end %>
  </div>
</div>

ここから受けっとたメールアドレスからユーザーを探し出します。

ちなみに、、、気づいた方もいると思いますがこのフォーム、ログインフォームとかなり似ていますよね。後々リファクタリングをした方が良さそうです。

さてさて、フォームから受け取ったメールアドレスからユーザーを探すのですが、これはcreateアクションの出番ですね。

Password_resets_controller.rb

def create
  @user = User.find_by(email: [:password_resets][:email].downcase)
  if @user
    @user.create_reset_digest
    @user.send_password_reset_email
    flash[:info] = "Email sent with password reset instructions"
    redirect_to root_url
  else 
    flash.now[:danger] = "Email address not found"
    render 'new'
  end
end

Userが見つけ出せたら2つのメソッドを実行してroot_urlにリダイレクトするって感じですね。

次にその2つのメソッドを定義しましょう。

User.rb

attr_asseccor :reset_token
.
.
.
def create_reset_digest
  self.reset_token = User.new_token
  update_attribute(reset_digest, User.digest(reset_token))
  update_attribute(reset_sent_at, Time.zone.now)
end

def send_password_reset_email
  UserMailer.password_reset(self).deliver_now
end

1つ目のメソッドはトークンを作成して、それをダイジェストにしたものと、メールを送信した時間を保存しています。

次にメールの設定ですが、ここもアカウントの有効化と酷似しているので割愛させていただきます。

続いてリンクをクリックして再設定用のフォームを表示させます。

パスワードの登録のみですのでフォームを作るのは比較的簡単だと思います。

ただ、今回メールアドレスをキーにしてユーザーを探し出しているためupdateアクションでもメールアドレスが必要になります。

そのため、hidden_field_tagというヘルパーを新たに使います。

edit.html.erb

<% provide(:title, 'Reset password') %>
<h1>Reset password</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user, url: password_reset_path(params[:id])) do |f| %>
      <%= render 'shared/error_messages' %>

      <%= hidden_field_tag :email, @user.email %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation, class: 'form-control' %>

      <%= f.submit "Update password", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

これでupdateアクションでもメールアドレスが利用できます。

早速このフォームを描画と言いたいところですが、その前に有効化や認証を実装しなければいけません。

これらはeditアクションの前、つまりbefore_actionで実装します。

password_resets_controller.rb

before_action :get_user, only:[:edit, :update]
before_action :valid_user, only:[:edit, :update]
.
.
.
def get_user
  @user = User.find_by(email: params[:email])
end

def valid_user
  unless (@user && @user.activated? &&
          @user.autenticated?(:reset, params[:id]))
    redirect_to root_url
  end
end

これらを通過した人のみ、パスワードを変更できます。

パスワードの更新の際には以下の項目をチェックします。

1.パスワード再設定の期限

2.無効なパスワードなら保存しない

3.空になっていないか

4.正しければ更新する

以上の項目を気をつけ実装します。

Password_resets_controller.rb

before_aciton check_expiration, only:[:edit, :update] #1番の対応
.
.
.
def update
  if params[:user][:password].empty?  #3番の対応
    @user.errors.add(:password, :blank)
    render 'edit'
  elsif @user.update_attribute(user_params) #4番の対応
    log_in @user
    flash[:success] = "Password has been reset"
    redirect_to @user
  else
    render 'edit' #2番の対応
  end
end

def user_params
  params.require(:user).permit(:password, :password_confirmation)
end

def check_expiration
  if @user.password_reset_expired?
    flash[:danger] = "Password reset has expired."
    redirect_to new_password_resert_url
  end
end

User,rb

def password_reset_expired?
  reset_sent_time < 2.hours.ago
end

以上でパスワード再設定の解説はおしまいです。

最後まで見ていただきありがとうございました。

Rails tutorial 11章 〜アカウントの有効化〜

おはようございます。

本日はrails tutorial 11章 アカウントの有効化についてまとめてみました。
ぜひ、最後まで見ていってください。

アカウント有効化の意義

アカウントの有効化を実装することで登録されたメールアドレスは本当にそのユーザーのものなのかが確認できるようになります。

実装するための大まかな手順

  1. 初期登録が行われた時に有効化トークンとそれに対応する有効化ダイジェストを作成する
  2. 有効化ダイジェストはDBに保存しておき、有効化トークンはユーザーに送信するメールに仕込んでおく
  3. ユーザーがメールアドレスのリンクをクリックしたら、メールアドレスをキーにトークンとダイジェストを比較し認証する

以上の流れになります。
順番に見ていきます。

有効化トークン、ダイジェストの作成

まず、最初にControllerの作成を行います。

rails g controller AccountActivation

これでControllerの作成は完了です。

ユーザーがメールアドレスのリンクをクリックして有効化をする際、次のurlが含まれます。

edit_account_activaton_url(activation_token)

ですので、先に名前付きルートを作成します。
routes.rb

・
・
・
resources :account_activations, only: [:edit]

次にUserモデルにカラムを追加していきますが、
仮想化した属性を使いハッシュ化した文字列をDBに保存していきます。
また、activated属性で論理値を取るようにします。

以上を踏まえて次のコマンドを実行します。

rails g migration add_activation_to_users \
activation_digest:string activated:boolean activated_at:datetime
rails db:migrate

これで変更が反映されました。

続いて、ユーザーが作成される前に有効化トークン、ダイジェストが作成されるようにbefore_createメソッドを設定します。
before_saveが保存される直前に実行されるのに対してこちらは作成される直前に実行されます。

User.rb

attr_accessor :activation_token
before_create :create_activation_digest
・
・
・
def create_activation_digest
self.activation_token = User.new_token
self.activation_digest = User.digest(activation_token)
end

アカウント有効化のメール送信

メイラーはrails gで簡単に作成できます。

rails g mailer UserMailer account_activation password_reset

password_resetは12章で使います。

次にテンプレートビューをカスタマイズします。

application_mailer.rb

class ApplicationMailer < ActionMailer::Base
default from: <u>"noreply@example.com"</u>
layout 'mailer'
end

user_mailer.rb

class UserMailer < ActionMailer::Base
<u>def account_activation
@user = user
mail to:user.email, subject: "Account activation"
end</u>
end

有効化トークンをメールに仕込む

パスワードだったら入力してそれをrailsがダイジェストに変換して認証を行なっていました。
しかし、今回はURLをクリックするだけです。
つまり、URLに何かしらの工夫をしなければなりません。

URLには認証のためのトークンとユーザーを見つけ出すためのメールアドレスを仕込みます。

<%= edit_account_activation_url(@user.activate_token, email:@user.email) %>

これによって発行されるURLは以下のようになります。

account_activations/q5lt38hQDc_959PVoo6b7A/edit?email=foo%40example.com

次にcreateアクションを変更して新規登録時にメールが送信されるようにします。

users_controller.rb

def create
@user = User.new(user_params)
if @user.save
UserMailer.account_activation(@user).deliver_now
flash[:info] = "Please check your email to activate your account."
redirect_to root_url
else
render 'new'
end
end

アカウントの有効化

これからaccount_activations_controllerのeditアクションを書いていきますが、その前にauthenticated?の文多少変更します。

現在のauthenticatedメソッドはこちらです。

User.rb

# トークンがダイジェストと一致したらtrueを返す
def authenticated?(remember_token)
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

このままだとremember_tokennしか引数として受け取れませんのでここを変数に変える必要があります。
これにはsendメソッドを使います。
sendメソッドは渡されたオブジェクトにメッセージを送ることによって呼び出すメソッドを動的にすることができます。

def authenticated?(attribute, token)
digest = send("#{attribute}_digest")
return false if digest.nil?
BCrypt::Password.new(digest).is_password?(token)
end

これでeditアクションを呼び出す準備が整いました。
account_activations_controller.rb

def edit
user = User.find_by(email: params[:email])
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.update_attribute(:activated,    true)
user.update_attribute(:activated_at, Time.zone.now)
log_in user
flash[:success] = "Account activated!"
redirect_to user
else
flash[:danger] = "Invalid activation link"
redirect_to root_url
end
end
end

以上で、アカウント有効化の大まかな手順の説明は終わります。
ありがとうございました。

Rails tutorial 10章 ユーザー削除の実装をしてみたよ

みなさんおはようございます。

つい先日rails tutorial 10章終了しました。
都度、ブログにまとめていたら大分時間がかかってしましました。笑

今日は最後のUserリソースの最後の部分、destroyアクションについてまとめたのでぜひ最後まで見ていってください。

実装計画

  1. ユーザー一覧ページを作る
  2. 管理者を一人設定する
  3. ユーザー一覧のページで管理者が閲覧している時のみ削除ボタンを実装する
  4. ユーザーを削除したら、ユーザー一覧ページへ戻る

以上の手順で進めていきます。

ユーザー一覧ページの作成

ユーザー一覧ページを作成します。
また、ここではペジネーションも実装します。

users_controllerで全員のuserを取得、viewで表示という流れになります。

users_controller.rb

  PER = 30
def index
@users = User.page(params[:page]).per(PER)
end

通常モデル内の全てのデータを取得する際はallを用いますが、今回はペジネーションを使うので上記のようになります。

次にviewの作成です。

index.html.erb

<%= provide(:title,"All users") %>
<h1>All users</h1>
<ul class="users">
<%= paginate @users %>
<%= @usres.each do |user| %>
<li>
<%= gravatar_for user, size: 50 %>
<%= link_to user.name, user %>
</li>
<% end %>
<%= paginate%>
</ul>

これで一覧ページは完成です!!!
と、言いたいところですが、コンパクトなviewにするためにrenderを使い、リファクタリングをします。

index.html.erb

<%= provide(:title,"All users") %>
<h1>All users</h1>
<ul class="users">
<%= paginate @users %>
<%= render @users%>
<%= paginate @users%>
</ul>

_users.html.erb

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

renderではファイル名でなく、変数名を指定していますが、これはRailsがuserのコレクションだなと推測し、呼び出してくれます。
これで一覧ページは完成です。

管理者の設定

管理者を実装するためにUserモデルにadminカラムを追加します。
これはuserが管理者かどうかを真偽値で返します。

iterm

rails g migration add_admin_to_users admin:boolean

次にadminのデフォルト値の設定をします。
管理者は一人だけなのでデフォルト値はfalseにします。

add_admin_to_users.rb

class AddAdminToUsers < ActiveRecord::Migration[5.0]
def change
add_column :users, :admin, :boolean, default: <u>false</u>
end
end

migrateします。

rails db:migrate

最後にテストユーザーを一人管理者にします。

seed.rb

User.create!(name:  "Example User",
email: "example@railstutorial.org",
password:              "foobar",
password_confirmation: "foobar",
admin: true)

削除ボタンの実装

次に削除ボタンの実装をします。

現在のユーザーが管理者の時のみ削除ボタンを付けるのでif文を使います。

_user.html.erb

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

これで現在のユーザーが管理者の時のみ削除ボタンが現れます。

destroyアクションの実装

destoroyアクションを実装します。
最初にこのアクションはログインしていてかつ管理者でなくてはいけないのでbeforeフィルターに記述します。

users_controller.rb

class UsersController << ActiveController
before_action :is_logged_in_user, only:[:index, :edit, :update, :destroy]
before_action :admin_user, only: :destroy
・
・
・
def destroy
User.find(params[:id]).destroy
flash[:success] = "User deleted"
redirect_to users_url
private
def admin_user
redirect_to(root_url) unless current_user.admin?
end

以上で実装は完了です!!!!
最後まで見ていただきありがとうございました!

rails tutorial 10章 認可の実装の要約

みなさんおはようございます。

本日はrails tutorial 10章の認可についてまとめてみました。
もし、良ければ見ていってください。

認証と認可の違い

最初に似ている単語である認証と認可の違いについて簡単に説明しようと思います。

認証とは・・・サイトにログインしているユーザーを識別すること
認可とは・・・現在ログインしているユーザーができる行動を管理することです。

認可を具体例を挙げるならば、プロフィールの編集が挙げられます。
自分のだけでなく、他人のも変えられたらやりたい放題になってしましますよね。
そのようなことが無いように、認可で行動を管理します。

認可の実装

それでは実装についてまとめましたので、ぜひ見ていってください。
具体的にはログインしているユーザーは自分のプロフィール以外は編集できないという認可を実装していきます。

許可されていないユーザーがアクセスしようとした時にはbeforeフィルターを使います。
これは何らかの処理が実行される直前に行うメソッドです。

users_controller.rb

before_aciton :logged_in_users, only: [:edit, :udate]
・
・
・
def logged_in_users
unless is_logged_in?
flash[:danger] = "Please log in"
redirect_to root_url
end
end

これでまずログインしていないユーザーはedit,updateアクションを行おうとしたらroot_urlにリダイレクトします。
デフォルトではbeforeアクションは全てのアクションに適用されるためonlyで指定します。

次にログインしているユーザーは自分の情報以外変更できないようにします。
別のユーザーの情報を変更しようとしたらリダイレクトさせたいのでcorrect_userというメソッドを作成し、beforeフィルターで呼び出します。

before action :correct_user, only:[:edit, :update]
・
・
・
def correct_user
@user = User.find(params:id)
redirect_to root_url unless @user == current_user
end

次にリファクタリングですが、currnet_user?という論理値を返すメソッドを作成し、correct_userのunless @user == current_userの部分を変更します。

sessions_helper

def current_user?(user)
user = current_user
end

users_controller

def correct_user
@user = Use.find(params[:id])
redirect_to root_url unledd current_user(@user)
end

最後にログインした後に元々アクセスしようとしていたページにリダイレクトさせるアクションを作りたいと思います。

ユーザーを希望のページにリダイレクトさせるにはそれらのページをあらかじめ保存しておき、ログインができたらそのページにリダイレクトさせるという動作が必要になります。
これらの動作をstore_location,redirect_back_orメソッドで定義します。

sessions_helper

def store_location
session[:fowarding_url] = request.original_url if request.get
end
def redirect_back_or(default)
redirect_to([:fowarding_url] || default)
session.delete[:following_url]
end

store_locationメソッドではrequestに送られてきたurlをsession変数のfowarding_urlキーに格納します。
store_locationはユーザーがログインしてない時にedit,udateアクションしようとした時に行いたいのでlogged_in_userに加えます。

def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in"
redirect_to login_url
end
end

フォワーディング自体を実装するにはrediret_back_orメソッドを追加します。
このメソッドはsession[:fowarding_url]の値がtrueならそのurlに、なければデフォルトの値に飛びます。
デフォルトはcreateアクションに設定します。
users_controller

def create
・
・
・
redirect_back_or user
end

説明は以上です。
最後まで見ていただきありがとうございました。

remember機能の実装

おはようございます。

本日はrails tutorial9章、remember機能の実装についてまとめたいと思います。

永続的セッションを実装するための実装は以下のように記述されました。。

  1. 記憶トークンにランダムな文字列を用いる
  2. ブラウザのcookiesにトークンを保存するときは有効期限を設定する
  3. トークンはハッシュ値にしてから保存する
  4. ブラウザのcookiesに保存するユーザーidは暗号化する
  5. 永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する

少しわかりにくいですよね笑初めて読んだときはわけ分からなくなりました。

これを自分なりに解釈したところ

  1. 記憶トークンをランダムな文字列を用いて作成し、さらにそれを暗号化した記憶ダイジェストを作る
  2. ユーザーの暗号化済みIDと記憶トークンを使いcookiesを作成する
  3. ユーザーがサイトに来た際、暗号化済みのIDと記憶トークンを持ちいて認証を行う

といった流れかなと思います。

remember機能の実装

では、早速説明していこうと思います。

remember_digestの追加

まず、最初にユーザーに記憶トークンを持たせるためのremember_digestカラムをUserモデルに追加します。

rails g migraiton add_remember_digest_to_users remember_digest:string
rails db:migrate

この二つのコマンドで実装できました。

ランダムな文字列の作成

記憶トークンはランダムな文字列でなければなりません。
TutorialではRuby標準ライブラリのSecureRandomモジュールにあるurlsafe_base64メソッドを使います。

よって以下のメソッドをUserモデルに追記します。

def User.remember_token
SecureRandom.urlsafe_base64
end

これによりランダムな文字列が追加されます。

User.rememberメソッドで記憶トークンをユーザーと関連付け、記憶ダイジェストをDBに保存する

remeber_digest(記憶ダイジェスト)は追加しましたが、記憶トークンはまだ追加されていません。
そのため、まずattr_accessorメソッドにremember_token(記憶トークン)を宣言し、アクセスできるようにします。

User.rb

attr_asseccor: remember_token
・
・
・
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest,User.digest(remember_token))
end

cookiesで永続セッション作成

ユーザーの暗号化済みのIDと記憶トークンをcookiesに保存して永続セッションを作成します。

これにはcookiesメソッドを使います。
cookiesは一つの値と有効期限から出来ています。

cookies[:remember_token] = {value: remember_token, expires: 20.years.from_now.utc}

上記のコードはpermanentメソッドを使うとシンプルになります。

cookies.permanent[:remember_token] = remember_token

ユーザーIDを保存するには

cookies[:user_id] = user.id

と記述します。

ユーザーIDと記憶トークンはセットで使うのでこちらも永続的でなければなりません。
また、このままだとセキュリティ的に良しとしないのでさらに改良します。

cookies.permanent.signed[::user_id] = user.id

signedメソッドによって暗号化できます。

記憶トークンと記憶ダイジェストを使い認証を行う

この認証にはパスワード認証の際にも使ったbcryptを用います。

User.rb

def authenticate?(remember_token)
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end

次は実際にユーザーを記憶します。

sessions_controller

def create
user = User.find_by(email: params[:session][:email].downcase)
if user && user.authenticate(params[:session][:password])
log_in user
remember user
redirect_to user
else
flash.now[:danger] = 'Invalid email/password combination'
render 'new'
end
end

sessions_helper

def remember
user.remember
cookies.permanent.signed[:user_id] = user.id
cookies.permanent[:remember_token] = remember_token
end

既存コードの修正

今までのコードは一時セッションを前提に書かれていたのでそれを少し変更します。

現時点では一時セッションしか扱ってないので正常には機能しません。
永続セッションの場合はsession[:user_id]が存在すれば一時セッションからユーザーを取り出し、それ以外の場合はcookies[:user_id]から取り出し、対応する永続セッションにログインする必要があります。

def current_user
if (user_id = session[:user_id])
@current_user ||= User.find_by(id: user_id)
else (user_id = cookies.signed[:user_id])
user = User.find_by(id: user_id)
if user && user.authenticated(remember_token)
log_in user
@current_user = user
end
end
end

永続セッションの破棄

手順は以下の通りです。

  1. remember_digestをnilにするメソッドを作成
  2. 1のメソッドを用いて、cookiesを削除し、ログアウトする

1については記憶ダイジェストをnilで更新すればokです。

User.rb

def forget
update_attribute(:remember_digest, nil)
end

次に1のメソッドを用いてcookiesを削除し、ログアウトします。

sessions_helper

def forget(user)
User.forget
cookies.delete(:user_id)
cookies.delete(:remember_token)
end
def log_out
forget(current_user)
session.delete(:user_id)
current_user = nil