wolfmasa's blog

フロンターレとプログラミング関係の話題を、気が向いたときにつぶやくブログです。

Train-Ticket問題をやってみた

はじめに

Railsの資料を漁っていたら、あの伊藤さんがRailsアプリケーション作成問題を公開していたので、10周くらい遅くなったけど、勉強のためにやってみた。

github.com

元々は、meetupでコードレビューをするための題材ということで、シンプルながらもポイントを抑えた良問だと思います。

実際に、meetupの動画は見当たりませんでしたが、後日ほかの方のも含めてコードレビューを別録した動画をアップされていました。

YouTube

YouTube

1つの考え方とはいえ、Railsを書いていく上での考え方が、随所に参考になりました。ありがとうございます!

やってみた

とりあえず小一時間?で全部の問題を解いてみた。

github.com

問題は、チケット(切符)とゲート(改札)と、それをコントロールするコントローラに絞った問題で、オブジェクト指向的な考え方を問いつつ、所々validationなどRailsの記法や考え方が必要になる感じ。

最初は丁寧にやっていて、ある程度正解と近い部分もあったけど、後半は焦ってしまい、あまりちゃんと考えられなかった。

特に、この問題の1つのポイントであるところの、どこでエラーハンドリングをするか?について、あとで振り返るとコードレビューで伊藤さんも指摘されているようにコントローラに責務を持たせる状態に陥ってしまった気がする。

あとは、エラーチェックのまとまりも、もうちょっと整理して設計でできたなと。

その辺が反省。

答え合わせ

最後に、答えを見ながら写経して、反省をしめる。

github.com

Railsって問題を作るのがどうしても大変だけど、とても勉強になりました。

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

LINE botをRails6で作ってみた

はじめに

LINEのBOT API(厳密にはMessaging API)をHerokuとRailsを使って実装してみました。

ちなみに、Rails6で作ってみましたが、5と特に変わりはありませんでした。webpackerとか一応叩いてみたけど、あまり関係なかったっぽい。。。

developers.line.biz

今更〜という記事を見て、更に今更ですが(苦笑)、こんな手軽に使うRailsもあるのか、という感じで作れたので記録に残しておきます。

準備

  • Railsのインストール
  • herokuのアカウント+CLIツールインストール
  • LINE Developerアカウント作成

LINEのアカウントは、BOTAPIがリリースされた直後に作ったものがありましたが、気づいたらLINE Developerということでサイトも内容も一新されておりました。

f:id:wolfmasa:20191007182118j:plain

今回は参考サイトでも取り上げられているような、話しかけられたら適当に返事をするだけのアプリケーション。

Ruby on Railsの開発

rails new sarabot -d postgresql

が、いきなりポスグレのbundle installがこける!

調べると(よく考えれば当たり前だが)、ローカルサービスを立ち上げる必要があり、macの場合appをインストールするのが手っ取り早いとのことなので、公式HPからappをインストールし、セットアップ。

bundle config build.pg --with-pg-config=/Applications/Postgres.app/Contents/Versions/latest/bin/pg_config
bundle
bin/rails db:create

これでRailsアプリが立ち上がるので、あとは実装。

bin/rails g scaffold Post name:string
bin/rails db:migrate

まず、返答のデータベースとなるPostモデルを作る。返答は、いくつかの決められたパターンは固定で返答し、それ以外はPostsからランダムで返す単純なもの。

class LinebotController < ApplicationController
    require 'line/bot'  # gem 'line-bot-api'
  
    # callbackアクションのCSRFトークン認証を無効
    protect_from_forgery :except => [:callback]
  
    def client
      @client ||= Line::Bot::Client.new { |config|
        config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
        config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
      }
    end
  
    def callback
  
      # Postモデルの中身をランダムで@postに格納する
      @post=Post.offset( rand(Post.count) ).first
      body = request.body.read
  
      signature = request.env['HTTP_X_LINE_SIGNATURE']
      unless client.validate_signature(body, signature)
        head :bad_request
      end
  
      events = client.parse_events_from(body)
  
      events.each { |event|
  
        # event.message['text']でLINEで送られてきた文書を取得
        if event.message['text'].include?("好き")
          response = "いひひ"
        elsif event.message["text"].include?("行ってきます")
          response = "はーい"
        elsif event.message['text'].include?("おはよう")
          response = "うーーーーーーーん。。。(また寝る)"
        elsif event.message['text'].include?("さら")
          response = "さらちゃん!"
        elsif event.message['text'].include?("ママ")
          response = "ママたん!"
        elsif Post.count == 0
            response = event.message['text']
        else
          response = @post.name
        end
        #if文でresponseに送るメッセージを格納
  
        case event
        when Line::Bot::Event::Message
          case event.type
          when Line::Bot::Event::MessageType::Text
            message = {
              type: 'text',
              text: response
            }
            client.reply_message(event['replyToken'], message)
          end
        end
      }
  
      head :ok
    end
  end

ほとんど参考サイトのコピペ(すみません)。。。 コピペしながら、Messaging APIの仕様をなんとなく理解。

難しいのは、実際にherokuにデプロイしてみないと、APIが叩かれた後の確認やデバッグができないこと。

Rails.application.routes.draw do
  resources :posts
  post '/callback' => "linebot#callback"
end

ちなみに、routes.rbはこんな感じ。

動くか心配のまま、herokuにデプロイ。(上のコードは修正済みなので動きます)

heroku login
heroku create
heroku config:set LINE_CHANNEL_SECRET=xxxxx
heroku config:set config:set LINE_CHANNEL_TOKEN=xxxxxx  #(私のはめっちゃ長かった)
git push heroku master
heroku run

herokuのURLが決まったら、/callbackを含めてLINE Developer設定のWebHookに追記。

これで呼び出されるはず、だが最初は動かず、heroku上のデバッグどうするんだ!?となったので、

heroku run rails c #consoleを開く
heroku run rails tail #ログを確認する

こんな感じでできるよう。ローカルとほぼ同等のことができて素晴らしい(これで今回は十分だったのでこれ以上調べてない)

結局、callbackが呼ばれた後に例外で落ちていたのがログから確認できたので、修正して動きました。

めでたしめでたし。

f:id:wolfmasa:20191007184740p:plain

参考

qiita.com

qiita.com

qiita.com

Rails 6にdeviseを入れてみる

認証機能の実装を行う。

使うのは、有名なdevise

github.com

Gemfileにdeviseを追記し、bundle install

特に過去のバージョンから変わったことはなく、すんなりとインストール完了。

んで、実際にdeviseの設定を行っていくが、これが少し手順が多いため、後で軌道修正する力量がない人は、しっかりと説明の手順通りに進めるの吉。

rails g devise:install

deviseのインストールを行うと、初期化やローカライズファイルと共に、手順の説明が表示される。

これを見逃さずに1つ1つ対応していく。

Running via Spring preloader in process 4957 create config/initializers/devise.rb

  create  config/locales/devise.en.yml

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb:

    config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

    In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to something in your config/routes.rb. For example:

    root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example:

    <%= notice %>

    <%= alert %>

  4. You can copy Devise views (for customization) to your app by running:

    rails g devise:views

ActionMailerの設定がどこまで有効かわからないけど、rootパスの設定をし、HTMLのヘッダに警告を表示するコンポーネントを配置したら、viewのcreateを行う。これでわかる通り、まずはviewを作り、そのあと実際に認証を管理するUserクラスを作っていく。

$ rails g devise User
Running via Spring preloader in process 5500
      invoke  active_record
      create    db/migrate/20190910094526_add_devise_to_users.rb
      insert    app/models/user.rb
       route  devise_for :users
$ vim db/migrate/20190910094526_add_devise_to_users.rb 
$ rails db:migrate

deviseからUserモデルを作るが、すでに自分はUserモデルを作成済みで、かつemailのカラムも作っていたので、このままではdeviseが作ったmigrateとバッティングしてエラーになってしまう。そのため、add_devise_to_users.rb の中身でemailを追加する行をコメントアウトして実行する。

あとは、認証をして実際に表示するページを

$ rails g controller Pages index show

で作成して、routeの設定をすると完了。

では、認証とは何か。

まず、表示するページ(GETに応答するHTMLのデータ)をブラウザに返す前に、「認証されていること」を条件にしておく。もし認証されていなければ、403などのエラーを返すことを意味する。

次に、「認証」とは一般的の「ユーザ名」と「パスワード」の対であろう。これをサーバに保持しておき、ユーザが入力した値と比較して一致すれば「認証されている」としても良いことになる。ただし、セキュリティの観点から「パスワード」をそのままサーバ上に保存しておくのは好ましくない。例えばDBをハッキングされ、盗まれた場合や、そもそもサーバの管理者が悪意のある誰かだった場合などに、ユーザに不利益となるからだ。(多くの場合ユーザはパスワードを使い回すのでよりリスクが高い)

なので、サーバ上ではパスワードは暗号化して保存する。ここでは、digestと呼ばれるハッシュ値にして保存しておくことで、ユーザが入力した文字列のハッシュ値と、サーバに保存されているハッシュ値との比較をすることで認証が可能となる。

最後に、ステートレスなHTTP通信でステートを管理するセッションを利用する。おそらく、最も簡単な方法としては、認証が成功した場合にセッション情報としてユーザIDをサーバからブラウザに返し、次回以降の接続にはセッション情報にユーザIDを含めてもらうことで、認証されているものとして扱うことができる。

セッションはサーバ上で作るデータなので有効期限が設定でき、有効期間内であれば再度認証をする必要がないが、もしかしたらユーザIDだけでは、ユーザIDを盗まれた場合になりすましなどのリスクもあるような気がする。これには例えばブラウザ側のIPアドレスをセットでセッション管理に紐づけることで、簡単ななりすましは防げるのかもしれない。

セキュリティ的な観点は詳しくないが、ともあれ、基本的な認証の仕組みはこのようなもので、deviseはこれを簡単に実装してくれるライブラリである。

Rails 6にdeviseを入れてみる

認証機能の実装を行う。

使うのは、有名なdevise

github.com

Gemfileにdeviseを追記し、bundle install

特に過去のバージョンから変わったことはなく、すんなりとインストール完了。

んで、実際にdeviseの設定を行っていくが、これが少し手順が多いため、後で軌道修正する力量がない人は、しっかりと説明の手順通りに進めるの吉。

rails g devise:install

deviseのインストールを行うと、初期化やローカライズファイルと共に、手順の説明が表示される。

これを見逃さずに1つ1つ対応していく。

Running via Spring preloader in process 4957 create config/initializers/devise.rb

  create  config/locales/devise.en.yml

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb:

    config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

    In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to something in your config/routes.rb. For example:

    root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example:

    <%= notice %>

    <%= alert %>

  4. You can copy Devise views (for customization) to your app by running:

    rails g devise:views

ActionMailerの設定がどこまで有効かわからないけど、rootパスの設定をし、HTMLのヘッダに警告を表示するコンポーネントを配置したら、viewのcreateを行う。これでわかる通り、まずはviewを作り、そのあと実際に認証を管理するUserクラスを作っていく。

$ rails g devise User
Running via Spring preloader in process 5500
      invoke  active_record
      create    db/migrate/20190910094526_add_devise_to_users.rb
      insert    app/models/user.rb
       route  devise_for :users
$ vim db/migrate/20190910094526_add_devise_to_users.rb 
$ rails db:migrate

deviseからUserモデルを作るが、すでに自分はUserモデルを作成済みで、かつemailのカラムも作っていたので、このままではdeviseが作ったmigrateとバッティングしてエラーになってしまう。そのため、add_devise_to_users.rb の中身でemailを追加する行をコメントアウトして実行する。

あとは、認証をして実際に表示するページを

$ rails g controller Pages index show

で作成して、routeの設定をすると完了。

では、認証とは何か。

まず、表示するページ(GETに応答するHTMLのデータ)をブラウザに返す前に、「認証されていること」を条件にしておく。もし認証されていなければ、403などのエラーを返すことを意味する。

次に、「認証」とは一般的の「ユーザ名」と「パスワード」の対であろう。これをサーバに保持しておき、ユーザが入力した値と比較して一致すれば「認証されている」としても良いことになる。ただし、セキュリティの観点から「パスワード」をそのままサーバ上に保存しておくのは好ましくない。例えばDBをハッキングされ、盗まれた場合や、そもそもサーバの管理者が悪意のある誰かだった場合などに、ユーザに不利益となるからだ。(多くの場合ユーザはパスワードを使い回すのでよりリスクが高い)

なので、サーバ上ではパスワードは暗号化して保存する。ここでは、digestと呼ばれるハッシュ値にして保存しておくことで、ユーザが入力した文字列のハッシュ値と、サーバに保存されているハッシュ値との比較をすることで認証が可能となる。

最後に、ステートレスなHTTP通信でステートを管理するセッションを利用する。おそらく、最も簡単な方法としては、認証が成功した場合にセッション情報としてユーザIDをサーバからブラウザに返し、次回以降の接続にはセッション情報にユーザIDを含めてもらうことで、認証されているものとして扱うことができる。

セッションはサーバ上で作るデータなので有効期限が設定でき、有効期間内であれば再度認証をする必要がないが、もしかしたらユーザIDだけでは、ユーザIDを盗まれた場合になりすましなどのリスクもあるような気がする。これには例えばブラウザ側のIPアドレスをセットでセッション管理に紐づけることで、簡単ななりすましは防げるのかもしれない。

セキュリティ的な観点は詳しくないが、ともあれ、基本的な認証の仕組みはこのようなもので、deviseはこれを簡単に実装してくれるライブラリである。

Rails 6にdeviseを入れてみる

認証機能の実装を行う。

使うのは、有名なdevise

github.com

Gemfileにdeviseを追記し、bundle install

特に過去のバージョンから変わったことはなく、すんなりとインストール完了。

んで、実際にdeviseの設定を行っていくが、これが少し手順が多いため、後で軌道修正する力量がない人は、しっかりと説明の手順通りに進めるの吉。

rails g devise:install

deviseのインストールを行うと、初期化やローカライズファイルと共に、手順の説明が表示される。

これを見逃さずに1つ1つ対応していく。

Running via Spring preloader in process 4957 create config/initializers/devise.rb

  create  config/locales/devise.en.yml

Some setup you must do manually if you haven't yet:

  1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb:

    config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

    In production, :host should be set to the actual host of your application.

  2. Ensure you have defined root_url to something in your config/routes.rb. For example:

    root to: "home#index"

  3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example:

    <%= notice %>

    <%= alert %>

  4. You can copy Devise views (for customization) to your app by running:

    rails g devise:views

ActionMailerの設定がどこまで有効かわからないけど、rootパスの設定をし、HTMLのヘッダに警告を表示するコンポーネントを配置したら、viewのcreateを行う。これでわかる通り、まずはviewを作り、そのあと実際に認証を管理するUserクラスを作っていく。

$ rails g devise User
Running via Spring preloader in process 5500
      invoke  active_record
      create    db/migrate/20190910094526_add_devise_to_users.rb
      insert    app/models/user.rb
       route  devise_for :users
$ vim db/migrate/20190910094526_add_devise_to_users.rb 
$ rails db:migrate

deviseからUserモデルを作るが、すでに自分はUserモデルを作成済みで、かつemailのカラムも作っていたので、このままではdeviseが作ったmigrateとバッティングしてエラーになってしまう。そのため、add_devise_to_users.rb の中身でemailを追加する行をコメントアウトして実行する。

あとは、認証をして実際に表示するページを

$ rails g controller Pages index show

で作成して、routeの設定をすると完了。

では、認証とは何か。

まず、表示するページ(GETに応答するHTMLのデータ)をブラウザに返す前に、「認証されていること」を条件にしておく。もし認証されていなければ、403などのエラーを返すことを意味する。

次に、「認証」とは一般的の「ユーザ名」と「パスワード」の対であろう。これをサーバに保持しておき、ユーザが入力した値と比較して一致すれば「認証されている」としても良いことになる。ただし、セキュリティの観点から「パスワード」をそのままサーバ上に保存しておくのは好ましくない。例えばDBをハッキングされ、盗まれた場合や、そもそもサーバの管理者が悪意のある誰かだった場合などに、ユーザに不利益となるからだ。(多くの場合ユーザはパスワードを使い回すのでよりリスクが高い)

なので、サーバ上ではパスワードは暗号化して保存する。ここでは、digestと呼ばれるハッシュ値にして保存しておくことで、ユーザが入力した文字列のハッシュ値と、サーバに保存されているハッシュ値との比較をすることで認証が可能となる。

最後に、ステートレスなHTTP通信でステートを管理するセッションを利用する。おそらく、最も簡単な方法としては、認証が成功した場合にセッション情報としてユーザIDをサーバからブラウザに返し、次回以降の接続にはセッション情報にユーザIDを含めてもらうことで、認証されているものとして扱うことができる。

セッションはサーバ上で作るデータなので有効期限が設定でき、有効期間内であれば再度認証をする必要がないが、もしかしたらユーザIDだけでは、ユーザIDを盗まれた場合になりすましなどのリスクもあるような気がする。これには例えばブラウザ側のIPアドレスをセットでセッション管理に紐づけることで、簡単ななりすましは防げるのかもしれない。

セキュリティ的な観点は詳しくないが、ともあれ、基本的な認証の仕組みはこのようなもので、deviseはこれを簡単に実装してくれるライブラリである。

まずRails new

まずは rails newをしてみる。

もちろんバージョンは

Rails 6.0.0

チュートリアルの内容なので、ざっと手順を紹介。今回はSimpleNoteというサンプルアプリを作ってみる。

# railsのインストール
sudo gem install rails

# create project
rails new SimpleNote .

cd SimpleNote

# 昔はなかったと思うが、webpackerのインストールが必要そう。
rails webpacker:install

# bunder install自体はnewの中でされるが、追加gemについては改めてインストール
vim Gemfile
bundler install

# passはいらなかったかも。。。予定ではMD5のハッシュ値を保存して認証に使おうかと
rails g scaffold user name:string email:string pass:string
rails g scaffold article title:string contents:text
rails db:migrate

とりあえずここまででUserとArticleのモデルとRouteなどの一式が作成。

次はRouteを設定して、Userが複数のArticleを持つ構造を表現し、かつ認証なんかも着手したい。

まずRails new

まずは rails newをしてみる。

もちろんバージョンは

Rails 6.0.0

チュートリアルの内容なので、ざっと手順を紹介。今回はSimpleNoteというサンプルアプリを作ってみる。

# railsのインストール
sudo gem install rails

# create project
rails new SimpleNote .

cd SimpleNote

# 昔はなかったと思うが、webpackerのインストールが必要そう。
rails webpacker:install

# bunder install自体はnewの中でされるが、追加gemについては改めてインストール
vim Gemfile
bundler install

# passはいらなかったかも。。。予定ではMD5のハッシュ値を保存して認証に使おうかと
rails g scaffold user name:string email:string pass:string
rails g scaffold article title:string contents:text
rails db:migrate

とりあえずここまででUserとArticleのモデルとRouteなどの一式が作成。

次はRouteを設定して、Userが複数のArticleを持つ構造を表現し、かつ認証なんかも着手したい。