オブジェクト指向設計実践ガイド Rubyで分かる柔軟なアプリケーションの育て方 第2章まとめ

おはようございます。
今日は「オブジェクト指向設計実践ガイド Rubyで分かる柔軟なアプリケーションの育て方」の第2章を自分なりにまとめていきたいと思います。

第二章 単一クラスを設計する

なぜ単一責任のクラスを設計することが重要なのか

→ これは変更が簡単にできるようなアプリケーションを組みたいからになります。2つ以上の責任を持つクラスは簡単には変更できません。

では単一責任で簡単なコードを書くとどんないいことがあるのか

・見通しがよい(transparent):変更するコードにおいても、そのコードに依存する別の場所のコードにおいても変更がもたらす影響が明白である。難しくしてますが、分かりやすいコードということですね。

・合理的(reasonable):どんな変更であっても、かかるコストは変更がもたらす利益にふさわしい。すぐに変更が可能ということですね。

・利用性が高い(Usable):新しい環境、予期していなかった環境でも再利用できる。

・模範的(Exemplary):コードに変更を加える人が、上記の品質を自然と保つようなコードになっている。

※ これらの頭文字をとってTRUEなコードという。

クラスが単一責任かどうかを見極めるためには

方法は2つあります。
① クラスの持つメソッドを質問に言い換えたときにクラスに聞くべき質問であるかを確かめる
② 一文でクラスを説明してみる。考えつく限り短い説明に「それと」や「または」が入っている場合は2つ以上の責任を負っている。
説明だけだと分かりにくいので例を見ていきます。

class Gear
  attr_render :chainring, :cog, :rim, :tire
  def initialize(chainring, cog, rim, tire)
    @chainring = chainring
    @cog = cog
    @rim = rim
    @tire = tire
  end
  
  def ratio
    chainring / cog.to_s
  end

  def gear_inches
    ratio * (rim + (tire * 2))
  end
end


この例を使って①で確かめると、「Gearさん、あなたの比を教えてくれませんか?」はとても理にかなった質問です。しかし、「Gearさん、あなたのギアインチを教えてくれませんか?」はグレー、「Gearさん、あなたのタイヤのサイズを教えてくれませんか?」は完全に的ハズレな質問なのが直感的に分かると思います。これにより、Gearクラスは2つ以上の責任を追っていることが分かります。
また、②のやり方でクラスを一文で表すと、「自動車へのギアの影響を計算する」となります。どう考えてもギアとタイヤは直接的に関係がないため、タイヤのサイズについてはこのクラスで責任を持たすべきではないことが分かります。

「変更しやすいコードを書く」というテーマで悪い例を挙げて、どこが悪いかを一つずつ見ていく

ここからは表題の通り、悪い例を挙げて指摘していきます。

例①

class Gear
  def initialize(chainring, cog)
    @chainring = chainring
    @cog = cog
  end

  def ratio
    @chainring / @cog.to_f
  end
end


この例はよろしくありません。なぜなら@cogが10箇所で参照されていたとすると、@cogを修正する必要があったときに@cogが使われている箇所すべてを修正する必要があるからです。なので、最初の段階からそうした場合に備えて以下のようにした方がよいです。

class Gear
  attr_render :chainring, :cog     # 追加
  def initialize(chainring, cog)
    @chainring = chainring
    @cog = cog
  end

  def ratio
    @chainring / @cog.to_f
  end
end


自分もすごく疑問に思ったところを補足します。手間を減らすためなら、initializeメソッドを変更すれば一箇所の修正だけで終わるじゃないかという意見があります。しかし、インスタンス変数の参照先で@cogをそのまま使いたい場合に困ります。例えばオートマ車を想像すると分かりやすいです。オートマ車では、速度によって@cogが変わるのでいろいろな@cogを定義したいです。そういった場合、インスタンス変数に直接アクセスするようなやり方では実装できないため、アクセサメソッドを用意しておいたほうが後々変更が簡単になります。


例②

class ObscuringReferences
  attr_reader :data
  def initialize(data)
    @data = data
  end

  def diameters
    data.collect {|cell|
      cell[0] + (cell[1] * 2)     # 0はリムを1はタイヤを表す。
    end
  end
    # インデックスで配列の値を参照するメソッドが他にも多数ありとする
end


この例もよろしくないです。なぜなら、diametersメソッドが配列の構造に依存しているからです。配列の構造が変われば、コードをまるごと変更しなければなりません。では、どのように書けばいいのか。それは配列で示され複雑な構造をStructクラスを使って隔離します。具体的なコードを見ていきましょう。

class ObscuringReferences
  attr_reader :wheels
  def initialize(data)
    @wheels = wheelify(data)
  end

  def diameters
    wheels.collect {|wheel|
      wheel.rim + (wheel.tire * 2)
    end
  end

  wheel = Struct.new(:rim, :tire)
  def wheelify(data)
    data.collect {|cell|
      wheel.new(cell[0], cell[1]
  end
end


まず、Structクラスとはなんなのかを簡単に書きます。

Structクラスとは

簡易的なクラスで、まとまったデータを扱いたいがクラスを作るまでもなく、クラス内で特定のデータのまとまりを表現する場合に用います。

Structクラスの特徴

① Struct.newの引数に渡したシンボルはクラスの属性になり、値を読み書きするメソッドが自動的に作成される。
② 作成したクラスのnewメソッドに渡した引数は、それぞれStruct.newで作成した属性に対応する属性値となる。


こうやってまとめてみるとめちゃくちゃ便利なやつですね。
このStructクラスを使ってリムやタイヤのデータを隔離することで、配列の構造が変わってもStructクラスの箇所を修正するだけでよくなり外部データの構造の変化に強くなりました。

「あらゆる箇所を単一責任にする」というテーマで悪い例を挙げて、どこが悪いかを一つずつ見ていく

今度はあらゆる箇所を単一責任にするという目的で見ていきます。あらゆるメソッドを単一責任とすることでクラスのスコープが明白になり、余計な責任を隔離しやすくすることができるようになります。では例を見ていきます。


例③

def diameters
  wheels.collect {|wheel|
    whell.rim + (wheel.tire * 2)
end


このメソッドは責任を2つ背負っています。wheelsに対して繰り返し処理を行うと同時に、wheelの直径を計算しています。これを2つのメソッドに分けるとこうなります。

def diameters
  wheels.collect {|wheel| diameter(whell) }
end

def diameter(whell)
  whell.rim + (wheel.tire * 2)
end


このリファクタリングのより、diameterを1つ分取得できるようになりました。これを使ってギアインチの計算もすることができるようになります。

def gear_inches
  wheels.collect {|wheel| gear_inche(wheel) }
end

def gear_inche(wheel)
  diameter(wheel) * ratio
end


メソッドを単一責任にしただけですが、たったこれだけのコードの追加でギアインチを計算できるようになるという大きな副産物をもたらしてくれました。

最初の例をリファクタリングし、単一責任のクラスをつくる

最初の例に戻ると、gear_inchesメソッドは次のように単一責任のメソッドに分けることができます。

def gear_inches
  ratio * diameter
end

def diameter
  rim + (tire * 2)
end


このようにメソッドを分けるとdiameterメソッドは車輪のような振る舞いをしており、Gearクラスから隔離するべきだということが見えてきます。それでは最初の例を変更がしやすい理想的なコードに編集します。

class Gear
  attr_render :chainring, :cog, :wheel
  def initialize(chainring, cog, rim, tire)
    @chainring = chainring
    @cog = cog
    @wheel = Wheel.new(rim, tire)
  end
  
  def ratio
    chainring / cog.to_f
  end

  def gear_inches
    ratio * wheel.diameter
  end

  Wheel = Struct.new(:rim, :tire) do
    def diameter
      rim + (tire * 2)
    end
  end
end


これでWhellを使う別のメソッドを導入する際、簡単にWheelクラスを独立させることができるようになっています。


今回はこれで終わります。ここまで読んでくださり、ありがとうございました。分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!

【Rails】アクセサメソッドについて(attr_reader, attr_writter, attr_accessor)

こんにちは
一週間ぶりの投稿です。
今日はアクセサメソッドについて自分なりにまとめていきたいと思います。色々な記事を見ましたが、こちらの記事がすごく分かりやすかったです。
http://bryankawa.hatenablog.com/entry/2017/01/28/150537


アクセサメソッドとは

Rubyなどのオブジェクト指向プログラミング言語で用意されているメソッド。Rubyを学習しているのでRubyで言いますと、クラス内のインスタンス変数にクラス外からアクセスするために用意されているメソッドのことで、主に下の3つがあります。
・attr_reader
・attr_writter
・attr_accessor
オブジェクト指向においてインスタンス内の変数は内部の状態を表すもので、外から直に参照すべきではないという考え方に基づきます。

アクセサメソッドはなぜ必要か

アクセサメソッドの必要性を例を用いて説明していきます。
例①

class Gear

end

gear = Gear.new
puts gear.cog     # => undefined method 'cog'

例①ではcogメソッドを実行しようとしていますが、クラス内にcogメソッドを定義していないので、当然 No Method Errorとなります。


例②

class Gear
  def cog
    @cog
  end
end

gear = Gear.new
gear.cog     # => nil
gear.cog = "10"     # => undefined method "cog="

例②では無事cogメソッドを実行することができました。が@cogの値を書き込むことはできていません。


例③

class Gear
  def cog
    @cog
  end
  
  def cog=(str)
    @cog = str
  end

end

gear = Gear.new
gear.cog = "10" 
puts gear.cog     # => 10

例③ではクラス外部からインスタンス変数に書き込み及び読み込みができています。
毎回こんなに書くのがめんどくさいので、下のように省略して書くことができます。

class Gear
  attr_reader :cog  # ゲッターと呼ばれる
  attr_writter :cog  # セッターと呼ばれる
end

gear = Gear.new
gear.cog = "10" 
puts gear.cog     # => 10

また、さらに下記のように省略することもできます。

class Gear
  attr_accessor :cog
end

gear = Gear.new
gear.cog = "10"
puts gear.cog     # => 10

アクセサメソッドの必要性について何となくでも分かっていただけたでしょうか。クラス外部からインスタンス変数の読み書きをしたい場合に必要だということですね。

今回は以上になります。
非常に基礎的なところではありますが、すごく勉強になりました。
ここまで読んでくださり、ありがとうございました。分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!

【GemNotFound】Capistranoを用いた自動デプロイにてUnicornが再起動しないエラーについて

こんばんわ
今回はcapistranoを使った自動デプロイにてunicornが再起動しないエラーについて記事を書きたいと思います。たくさん参考になる記事はありますが、自分の備忘録のために書いていきたいと思います。

ハマったエラー

# Gemfileが見つからないエラー
I, [2020-06-13T06:11:10.779641 #3346] INFO -- : executing ["/var/www/FesLive-app/shared/bundle/ruby/2.5.0/bin/unicorn", "-c", "/var/www/FesLive-app/current/config/unicorn.rb", "-E", "deployment", "-D", {8=>#}] (in /var/www/FesLive-app/releases/20200613061044)
/home/ec2-user/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/bundler-2.0.2/lib/bundler/spec_set.rb:87:in `block in materialize': Could not find bindex-0.8.1 in any of the sources (Bundler::GemNotFound)

# unicorn再起動がうまくいかなかったときに出るエラー
E, [2020-06-13T06:11:10.960012 #30571] ERROR -- : reaped # exec()-ed

解決法

エラー文を見ると、「GemNotFound」とありGemファイルが見つからないため、Unicorn再起動ができていない状態であることが分かります。
Gemfileはもちろんあるのになぜエラーになっているのか。それはGemfile読み込み時に過去のディレクトリのGemfileを参照しているため起こるようです。(原因は不明)
なので、unicorn.rbにgemfileのディレクトリを明示してあげると解決しました。

unicorn.rb

app_path = File.expand_path('../../../', __FILE__)
# 追記
before_exec do |server|
  ENV['BUNDLE_GEMFILE'] = "#{app_path}/current/Gemfile"
end

上の記載をした後、コミット&プッシュ行い、自動デプロイの前に一度手動でunicornのmasterをkillすることで変更が反映され、次回からの自動デプロイでは上のようなエラーは出なくなりました。

最後に

今回は短いですが、以上になります。
ここまで読んでくださり、ありがとうございました。分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!


【Jquery】スクロール時に文字を下からフェードインさせる方法

おはようございます。
今日はおしゃれなサイトによくある文字がフワっとでてくるやつの実装方法を書いていこうと思います。今回はopacity(透明度)を使って実装していきます。完成形のイメージは下記になります。
https://gyazo.com/a45b65dfa8dcb0ba9f8f931368fd453c


実装の流れ

① フワッとさせたい要素に対して新たにクラスをつける
② CSSで要素の初期設定を行う
③ Jqueryでスクロールしたときに文字を移動させながら、透明度を0から1へ(完全に不透明)変化させる

以上になります。簡単に実装することができました。

① フワッとさせたい要素に対して新たにクラスをつける

フワッとさせたい要素に対して好きなクラス名を付けます。
index.html.haml

.ticket__transfer__rightbox
  .ticket__transfer__rightbox__title.fadeInUp  # fadeInUpクラスを追記
    チケット譲渡
  .ticket__transfer__rightbox__explanation.fadeInUp  # fadeInUpクラスを追記
    イベント開催間際でも行けなくなったイベントチケットをSNSアカウントや
     %br
     プロフィールを見て譲りたいファンの方に譲渡することができます。
     %br
     また、チケットを探している人は定価でチケットを手に入れるチャンスがあります。
     %br
     本アプリでは定価の10%以上の価格でのチケット譲渡は行えない仕様になっております。
  .ticket__transfer__rightbox__button.fadeInUp  # fadeInUpクラスを追記
    = link_to "transfer", "#"


② CSSで要素の初期設定を行う

ここではページを読み込んだ際の要素のCSSを書きます。ページを読み込んだ際なので、当然要素は写りませんし、本来あるべき箇所より、下にあります。(下記画像参考)
f:id:shun_0211:20200613114049j:plain
では、実際にコードを見ていきます。

_ticket.scss

.fadeInUp{
   // opacity → 不透明度の指定
  opacity : 0;
   // transform → 移動距離の指定
   transform: translateY(50px);
   // transition → 移動時間の指定
   transition: 2.5s;
}

opacityで透明度の指示

opacityは透明度を示します。透明度の初期設定は0にします。これで要素は見えなくなります。

transformで移動距離の指定

transformで要素を移動させることができます。この場合、Y軸方向(縦)に50px移動させます。ここを変更することでどれくらいの距離をフェードさせるか変更することができます。

transitionで移動時間の指定

transitionで変化するまでの時間を指定できます。ここを変更することで何 sで移動させるか変更することができます。

③ Jqueryでスクロールしたときに文字を移動させながら、透明度を0から1へ(完全に不透明)変化させる

ここからが本番です。まずはコードを記載します。
top_page.js

$(function(){
  function animation(){
    $('.fadeInUp').each(function(){
      // offset() → HTML要素の表示位置を座標で取得できるメソッド
      // これにtopを組み合わせることで、Y座標のみを取得できる
      var target = $(this).offset().top;
      //スクロール量を取得
      var scroll = $(window).scrollTop();
      //ウィンドウの高さを取得
      var windowHeight = $(window).height();
      //ターゲットまでスクロールするとフェードインする
      if (scroll > target - windowHeight){
        $(this).css('opacity','1');
        $(this).css('transform','translateY(0)');
      }
    });
  }
  $(window).scroll(function (){
    animation();
  });
});

$(window).scroll(function (){ ~~

画面がスクロールされたとき、animation関数を実行します。 これによりスクロール時に都度animation関数が走ります。

$('.fadeInUp').each(function(){ ~~

fadeInUpのクラス名がついている要素一つ一つに対してフェードインの処理を実行します。こういう場面でeach文が使えるというのは勉強になりました。

var target = $(this).offset().top;

$(this)でfadeInUpのクラス名がついている要素を取得しています。これにoffset()メソッドを用いることで、要素の表示位置を座標で取得できます。今回はY軸の座標しか必要ないため、.topをつけてY軸だけ取得しています。

var scroll = $(window).scrollTop();

scrollTop()メソッドはスクロール量を取得することができます。windowオブジェクトに対して行います。
※ windowオブジェクト → ブラウザを操作するための機能を集めたオブジェクトのこと

var windowHeight = $(window).height();

height()メソッドを使って画面の高さのピクセルを取得しています。

if (scroll > target - windowHeight){ ~~

条件分岐を使って処理をします。スクロール量が要素の高さから画面の高さを引いた量を上回った場合、以下のCSSを適用します。

$(this).css('opacity','1');  # 透明度を1に(不透明化)
$(this).css('transform','translateY(0)');  # 移動距離を0に(元の位置に戻る)

これで、faceInUpクラスに適用したCSSから2.5 sかけて元に戻る処理が書けました。

終わり!



最後に

以上になります。JavaScriptについてある程度理解がある人であれば簡単に実装できると思います。ここまで読んでくださり、ありがとうございました。分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!


【SNS認証】Facebook認証、Google認証でのユーザー新規登録を行う

おはようございます。
今日はFacebook認証とGoogle認証でのユーザー新規登録のやり方について記事を書いていきたいと思います。基本的にはTwitter認証でのやり方と同じです。

前提

Twitter認証によるユーザー新規登録ができているものとします。まだできていない場合はまずは、下記事を読んだ上での閲覧をお願いします。

https://shun-0211.hatenablog.com/entry/2020/06/10/110831

実装の流れ

① 必要なgemをインストールする
③ Facebook developerとGoogle developerでAPI使用の登録を行う
② devise.rbファイルに環境変数とスコープを記載する
④ viewで新規登録用のボタンを作成する
⑤ コントローラーにFacebookgoogleの新規登録用メソッドを追加する

以上のステップで実装していきます。Twitter認証機能が実装できていれば10分ほどで実装完了できます。

① 必要なgemをインストールする

毎度おなじみGemfileに必要なgemを記載しbundle installです。
gemfile

gem 'omniauth-facebook'
gem 'omniauth-google-oauth2'

terminal
bundle install

② Facebook developerとGoogle developerでAPI使用の登録を行う

Twitterでの登録は使用目的など事細かに書く必要がありましたが、FacebookGoogleに関しては特にありませんでした。下記サイト通りにすれば簡単に登録ができました。一点だけ注意点があるとすると、Facebook developerではリダイレクトURLを記載しなければいけません。ここには、コールバックURLを記載します。自分の場合、http://localhost:3000/users/auth/google_oauth2/callbackでした。

(参考サイト)
Facebook developer https://qiita.com/mailok1212/items/74e6dae08c1bafb874ec
Google developer https://qiita.com/bino98/items/596b5cffeca7c104bd90


③ devise.rbファイルに環境変数とスコープを記載する

まずは、環境変数を.envに定義します。環境変数は各種ページからコピーしてきます。Facebookの場合、下画像のアプリIDとapp secretです。

f:id:shun_0211:20200611204147p:plain
Googleの場合、下画像のクライアントIDとクライアントシークレットです。

f:id:shun_0211:20200611204637p:plain
.env

FACEBOOK_API_KEY = 734142477321955
FACEBOOK_API_SECRET = ~~~

GOOGLE_API_KEY = 79047870815-2p41in0b6la2k88po136iv9kfu5t5m1a.apps.googleusercontent.com
GOOGLE_API_SECRET = ~~~

また、deviseでのログインになるので、devise.rbにSNS認証のための環境変数と使用するスコープを記載します。
devise.rb

Devise.setup do |config|
  config.omniauth :facebook, ENV['FACEBOOK_API_KEY'], ENV['FACEBOOK_API_SECRET'], scope: 'email'
  config.omniauth :google_oauth2, ENV['GOOGLE_API_KEY'], ENV['GOOGLE_API_SECRET'], scope: 'email, profile'
end

ここで注意するのはgoogleの方にはscopeにprofileも入れているところです。ユーザー新規登録する際、googleアカウントでの名前を使う場合には記載が必要です。


④ viewで新規登録用のボタンを作成する

viewに新規登録用のボタンを作成していきます。rails routesにはSNS認証で使いそうなパスが2つありますが、user_facebook_omniauth_authorize_pathの方を記載します。このパスで認証を行い、user_facebook_omniauth_callback_pathの方で、データベースに登録しています。
f:id:shun_0211:20200611210206p:plain

new.html.haml

.button__box
# 追記
  = link_to user_facebook_omniauth_authorize_path, class: "facebook__registration__button" do
    %i.fab.fa-facebook-square
     facebookで登録する
  = link_to user_google_oauth2_omniauth_authorize_path, class: "google__registration__button" do
    %i.fab.fa-google
     googleで登録する


⑤ コントローラーにFacebookgoogleの新規登録用メソッドを追加する

最後にコントローラーにメソッドを追加します。あとの処理はtwitter認証のときに記載したメソッドがそのまま使えるので、ここで終わりです。
omniauth_callbacks_controller.rb

# 追記
def facebook
  callback_from :facebook
end

def google_oauth2
  callback_from :google_oauth2
end



以上になります。ここまで読んでくださり、ありがとうございました。分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!

【SNS認証】Twitter認証によりユーザー新規登録を行う

おはようございます。
今日はSNS認証ログインについて記事を書きたいと思います。twitter認証での実装を行いました。

対象

対象となるのはdeviseによるログイン、新規登録機能ができていて、deviseの機能が何となくわかっている方とします。

実装までの流れ

① Twitter API 登録 (アカウント申請)を行う
② SNS認証に必要なgemをインストールする
③ ルーティングを編集する
④ 環境変数の設定、callback URLの設定をする
⑤ viewにtwitter登録のためのボタンをつくる
⑥ usersテーブルにuidカラムとproviderカラムをつくる
⑦ モデルにデータベース登録をするためのメソッドをかく
⑧ コントローラーにログインするための処理をかく

手順は多いですが、一つ一つの作業は少なめなので簡単に実装できます。

① twitter API登録(アカウント申請)を行う

下記のリンクでAPI登録を行います。簡単に言うと、Twitter社にTwitterに登録してある情報をアプリに使ってもいいですか?という登録になります。やり方は下記Qitta記事を見ながらしましたが、簡単にできました。申請に時間がかかるのかなと思っていましたが、1分も経たず承認されました。笑 申請に記入する欄がたくさんありますが、そこまで考え込む必要はないです。Google翻訳を使ってパパっとやっちゃいましょう。
https://developer.twitter.com/ja
https://qiita.com/kngsym2018/items/2524d21455aac111cdee


② SNS認証に必要なGemをインストールする

今回必要なGemは以下の3つです。dotenv-rails環境変数を管理することができるアプリで今回はこちらを使用しました。

gemfile

gem 'omniauth-twitter'
gem 'omniauth'
gem 'dotenv-rails'


③ ルーティングの編集をする

ルーティングを以下のように追記します。

devise_for :users, controllers: {
  omniauth_callbacks: 'users/omniauth_callbacks'
}


④ 環境変数の設定、callback URLの設定をする

twitter developerのkey and tokensのタブからAPIキーとSECRETキーの環境変数を持ってきて、アプリ側に持たせる必要があります。まずは、appなどと同じディレクトリに.envファイルを作ります。そして、そのファイル内にtwitter developerからコピーしたAPIキーとSECRETキーの環境変数を定義します。

.env

TWITTER_API_KEY = ~~~~~~
TWITTER_API_SECRET = ~~~~~~

また、環境変数は他人に見られてはいけないため、githubにpushされないよう.gitignoreファイルを編集します。
.gitignore

.env      #追記

これで環境変数を使う準備はできたので、config/initializers/devise.rbにAPIキーを記載します。
devise.rb

Devise.setup do |config|
  (中略)
  config.omniauth :twitter, ENV['TWITTER_API_KEY'], ENV['TWITTER_API_SECRET'], scope: 'email'
end



最後にtwitter developerに、callback URLを記載します。

f:id:shun_0211:20200610105240p:plain

これで、twitter認証を行う準備はできました。
もし、403 Forbiddenというエラーが出るようならcallback URLが間違っている可能性が高いので確認しましょう。

f:id:shun_0211:20200610094620p:plain


※ callback URLの指定のやり方でlocalhost~~ではなく127.0.0.1~~としないとエラーになるという記事がありましたが、自分の場合localhost~~ で問題なかったです。


⑤ viewにtwitter登録のためのボタンをつくる

当然twitter登録のためのボタンが必要なので、viewに作っていきます。リンク先のパスはusers/omniauth_callbacksコントローラーのpassthruアクションを行うので、そちらのパスを指定します。この後にでてきますが、実際動かしているのはtwitterアクションじゃないかというツッコミが入りそうですが、omniauthの中でどのような動きをしているのかは分からないのでまた分かれば記事にしたいと思います。

.button__box
  = link_to  user_twitter_omniauth_authorize_path, class: "twitter__registration__button" do
    %i.fab.fa-twitter
     Twitterで登録する


⑥ usersテーブルにuidカラムとproviderカラムをつくる

uidとは利用者識別用のIDになります。twitterはuidを見てどのユーザーなのかを見分けているので、今回導入するtwitter認証でもこちらを利用します。また、providerカラムはどのSNSを使ってログインしようとしているのかを明記するために作ります。そうしないと、確率は低いですが、twitterのuidとfacebookでのuidがたまたま一致してしまった場合、不具合となります。後の処理ででてきますが、このuidとproviderが一致していないときにusersテーブルを新たに作成します。では、早速usersテーブルにカラムを追加します。

terminal
$ bundle exec rails g migration AddColumnsToUsers provider:string uid:string

20200606044514_add_columns_to_users.rb

class AddColumnsToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :provider, :string
    add_column :users, :uid, :string
  end
end

terminal
rails db:migrate


⑦ コントローラーにログインするための処理をかく

いよいよ本番です。コントローラーにログインするための処理を書いていきます。
omniauth_collbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
 
  def twitter
    callback_from :twitter
  end

  private
  # コールバック時に行う処理
  def callback_from(provider)
    provider = provider.to_s
    @user = User.find_for_oauth(request.env['omniauth.auth'])

    # persisted?でDBに保存済みかどうか判断
    if @user.persisted?
      # サインインする記載をします
      sign_in_and_redirect @user, event: :authentication
    else
      session["devise.#{provider}_data"] = request.env['omniauth.auth']
      redirect_to new_user_registration_path
    end
  end

find_for_oauthメソッド

モデルで定義するメソッドになります。後で書きますが、ここでは値としてuser情報を返します。引数にはrequest.env['omniauth.auth']を指定しています。これの中身をterminalで確認すると以下のようになっています。中身を見ると分かるようにTwitter情報がまるごと入っています。これら情報をメソッドの引数として渡します。

f:id:shun_0211:20200610094604p:plain

session~~

session~~の記述は何のために記載しているのか分かっていませんが、おそらくDBに保存できなかったとき、新規登録するための入力をする負担を減らすために記載しているのだと思います。DBに保存できないことはこのアプリでは起こり得ないので、今回は割愛します。


⑧ モデルにデータベース登録するためのメソッドをかく

コントローラーで使用しているメソッドをモデルに書いていきます。また、deviseのオプションの追記を行います。
user.rb

class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :omniauthable, omniauth_providers: [:twitter]    # :omniauthable, omniauth_providers: [:twitter]を追記
  # self.~~:クラスメソッド(クラス内のどこでも使える変数)
  def self.find_for_oauth(user_data)
    # usersテーブルの中から指定したuidとproviderの組み合わせを持つユーザーを探す
    user = User.find_by(uid: user_data.uid, provider: user_data.provider)
    
    # ||= → 変数の中身がnilの場合、右辺のメソッドを実行する
    user ||= User.create!(
      uid: user_data.uid,
      provider: user_data.provider,
      nickname: user_data[:info][:nickname],
      # 下で定義するメソッドを使い、一意のメールアドレスを作成
      email: User.dummy_email(user_data),
      # 半角英数字8文字のランダム文字列を作成
      password: [*'a'..'z', *'0'..'9'].sample(8).join
    )
    # 返り値としてuserを返す
    return user
  end
  
  def self.dummy_email(user_data)
    "#{Time.now.strftime('%Y%m%d%H%M%S').to_i}-#{user_data.uid}-#{user_data.provider}@example.com"
  end
  
end

これでローカル環境でのtwitter認証はできるはずです。

まとめ

以上になります。rails のGemはほんとに便利で何でもやってくれますね。
ここまで読んでくださり、ありがとうございました。分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!


【devise】どうしてもログイン画面でエラーメッセージを表示させたい

おはようございます。
前回新規登録画面でのエラーメッセージの表示のさせ方の記事を書かせてもらいましたが、今回はログイン画面でエラーメッセージを表示させる方法について書いていきます。実装後のイメージはこんな感じです。

f:id:shun_0211:20200606104729p:plain

rails】deviseでのバリデーションチェックのかけ方【同期通信】
https://shun-0211.hatenablog.com/entry/2020/06/05/204127



新規登録と同じようにやればできると思っちゃいますができません。自分も同じようにやろうとして大苦戦しました。そもそもログインができないのは入力後のメールアドレスに一致したユーザーのパスワードと入力したパスワードが同じであるかを見ているのであって、バリデーションチェックをしている訳ではないのです。これに気付ければ実装が見えてきます。

では一体どのように実装するのか。結論はrailsにデフォルトでついているflashを利用します。

flashとは

flashとはユーザーがログインしたときやメッセージを投稿したとき、一回限りの簡易的なメッセージを表示できるrailsの機能のこと。

では実際にflashを利用したエラーメッセージの表示をviewに入れてみます。

= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
   - flash.each do |key, value|  #追加
     = content_tag :div, value, class: key  #追加
   .input__form__email
     = f.text_field :email, placeholder: "ご登録頂いたメールアドレス", class: "email__form"
   .input__form__password
     = f.text_field :password, placeholder: "パスワード", class: "password__form"
   .login__button
     = f.submit "ログイン", class: "login__button"
   .for__forget__password   
     = link_to "パスワードをお忘れの方", "#"  

flash.each do |key, value|

deviseを使うとログイン時などに表示させるメッセージがキーとバリューの方式で自動で保存されます。ここでは、複数あるメッセージを一つ一つ取り出し表示させるための記述をしています。

content_tag :div, value, class: key

content_tagヘルパーメソッドでは1つ目の引数でタグ、2つ目の引数でそのコンテンツを指定することでHTML表記を実現させることができ、さらに引数を指定することでclassもつけることができます。これにより、エラーメッセージを画面上に表示させています。

後は、CSSで見た目を整えてあげて終了です。

.alert{
   font-weight: bold;
   color: red;
   padding-bottom: 15px;
 }
 .notice{
   display: none;
 }

noticeを非表示にしているのはログアウト後、すぐにログインすると下のような「ログアウトしました」と意図していない表示がされてしまうためです。ここはそもそもviewの書き方を直すのが本来になるので、これを参考にされる方はぜひそうして下さい。

f:id:shun_0211:20200606112108p:plain



以上で実装終了になります。最初にも触れましたが、バリデーションを見ていると勘違いしていたせいでハマりました。思い込みは怖いです。
ここまで読んでくださり、ありがとうございました。分かりにくいやアドバイス等ありましたらコメントくださると幸いです。では!