20190324のRailsに関する記事は25件です。

Ruby on Railsを体系的に学ぶためのRAILS GUIDESの使い方

始めに

この記事は、Railsアプリケーションがリクエストを受け取って、レスポンスを返すまでにどんな処理を行うかについて触れています。
個人的に「一度は読んでおいて損はない」と感じたRAILSGUIDEのリンクを集めました。
ここで紹介するリンクは、はじめは読んでも意味がわからないと思いますが、目を通す意味があると思います。

取り急ぎ執筆したため、現時点では世界一投げやりなRails講座です。

今後少しずつ説明を書いていこうと思います。
こうした方がいいんじゃないかなど、意見がありましたら、ご教授願います。

注意書き

初心者の人がここに書いてあることを全て理解するのは不可能だと思います。
目を通して頭の片隅に入れておくだけでいいです。
単語も覚えようとしないでください。「ふーん、こんなのがあるんだ」程度でいいです。

対象

以下のような人が「ギリギリ理解できるかできないか」レベルの箇所を抜粋しています。

  • Railsの勉強始めたけど腑に落ちない人
  • コードの意味がわかってない人
  • コピペコーダー

対象じゃない人

  • Rails newしたことない人
  • MVCという言葉を聞いたことすらない人
  • 読むより実践派の人
  • Railsできる人

0. リクエストとレスポンス

Webのアプリケーションはリクエスト(お願い)を受け取ることではじまります。
アプリ内のボタンをおされたり、URLが入力されたり、google検索に引っかかったりすることで、アプリケーションにリクエスト(お願い)が送られます。

リクエストはパス(URL)とHTTPリクエストメソッド(GETやPOST)で構成されます。

Webアプリケーションは、このリクエスト(お願い) に対して、様々な処理をした後にレスポンス(返信) を返します。

HTTPリクエストについては、こちらの記事を見るといいと思います。
超絶初心者のためのサーバとクライアントの話

1. Routing

Railsアプリケーションはリクエストを受け取ると、Railsのルーターがリクエストを識別します。
ルーターのカスタマイズは、config/routes.rbで行います。

以下のリンクの2.4までは目を通すといいと思います。
Railsのルーティング | RAILS GUIDE

2. Controller

MVCモデルのCです。
ルーターに指名されたコントローラは、何かしらの処理を行い、最終的にViewを用いてHTMLを表示します。

この「何かしらの処理」を理解するために、リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 4.1 コントローラの役割〜ハッシュと配列パラメータ
  • 4.3 ルーティングパラメータ
  • 4.5 Strong Parameter
  • 8 フィルタ

Railsのコントローラ | RAILS GUIDE

3. View

MVCモデルのVです。

前章で、コントローラは以下のように説明しました。

ルーターに指名されたコントローラは、何かしらの処理を行い、最終的にViewを用いてHTMLを表示します。

この章では、「Viewとは何か」と「コントローラはどうやってViewを用いるのか」について触れます。

3-1 Viewとは何か

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 3.1.1 Action Viewについて 〜 ERB
  • 3.2 ~ 4 パーシャル 〜 パーシャルレイアウト
  • 6.1 Action Viewのヘルパーメソッドの概要(余裕があれば)

Action View の概要 | RAILS GUIDE

もし余裕があればこちらも読んでみましょう。(優先順位は3-2の方が高いです。)
ユーザに入力させる、フォームについてです。

  • 1 ~ 2.2 基本的なフォームを作成する 〜 フォームとオブジェクトを結び付ける
  • 7 パラメータの命名ルールを理解する

Action View フォームヘルパー | RAILS GUIDE

3-2 コントローラはどうやってViewを用いるのか

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 2.2 概要: 部品を組み上げる 〜 レンダリングを使用する
  • 2.3 redirect_toを使用する
  • 3.4 パーシャルを使用する

レイアウトとレンダリング | RAILS GUIDE

4. モデル

MVCモデルのMです。

前章で、コントローラは以下のように説明しました。

ルーターに指名されたコントローラは、何かしらの処理を行い、最終的にViewを用いてHTMLを表示します。

この「何らかの処理」にデータベースが絡む場合、モデルが必要になります。
モデルは、基本的にはデータベース内のテーブルの数だけ存在します。モデルが複数存在するということは、テーブルのデータ同士が関連している可能性があります。この関連自体をアソシエーションといいます。

この章では、以下について触れます。

  • 4-1 Modelとは何か
  • 4-2 データベースの編集方法
  • 4-3 どうやってModelを通じてデータを取り出すか
  • 4-4 アソシエーションとは何か

4-1 Modelとは何か

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 3 Active Recordについて 〜 Active Recordのモデルを作成する
  • 5 ~ 8 CRUD: データの読み書き 〜 マイグレーション

Active Record の基礎 | RAILS GUIDE

4-2 データベースの編集方法

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 2 マイグレーションの概要 〜 マイグレーションを作成する
  • 3 (余裕があったら)
  • 4 ~ 8 CRUD: マイグレーションを実行する 〜 マイグレーションとシードデータ

Active Record マイグレーション | RAILS GUIDE

4-3 データベースの編集方法

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 3 データベースからオブジェクトを取り出す 〜 並び順

Active Record クエリインターフェイス | RAILS GUIDE

4-3 データベースの編集方法

リンクの以下の部分を目を通すといいと思います。

  • 1 ~ 2.3 関連付けを使用する理由 〜 has_many関連付け(余裕があったら、2章全体を読んでみましょう)

Active Record の関連付け (アソシエーション) | RAILS GUIDE

フィードバックについて

「こうした方がいいんじゃないか」
「この部分の説明はリンク先だけでは1mmも理解できない」
などの意見がありましたら、コメントにてご教授頂けると嬉しいです。

説明、オススメリンクを随時足していく予定です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

プログラミング学習記録36〜Cloud9で動くだけでも進歩〜

今日やったこと

  • Progate Railsの道場コース2までの内容をCloud9で再現する

ProgateでやっていたのをCloud9でもやっています。

まったく同じコードを書いても上手くいかないことがあるのには慣れてきました。

Progateで使われている画像はなしで再現しました。

道場コース2までは問題なくいけました。

ですが、道場コース2から3に進むと、いきなりUserモデルが出来上がってる状態から始まるので、ここからは学習コースの手順に従って進んでいこうと思います。

Progateの模写を終えた後はオリジナルアプリ作りに入りたいと思います。

ツイッターは投稿する欄が1つですが、これを3つにして、前田裕二さんの「メモの魔力」のようなメモができるアプリが作れたらいいなと思います。

ということで、明日からも引き続きプログラミング学習頑張ります。

おわり

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

質疑応答アプリ「ama」を動かしてみる

このまえのRailsdmで発表への質問を受け付けるのに使われてた「ama」というアプリが良さげだったので、試しに手元で動かしてみた。

ほとんどREADME.mdに書かれてるとおりにやればいいけど、自分のメモのために書いておく。

ローカルで動かす

Ruby 2.6.1とPostgreSQLが必要なのであらかじめセットアップしておく。

rbenvを使っているなら、Ruby 2.6.1を使うように設定。

$ rbenv local 2.6.1

DB接続情報を.envに設定:

.env
DATABASE_HOST=127.0.0.1
DATABASE_USERNAME=postgres

(好みで)gemをアプリ配下にインストールするように設定:

$ bundle config --local path .bundle

setupコマンド実行:

$ bin/setup

Github認証の設定。

アプリが登録されて、Client IDとClient Secretが発行されるので.envに追記:

.env
GITHUB_OAUTH_CLIENT_KEY=xxx
GITHUB_OAUTH_CLIENT_SECRET=xxx

以上で設定完了。railsサーバを起動する:

$ bin/rails s

ブラウザで http://localhost:3000/ にアクセスするとトップページが表示される。

Dockerで動かす

イメージをビルド:

$ docker-compose build

setupコマンド実行:

$ docker-compose run --rm app bin/setup

ローカル実行時と同じようにGithub認証を設定。

起動:

$ docker-compose up -d

ブラウザで http://localhost:3000/ にアクセスするとトップページが表示される。

管理者権限をつける

usersテーブルのrole列に1(admin)を設定すると管理者になる。
rails consoleで設定するならこんなかんじ:

$ bin/rails c
> user = User.find_by(nickname: 'okonomi')
> user.role = 'admin'
> user.save

管理者になると、Issueの新規作成・編集・削除ができたり、他の人のコメントの編集・削除ができるようになる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gemのあらゆるコマンドがAugumentErrorで実行できなかった件

この時のPC環境 ※要確認

OS:Windows10
Ruby:2.5.3p105 (2018-10-18 revision 65156) [x64-mingw32]
RubyGems:2.3.0

Ruby on Railsのバージョンを確かめようとコマンド"rails -v"を実行したところ、以下のようなエラー表示が出ました。

C:\Ruby25-x64\rubygems-3.0.3>rails -v
Traceback (most recent call last):
        2: from C:/Ruby25-x64/bin/rails:23:in `<main>'
        1: from C:/Ruby25-x64/lib/ruby/site_ruby/2.5.0/rubygems.rb:302:in `activate_bin_path'
C:/Ruby25-x64/lib/ruby/site_ruby/2.5.0/rubygems.rb:283:in `find_spec_for_exe': can't find gem railties (>= 0.a) with executable rails (Gem::GemNotFoundException)

エラー表示"ArgumentError"

Ruby on Railsをインストールしてみようと、コマンド"gem install rails"を実行したところ、以下のようなエラーが返ってきました。

ERROR:  While executing gem ... (ArgumentError)
    wrong number of arguments (given 1, expected 0)

ArgumentError・・・?引数がおかしい?

とりあえず、上のエラー表示についてググってみると、以下の記事を見つけました。
https://teratail.com/questions/156929
自分と同じようなエラーかなーと思いましたが、結局解決できていない模様。

さらにググってみると、以下の記事を発見。
https://github.com/rubygems/rubygems/issues/2224

この記事から、RubyとRubyGemsのバージョンによっては上手くいかないことがあると考え、RubyGemsをアップデートしてみることに。

コマンドプロンプトからRubyGemsをアップデートできない

gem update --systemを実行すると、以下のような表示が出ました。

C:\>gem update --system
Updating rubygems-update
ERROR:  While executing gem ... (ArgumentError)
    wrong number of arguments (given 1, expected 0)

またArgumentError・・・

どうにか別の方法でRubyGemsをアップデートできないものかとググりまくっていると、次の記事を発見。
http://d.hatena.ne.jp/c_mutoh/20100329/1269877259

この記事にはZipファイルを使ってRubyGemsをアップデートする方法が書かれていました。

Zipファイルを使ってRubyGemsをアップデートする方法

※先の記事に書かれている手順通りに実行しましたが、念のため書いておきます。

この時、私がインストールしたRubyGemsのバージョンは3.0.3です。(この記事を書いている時点において最新)

まず、RubyGemsのサイトのダウンロードページからZIPを選択して、Zipファイルをダウンロードします。

C:\Ruby25-x64のディレクトリ(Rubyがインストールされているディレクトリ)にダウンロードしたZipファイルを解凍します。

解凍できたら、cdコマンドで解凍したディレクトリに移動します。(私の場合"cd C:\Ruby25-x64\rubygems-3.0.3"コマンドを実行)

ここで、setup.rbを以下のように実行します。

C:\Ruby25-x64\rubygems-3.0.3>ruby setup.rb

これでRubyGemsのバージョン3.0.3がインストールできたはず・・・

バージョンを確認すると、

C:\Ruby25-x64\rubygems-3.0.3>gem -v
3.0.3

バージョンが2.3.0から3.0.3に更新されました!

Ruby on Railsをインストール

RubyGemsのバージョンアップが完了したところで、Ruby on Railsをインストールしてみます。
以下のコマンドを実行します。

C:\Ruby25-x64\rubygems-3.0.3>gem install rails
Fetching rails-5.2.2.1.gem
Successfully installed railties-5.2.2.1
Successfully installed rails-5.2.2.1
Parsing documentation for railties-5.2.2.1
Installing ri documentation for railties-5.2.2.1
Parsing documentation for rails-5.2.2.1
Installing ri documentation for rails-5.2.2.1
Done installing documentation for railties, rails after 0 seconds
2 gems installed

今度はエラーが返ってきませんでした。

Ruby on Railsが無事インストールされたか確認してみます。

C:\Ruby25-x64\rubygems-3.0.3>rails -v
Rails 5.2.2.1

インストールできたみたいです!

まとめ

Ruby on Railsをインストールしたいが、できない

RubyGemsのバージョンを変更する必要がある(?)

コマンドプロンプトでRubyGemsのバージョンを更新できない

Zipファイルを使ってRubyGemsのバージョンを更新

Ruby on Railsインストール成功

という話の流れでした。

個人的な感想

Qiita初投稿の記事になります。

私はRuby初心者ですが、プログラミングする以前に環境構築の時点でつまづくのは結構辛いですよね・・・
私はこの問題で半日以上費やしてしまい、同じような思いを他の方が味わうのは何とも酷だなと思い、思い切って投稿しました。

参考になれば幸いです。

あぁ・・・おとなしくMac買って使った方が良いんだろうな・・・

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gemのあらゆるコマンドがAugument Errorで実行できなかった件

この時のPC環境

OS:Windows10
Ruby:2.5.3p105 (2018-10-18 revision 65156) [x64-mingw32]
RubyGems:2.3.0

Ruby on Railsのバージョンを確かめようとコマンド"rails -v"を実行したところ、以下のような表示が出ました。

C:\Ruby25-x64\rubygems-3.0.3>rails -v
Traceback (most recent call last):
        2: from C:/Ruby25-x64/bin/rails:23:in `<main>'
        1: from C:/Ruby25-x64/lib/ruby/site_ruby/2.5.0/rubygems.rb:302:in `activate_bin_path'
C:/Ruby25-x64/lib/ruby/site_ruby/2.5.0/rubygems.rb:283:in `find_spec_for_exe': can't find gem railties (>= 0.a) with executable rails (Gem::GemNotFoundException)

エラー表示"ArgumentError"

Ruby on Railsをインストールしてみようと、コマンド"gem install rails"を実行したところ、以下のようなエラーが返ってきました。

ERROR:  While executing gem ... (ArgumentError)
    wrong number of arguments (given 1, expected 0)

ArgumentError・・・?引数がおかしい?

とりあえず、上のエラー表示についてググってみると、以下の記事を見つけました。
https://teratail.com/questions/156929
自分と同じようなエラーかなーと思いましたが、結局解決できていない模様。

さらにググってみると、以下の記事を発見。
https://github.com/rubygems/rubygems/issues/2224

この記事から、RubyとRubyGemsのバージョンによっては上手くいかないことがあると考え、RubyGemsをアップデートしてみることに。

コマンドプロンプトからRubyGemsをアップデートできない

gem update --systemを実行すると、以下のような表示が出ました。

C:\>gem update --system
Updating rubygems-update
ERROR:  While executing gem ... (ArgumentError)
    wrong number of arguments (given 1, expected 0)

またArgumentError・・・

どうにか別の方法でRubyGemsをアップデートできないものかとググりまくっていると、次の記事を発見。
http://d.hatena.ne.jp/c_mutoh/20100329/1269877259

この記事にはZipファイルを使ってRubyGemsをアップデートする方法が書かれていました。

Zipファイルを使ってRubyGemsをアップデートする方法

※先の記事に書かれている手順通りに実行しましたが、念のため書いておきます。

この時、私がインストールしたRubyGemsのバージョンは3.0.3です。(この記事を書いている時点において最新)

まず、RubyGemsのサイトのダウンロードページからZIPを選択して、Zipファイルをダウンロードします。

C:\Ruby25-x64のディレクトリ(Rubyがインストールされているディレクトリ)にダウンロードしたZipファイルを解凍します。

解凍できたら、cdコマンドで解凍したディレクトリに移動します。(私の場合"cd C:\Ruby25-x64\rubygems-3.0.3"コマンドを実行)

ここで、setup.rbを以下のように実行します。

C:\Ruby25-x64\rubygems-3.0.3>ruby setup.rb

これでRubyGemsのバージョン3.0.3がインストールできたはず・・・

バージョンを確認すると、

C:\Ruby25-x64\rubygems-3.0.3>gem -v
3.0.3

バージョンが2.3.0から3.0.3に更新されました!

Ruby on Railsをインストール

RubyGemsのバージョンアップが完了したところで、Ruby on Railsをインストールしてみます。
以下のコマンドを実行します。

C:\Ruby25-x64\rubygems-3.0.3>gem install rails
Fetching rails-5.2.2.1.gem
Successfully installed railties-5.2.2.1
Successfully installed rails-5.2.2.1
Parsing documentation for railties-5.2.2.1
Installing ri documentation for railties-5.2.2.1
Parsing documentation for rails-5.2.2.1
Installing ri documentation for rails-5.2.2.1
Done installing documentation for railties, rails after 0 seconds
2 gems installed

今度はエラーが返ってきませんでした。

Ruby on Railsが無事インストールされたか確認してみます。

C:\Ruby25-x64\rubygems-3.0.3>rails -v
Rails 5.2.2.1

インストールできたみたいです!

まとめ

Ruby on Railsをインストールしたいが、できない

RubyGemsのバージョンを変更する必要がある(?)

コマンドプロンプトでRubyGemsのバージョンを更新できない

Zipファイルを使ってRubyGemsのバージョンを更新

Ruby on Railsインストール成功

という話の流れでした。

個人的な感想

Qiita初投稿の記事になります。

私はRuby初心者ですが、プログラミングする以前に環境構築の時点でつまづくのは結構辛いですよね・・・
私はこの問題で半日以上費やしてしまい、同じような思いを他の方が味わうのは何とも酷だなと思い、思い切って投稿しました。

参考になれば幸いです。

あぁ・・・おとなしくMac買って使った方が良いんだろうな・・・

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails I18nでcreated_atなど各モデル共通の属性を一発で日本語化する

ユースケース

created_atやupdated_atなど、各モデル共通のattributesを日本語化したいときに。

コード

models.yml
ja:
  attributes:
    created_at: 作成日時
    updated_at: 更新日時

一番上の階層に記述すれば最初に読み込まれる。

参考

http://alfa.hatenablog.jp/entry/2013/12/03/221308
https://qiita.com/Kta-M/items/bd4ba36a58ad602a9d8b

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsチュートリアル 第九章 

はじめに

railsチュートリアルで理解しにくいところや、詰まったところを書いていく記事になります。
なので、手順を示す記事とはなっていません。

Remember me 機能

[remember me] 機能を利用すると、ユーザーが明示的にログアウトを実行しない限り、ログイン状態を維持することができるようになる。

この機能を利用するには、記憶トークン (remember token) を生成し、cookiesメソッドによる永続的cookiesの作成や、安全性の高い記憶ダイジェスト (remember digest) によるトークン認証にこの記憶トークンを活用することが必要となる。

(トークンとは、パスワードと似たようなものであるが、違いとしてはトークンの場合はコンピュータが作成した情報である)

永続的セッションを作成するにあたって、以下のようなことを留意点とする。

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

rememberダイジェストを追加

最初の手順として、マイグレーションファイルを生成し、remember_digest属性(string)をUserモデルに追加する。

また、remember_digestはユーザーが直接生み出すことはないので、インデックスを追加する必要がない。

記憶トークンの作成

記憶トークンには基本的に長くてランダムな文字列が求められるので今回はSecureRandomモジュールにあるurlsafe_base64メソッドを使用する。

このメソッドは、A–Z、a–z、0–9、"-"、"_"のいずれかの文字 (64種類) からなる長さ22のランダムな文字列を返す。
例:

$ rails console
>> SecureRandom.urlsafe_base64
=> "q5lt38hQDc_959PVoo6b7A"

ユーザーを記憶するには、記憶トークンを作成して、そのトークンをダイジェストに変換したものをデータベースに保存すればいい。

ここで、トークン生成用のメソッドを追加する。

app/models/user.rb
# ランダムなトークンを返す
  def User.new_token
    SecureRandom.urlsafe_base64
  end

次に有効なトークンとそれに関連するダイジェストを作成する。
具体的には、最初にUser.new_tokenで記憶トークンを作成し、続いてUser.digestを適用した結果で記憶ダイジェストを更新する。

app/models/user.rb
attr_accessor :remember_token
.
.
.
 # 永続セッションのためにユーザーをデータベースに記憶する
  def remember
    self.remember_token = User.new_token
    update_attribute(:remember_digest, User.digest(remember_token))
  end

rememberメソッドの1行目の代入の箇所では、selfというキーワードを使わないと、Rubyによってremember_tokenという名前のローカル変数が作成されてしまう。
selfキーワードを与えると、この代入によってユーザーのremember_token属性が期待どおりに設定される。
rememberメソッドの2行目では、update_attributeメソッドを使って記憶ダイジェストを更新している。

ログイン状態の保持

user.rememberメソッドが動作するようになったので、ユーザーの暗号化済みIDと記憶トークンをブラウザの永続cookiesに保存して、永続セッションを作成する準備ができた。
これを実行するにはcookiesメソッドを使う。

以下に手順を示す。

app/models/user.rb
.
.
.

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

rememberヘルパーメソッドを追加して、log_inと連携させる。

app/controllers/sessions_controller.rb
 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ヘルパーの動作は、rememberメソッド定義のuser.rememberを呼び出すまで遅延され、そこで記憶トークンを生成してトークンのダイジェストをデータベースに保存される。

続いて上と同様に、cookiesメソッドでユーザーIDと記憶トークンの永続cookiesを作成する。

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # ユーザーのセッションを永続的にする
  #追加
  def remember(user)
    user.remember
    cookies.permanent.signed[:user_id] = user.id
    cookies.permanent[:remember_token] = user.remember_token
  end

  # 現在ログインしているユーザーを返す (いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  def logged_in?
    !current_user.nil?
  end

  # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    @current_user = nil
  end
end

これにより、current_userヘルパーを追加修正する必要がある。

app/helpers/sessions_helper.rb
# 記憶トークンcookieに対応するユーザーを返す
  def current_user
    if (user_id = session[:user_id])
      @current_user ||= User.find_by(id: user_id)
    elsif (user_id = cookies.signed[:user_id])
      user = User.find_by(id: user_id)
      if user && user.authenticated?(cookies[:remember_token])
        log_in user
        @current_user = user
      end
    end
  end

ユーザーを忘れる

ユーザーがログアウトできるように、ユーザーを記憶するためのメソッドと同様の方法で、ユーザーを忘れるためのメソッドを定義する。

具体的には、記憶ダイジェストをnilで更新するメソッドを定義することとなる。

app/models/user.rb
.
.
.
# ユーザーのログイン情報を破棄する
  def forget
    update_attribute(:remember_digest, nil)
  end
end

forgetヘルパーメソッドを追加して、log_outヘルパーメソッドから呼び出す。

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
  .
  .
  .
  # 永続的セッションを破棄する
  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
  end
end

[Remember me] チェックボックス

ログインフォームにチェックボックスを追加する。
チェックボックスは、他のラベル、テキストフィールド、パスワードフィールド、送信ボタンと同様にヘルパーメソッドで作成できる。

app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:session, url: login_path) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

      <%= f.label :password %>
      <%= f.password_field :password, class: 'form-control' %>
      #追加
      <%= f.label :remember_me, class: "checkbox inline" do %>
        <%= f.check_box :remember_me %>
        <span>Remember me on this computer</span>
      <% end %>

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

2つのCSSクラスcheckboxとinlineを使い、Bootstrapではこれらをチェックボックスとテキスト「Remember me on this computer」として同じ行に配置することができる。

最後に、チェックボックスがオンのときにユーザーを記憶し、オフのときには記憶しないようにする。

ログインフォームから送信されたparamsハッシュには既にチェックボックスの値が含まれていて、オンの時は1、オフの時は0になるので、三項演算子を使うことで、コンパクトに処理を書くことができる。

app/controllers/sessions_controller.rb
def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      log_in user
      #追加
      params[:session][:remember_me] == '1' ? remember(user) : forget(user)
      redirect_to user
    else
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
end

おわりに

普段、当たり前のようにお世話になっている機能がここまで難しとは、、という感じです、、、
徐々に理解していこうと思います!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】読み仮名でもオートコンプリート検索できるようにしよう

概要

  • 検索にオートコンプリートをつけます
  • 読み仮名(ひらがな)でもオートコンプリートできるようにします。

動作イメージ

c0edcd5d09620e68158dfd6e8ebcd282.gif

環境

  • rails 6.0.0beta3
  • dbはsqliteなのでローカルで適当に試してください。
  • gem
    • fast_jsonapi オートコンプリート用の結果をjsonで返すときにつかう
    • rubyfuri 読み仮名を取得
    • dotenv-rails yahoo japanのapi keyを管理するため
  • javascript libary
    • selectize.js フロント側のオートコンプリート表示を制御

heroku

https://fuchu-nosanbutsu.herokuapp.com/

コード

https://github.com/junara/fuchu-nosanbutsu/releases/tag/1.1.1

ローカルで試す場合

gitから https://github.com/junara/fuchu-nosanbutsu/releases/tag/1.1.1 をクローンして下さい。

  • bundle install
bundle install
  • migarate
bin/rails db:migrate
  • データを取り込む
bin/rails c
> Store.import_csv('db/30chokubaijo_map.csv')
  • Storeデータからキーワードを登録
> importer = ItemImporter.new(Store.pluck(:description))
> importer.execute
  • ふりがなを登録
    • .env にyahoo japanのdeveloper apiを取得して書き込んでください。
    • その後以下を事項
bin/rake item:name_hiragana
  • サーバーを起動
bin/rail s
  • webpacker-dev-serverを起動
bin/webpack-dev-server 

下記にアクセス
http://localhost:3000

手順

  • サーバーサイド(オートコンプリートエンドポイント作成)
    • データ取り込み
    • ふりがなデータ取得
    • レスポンスのjson出力
    • エンドポイント
      • 複数キーワードで検索できるように修正
  • フロントエンド
    • javascript環境のセットアップ
      • jquery, selectizeのインストール
    • オートコンプリートの実装

サーバーサイド(オートコンプリートエンドポイント作成)

データ取り込み

オートコンプリート対象となるテーブル Item を作成します。

db/migrate/20190322115059_create_items.rb
class CreateItems < ActiveRecord::Migration[6.0]
  def change
    create_table :items do |t|
      t.string :name
      t.timestamps
    end
  end
end
bin/rails db:migrate

インポート用の ItemImporterを実装します。これは、Store.descriptionをparseしてItem.nameに取り込むクラスです。

app/importers/item_importer.rb
class ItemImporter
  def initialize(descriptions)
    @descriptions = descriptions
  end

  def execute
    ActiveRecord::Base.transaction do
      Item.delete_all
      list = []
      item_names.uniq.reject(&:blank?).each do |item_name|
        list << Item.new(name: item_name)
      end
      Item.import list
    end
  end

  private

  def item_names
    current_item_names = Item.pluck(:name)
    @descriptions.each_with_object(current_item_names) do |str, res|
      res.concat(custom_split(str))
    end
  end

  def custom_split(str)
    return [] if str.nil?
    str.gsub(/ほか|など|あり/, '').split(/・|\(|(|)|、|:|\n| |\)| /)
  end
end

これで取り込みを行います。

rails c
> importer = ItemImporter.new(Store.pluck(:description))
> importer.execute

これで、item.nameにstore.descriptionのデータからパースされたキーワードが保存されています。

Item.pluck(:name) などで確認して下さい。

ふりがなデータ取得

これだけだと ナス茄子なす でオートコンプリートすることができません。そのために、カタカナをひらがなに、さらに茄子を なすに対応させます。

まずふりがな(ひらがな)を格納するカラムをマイグレーションします。

db/migrate/20190324070509_add_name_furi_to_item.rb
class AddNameFuriToItem < ActiveRecord::Migration[6.0]
  def change
    add_column :items, :name_hiragana, :string
  end
end
bin/rails db:migrate 

フリガナを追加するためのメソッドを追加します。
Gemfileに下記のgemを追加します。rubyfuriはyahoo japanのapiを叩いて、読み仮名を取得するgemですdotenv-railsはapiキーを管理するためです。

gem 'dotenv-rails'
gem 'rubyfuri'

で、yahoo japanのdeveloper apiキーを取得します。 https://developer.yahoo.co.jp/webapi/jlp/ このあたりから取得してください。(取得方法は、各自調べて下さい。)

取得したapiキーを .env ファイルに記載します。(gitignoreに記載しているので大丈夫だと思いますが、こちらのAPIキーは決してgithubにアップロードしないで下さい!)

YAHOO_JAPAN_DEVELOPER_CLIENT_ID=あなたのAPIキー

良みこむためのメソッドをItemに付け加えます。

app/models/item.rb
class Item < ApplicationRecord

  def update_name_hiragana(force: false)
    rubyfuri = Rubyfuri::Client.new(ENV['YAHOO_JAPAN_DEVELOPER_CLIENT_ID'])
    update(name_hiragana: rubyfuri.furu(name))
  end
end

先ほど実装したメソッドを利用してitem.name_hiraganaに読み仮名を追加するためのrake taskを作ります。

lib/tasks/item.rake
namespace :item do
  desc 'item.nameに読み仮名をつける'
  # rake item:name_hiragana
  task name_hiragana: :environment do
    Item.all.each {|item| item.update_name_hiragana}
  end
end

上記rake taskを実行します。

> rake item:name_hiragana

Item.pluck(:name_hiragana) 等で読み仮名が格納されたことを確認して下さい。

レスポンスのjson出力

ユーザーが入力したキーワード(フロント側から送られてきたキーワード)に従って、オートコンプリート候補となるレスポンスを返します。レスポンスの形式はなんでもいいです。jbuilderで書いてもいいのですが、意外と面倒。
なので、簡単にいい感じのJSONを返してくれるgemをつかいます。
active_model_serializers が有名ですが、今回はactive_model_serializersと同様の使い勝手で、active_model_serializersよりも速い!とされる、 fast_jsonapi を使います。

まずgemをインストールします。
Gemfileに記述して

gem 'fast_jsonapi' 

ふつーにbundle install。

bundle isntall

fast_jsonapi でJSONを返すためには、 include FastJsonapi::ObjectSerializer したクラスを作り、出力したいカラムを attributes :id, :name, :name_hiragana のように書きます。これは、一番シンプルな例ですが、has_manyなどの関連でもできます。そちらをやりたい場合は公式のREADME読んでくださいませ。

app/serializers/item_serializer.rb
class ItemSerializer
  include FastJsonapi::ObjectSerializer
  attributes :id, :name, :name_hiragana
end

上記は単純に単純にid, name, name_hiraganaを返します。

なお、返ってくるjsonはこんなかんじ。(この構造はフロント側のコードで使うので頭の片隅に置いておいてください。)

{
  data: [
    {
      id: "167",
      type: "item",
      attributes: {
        id: 167,
        name: "トマト",
        name_hiragana: "とまと"
      }
    },
    {
      id: "168",
      type: "item",
      attributes: {
        id: 168,
        name: "きゅうり",
        name_hiragana: "きゅうり"
      }
    }
  ]
}

このjsonを返すコントローラのアクションはこちらです。フロント側から params[:keyword] として入力した文字列がやってきます。その文字列を検索して、結果をItemSerializerに渡してJSONで返します。 render json: ItemSerializer.new(items)

class AjaxController < ApplicationController
  def item_autocomplete
    items = Item.ransack(name_or_name_hiragana_cont: params[:keyword]).result
    render json: ItemSerializer.new(items)
  end
end

エンドポイント

オートコンプリートとは関係無いですが、複数のキーワードによる検索に対応できるようにします。
split_queries(str) がそれです。keywordが , で区切りだったら分割してransackに渡します。 description_cont_all とすることで、複数のキーワード(and検索)に対応しています。ransackの細かなところは こちら を見ましょう

app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    @stores = Store.ransack(description_cont_all: split_queries(params[:keyword])).result
  end

  private

  def split_queries(str)
    return nil unless str
    str.split(',')
  end
end

エンドポイントをroutes.rbに書きます。これで ajax/item_autocomplete でアクセスできます。(js由来だけに制限したい!とかったら適宜コード書き換えましょう。今回は緩ーく書いています)

config/routes.rb
Rails.application.routes.draw do
  root to: 'home#index'
  namespace :ajax do # このあたりを追加
    get :item_autocomplete
  end
end

フロントエンド

javascript環境のセットアップ

(rails 6なので最初からwebpacker入っています。rails 5系だったら 先にwebpackerを先にインストールしておいて下さい。)

jquery, selectizeのインストール

yarn add selectize
yarn add jquery

selectizeのcssを設定。( application.css だったら application.scss にリネームして下さい。)

ちなみに、bopotstrap3だと node_moudles/selectize/dist/css/ を見てbootstrap3用を選択するといいかも。

app/assets/stylesheets/application.scss
/*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
 *= require_tree .
 *= require_self
 */

@import "selectize/dist/css/selectize.default";

オートコンプリートの実装

ここら辺はrailsでseletize.jsの使うための手順。
sprocketにのっかって書いてもいいけど、ES6で書きたいのでwebpackerつかった実装にしました。
rails 5系で webpackerが入っていない場合はこちらのQiitaの記事 Rails & Webpackerでフロントエンド開発環境を整えるを参考に事前にインストールして下さい。

view

selectizeのターゲットとなるフォームにidを付与します。今回は、 store_search というidを追加しました。それぞれのプロジェクトであわせてください。
こちら後で説明しますが、javascriptを読み込むための javascript_pack_tag を書きます。今の段階でページを表示するとjavascriptがないのでエラーになります。

app/views/home/index.html.erb
<%= javascript_pack_tag 'home' %>

<div>
  <%= form_with url: root_path, method: :get, local: true do |f| %>
    <%= f.text_field :keyword, value: params[:keyword], autocomplete: 'off', id: 'store_search' %>
    <%= f.submit '検索' %>
  <% end %>
</div>

javascript

ようやく最後。Javascriptを書きます!

webpackerのエントリーポイントはデフォルトは app/javascript/packs 。こちらにjavascript書きます。ファイル名は何でもいいのですが、viewとあわせるとわかりやすいので home.js にしました。
で、実際のコードは app/javascript/home 以下に書くのでここでは読み込むだけのコードを書きます。

app/javascript/packs/home.js
require('home')

で、下記のコードが実行されます。ファイル名はindex.jsにしてください。( require('home') だとデフォルトでindex.jsを読み込むので。)

app/javascript/home/index.js
import $ from 'jquery';

require('selectize');

const url = (query) => {
  return `/ajax/item_autocomplete?keyword=${encodeURIComponent(query)}`
};

const items = (data) => {
  return data.map((item) => {
    return {
      text: item['attributes']['name'],
      sub_text: item['attributes']['name_hiragana'], // ひらがなでも検索結果を絞り込めるようにする
      label: item['attributes']['name'],
      value: item['attributes']['name']
    }
  })
};

const renderOption = (item, escape) => {
  return `<div><span>${escape(item.text ? item.text : '')}</span></div>`
};

const renderOptionCreate = (item, escape) => {
  return `<div><span>新規キーワード... </span><span>${escape(item.input) ? item.input : ''}</span></div>`
};

const getAutocomplete = (query, callback) => {
  $.ajax({
    url: url(query),
    type: 'GET',
    error: () => {
      callback()
    },
    success: (res) => {
      callback(items(res.data))
    }
  })
};

const addSelectize = () => {
  $('#store_search').selectize({
    searchField: ['text', 'sub_text'], // 入力値のフィルター対象、ひらがなでも検索結果を絞り込めるようにする
    labelField: 'label', // 表示させるラベル
    valueField: 'value', // inputのvalue
    closeAfterSelect: true,
    create: true, // 新規単語を追加を許可
    createOnBlur: true, // 画面外をタッチすると新規単語を追加
    render: {
      option: (item, escape) => {
        return renderOption(item, escape)
      },
      option_create: (item, escape) => {
        return renderOptionCreate(item, escape)
      }
    },
    load: (query, callback) => {
      if (!query.length) return callback();
      getAutocomplete(query, callback)
    }
  })
};

const initialize = function () {
  addSelectize();
};

window.onload = function () {
  initialize();
};

細かく解説します。

import $ from 'jquery';

require('selectize');

ここで、ライブラリを読み込みます。raisl 6は最初からjqueryがないので、 jqueyをimportして、selectizeも読み込みます。

const addSelectize = () => {
  $('#store_search').selectize({
    searchField: ['text', 'sub_text'], // 入力値のフィルター対象、ひらがなでも検索結果を絞り込めるようにする
    labelField: 'label', // 表示させるラベル
    valueField: 'value', // inputのvalue
    closeAfterSelect: true,
    create: true, // 新規単語を追加を許可
    createOnBlur: true, // 画面外をタッチすると新規単語を追加
    render: {
      option: (item, escape) => {
        return renderOption(item, escape)
      },
      option_create: (item, escape) => {
        return renderOptionCreate(item, escape)
      }
    },
    load: (query, callback) => {
      if (!query.length) return callback();
      getAutocomplete(query, callback)
    }
  })
};

ここら辺がselectizeの中心部。
$('#store_search') はjqueryのいつものやつ。 .selectize でselectizeを付与。

load: (query, callback) => {
      if (!query.length) return callback();
      getAutocomplete(query, callback)
    }

ここで、キーワードが入力されたときに実行されます。ここで、 ajaxリクエスト送ります。

const getAutocomplete = (query, callback) => {
  $.ajax({
    url: url(query),
    type: 'GET',
    error: () => {
      callback()
    },
    success: (res) => {
      callback(items(res.data))
    }
  })
};

みたままですが、queryがあったら下記のurlにリクエストを送ります。

const url = (query) => {
  return `/ajax/item_autocomplete?keyword=${encodeURIComponent(query)}`
};

レスポンスは下記で処理。

    success: (res) => {
      callback(items(res.data))
    }

レスポンスで得られたJSONを使いやすいようにマッピングし直します。

const items = (data) => {
  return data.map((item) => {
    return {
      text: item['attributes']['name'],
      sub_text: item['attributes']['name_hiragana'], // ひらがなでも検索結果を絞り込めるようにする
      label: item['attributes']['name'],
      value: item['attributes']['name']
    }
  })
};

マッピングし直したkeyをここで指定します。意味の詳細は、公式のusage で調べましょう。簡易的には下記のようにコメント書きました。

    searchField: ['text', 'sub_text'], // 入力値のフィルター対象、ひらがなでも検索結果を絞り込めるようにする
    labelField: 'label', // 表示させるラベル
    valueField: 'value', // inputのvalue

オートコンプリートリストおよび、フォームに表示される内容は下記で指定します。

const renderOption = (item, escape) => {
  return `<div><span>${escape(item.text ? item.text : '')}</span></div>`
};

const renderOptionCreate = (item, escape) => {
  return `<div><span>新規キーワード... </span><span>${escape(item.input) ? item.input : ''}</span></div>`
};

既に記述していますが、このjavascriptを使いたいところで読み込むために
<%= javascript_pack_tag 'home' %> をviewに記述します。
今回は、 app/views/home/index.html.erb 記述しました。

最後、webpackerでjavascriptをアセットコンパイルしてもらいます。

bin/webpack-dev-server                    

http://localhost:3000 にアクセスするとOK

c0edcd5d09620e68158dfd6e8ebcd282.gif

以上。
長文読んでくれてありがとうです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker Image を runして、 bundle exec rails new する

自分用の備忘録です。


まず、ビルド

docker build . -t <IMAGE_NAME>

Rails new

docker run --rm -v $PWD:$PWD -w $PWD <IMAGE_NAME> bundle exec rails new . --database=mysql --skip-test

docker runのオプションは下に説明

参考にした記事

Dockerコンテナからホスト側カレントディレクトリにアクセス

  • --rmオプションでDockerコンテナのプロセス(コマンド)が終了した時点でコンテナを破棄します。コマンド終了後にDockerコンテナ上のファイルを色々とアクセスしたい場合は外しても構いません。
  • --userオプションでホスト側のUIDを指定します。指定しない場合、生成したファイル等の所有者がroot:rootとなるためホスト側のユーザが編集したり削除したりできません。
  • -v $PWD:$PWDオプションで、ホス ト側カレントディレクトリとコンテナ側カレントディレクトリのボリューム内容を一致させます。
  • -w $PWDオプションで、ホスト側カレントディレクトリとコンテナ側カレントディレクトリのディレクトリパスを一致させます。-v $PWD:$PWDオプションと組み合わせて使います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル-本番環境でメールが送れないとき

Railsチュートリアルの「11.4 本番環境でのメール送信」について。
https://railstutorial.jp/chapters/account_activation?version=5.1#sec-activation_email_in_production

チュートリアルの手順を実施してもメールが送れなかったので解決方法をメモ。

症状

メールが送れない。

SenderGridの状態を確認。
herokuのappのページ -> Resources -> Add-onsのSendGrid -> Activityを開く。
すると、sample_appからのメール送信リクエストは受け取っているが、
ステータスが"Processed"で止まっていた。

原因

SenderGridのページの上部を見ると、
アカウントがSuspendされているというメッセージが表示されていた。
これが原因。

解決方法

SenderGridのサポートに、アカウントのSuspendを解除してもらうように依頼。
それで解決。

参考ページ

https://qiita.com/yuta-ushijima/items/f43ef5e0b59c2510ff52

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsdm2019の個人的なメモまとめ

2019.03.22-23で開催されたRailsdm2019に聞く側で参加してきました。
備忘録も兼ねて自分が面白いなーと思ったとこをまとめたいと思います。

最初に運営の皆さんに謝辞
開催にあたって色々とご尽力されたカルパスさん、他スタッフの皆さん、ご提供いただいた各社に感謝します。
本当に素晴らしいカンファレンスだったと思います。
今後は形を変えての運営という事になるようですが、ぜひ次も参加したいです!
楽しみにしています。

以下、個人的にささった順で、見てきたセッションについてのメモ。

ピクスタ 後藤さん: Ruby on Railsの正体と向き合い方

https://speakerdeck.com/yasaichi/what-is-ruby-on-rails-and-how-to-deal-with-it

  • Railsは何を妥協することで何を獲得したのか?という鋭い考察
    • 特に、RailsとHanami(クリーンアーキテクチャ)の比較が面白かった
  • URLからテーブルまでが一直線とか、モデルの中で例外が出てくる時が破綻のタイミングなど全体的にとてもわかりやすい資料。すごい。

クックパッド 庄司さん: DevOps, Immutable Infrastructure, Microservices and Chaos Engineering

https://speakerdeck.com/yoshiori/devops-immutable-infrastructure-microservices-and-chaos-engineering

  • Dockerはinfrastructureが不変であることをアプリケーションコードに強制した、というのが腹落ち感すごかった
  • Conwayの法則は最近どこに行っても聞く話
    • チーム編成/教育/スクラム関連の需要の高まりを感じる
  • ここまでやって(さらに言うならそれはMonolithの段階から)初めて「うちはMicroserviceやってます」って言えるんだなー、と
    • 自分がやってる規模は全然しょぼいよなーと再認識
    • カンファレンスはこういう普段の自分の環境と全然違う話を聞けるのが面白い
  • サービスメッシュの話が面白い
    • 各サービスは全部localhostを向いてて、あとは全部envoyがやってくれるそうな
  • Chaos Enginieeringはサーバーを落とすことではない
    • サーバーを落とすことだと認識していたのは私です。。。すいません
    • SteadyState = 計測指標を持つ、というのがとても大事
      • これは本当にそう思う。chaos engeneeringがどうとか関係なく

永和システムマネジメント 伊藤さん: rubyコミュニティの歩き方

松田さん: 毎日の開発に役立つRailsプラグインづくりの秘訣

https://speakerdeck.com/koic/somebody-to-ruby
https://speakerdeck.com/a_matsuda/extending-rails-for-real-world-app-development

  • 両者とも、もっと「参加」しようということを呼びかけていて、今の自分に足りないものだなと思った
    • とはいえまだまだ実力不足なので、まずはちゃんとお勉強から...
  • 最後のJeremyのkeynoteで質疑応答があった時も、質問者に「ぜひあなたがPRを出してください」と言っていたのが印象的だった

永和システムマネジメント 三村さん: Railsな受託の会社で僕がやっていること

https://speakerdeck.com/takkanm/what-i-do-in-a-rails-consulting-company

  • コードレビューで厳しめになるのは、そもそもその前提となる設計/実装ポリシー等の共有が足りてないのでそこを改善していくという考え方はなるほどなと
  • 最後に紹介していたRuby-道の話は資料見たらマジで感動ものだった

マチマチ 藤村さん: 入門 名前

https://speakerdeck.com/fujimura/ru-men-ming-qian

  • 良い名前ってなに?という問いに対するすごく明快な回答
    • 後藤さんのRailsの話でもあったけど、そもそも命名の時点で正しく分割できるように実装してかないとダメっていう方針で行きたいなと思った
    • 名前重要
  • 個人的に、すぐ使えて応用範囲が広いという意味ではこの発表が一番だったかなと思っています

トクバイ 前田さん: サービスを成長させる仮説検証

https://speakerdeck.com/takatoshimaeda/railsdm2019

  • ビジネスの要件でどのようなパスを見つけ、対処していくかという話
  • 前提として要素を列挙・計測しているというのがあって、やっぱそこ大事ですよねと思う
  • プロダクトに閉じない視野というのは抜けがちなので気をつけたい

kaizen platform 池田さん: ログを解析し続けてわかった、会社で眠っているアクセスログを活用する5つのプラクティス

https://blog.ikedaosushi.com/entry/2019/03/23/150125
- なぜそのデータを見えるようにすべきか?という観点をたくさん得られた
- 特に、必ずフィードバックをもらうというのは自分に欠けてる視点。ありがたい
- 先日Metabaseを使えるようにしたばかりだったので色々ためになった
- 開発者もデータをどう見て分析するのかという視点を持つの大事だと思う
- 場合によってはDB設計にも絡む話だし

STORES.jp 勝亦さん 自分たちを信じられるチームが作りたくて私はこうした

https://speakerdeck.com/katsumataryo/railsdm-katsumata
- 内容自体は特に目新しいことはないスクラムの話なんだけど、めっちゃ共感多かった
- 小さな改善の積み重ねが本当に大事だよなと改めて思う
- 「どこも似たような苦労があるんだな」って実感するのもカンファレンスのいいとこだと思う
- しんどかった話を堂々とできるのは普通に尊敬 (個人だけでなくそれを許可できる組織も)

Wovn Technologies 久田さん: red-black-tree

https://speakerdeck.com/kyuden/red-black-tree-for-ruby

  • 最近業務でパフォーマンスを気にしているところあったので参考になった
  • 即応用は難しいかもしれんけどHashやArrayばっかじゃなくてSetも色々試してみたいなと思った
    • こういうアルゴリズムとか実装の中の話聞くの楽しい

SmartHR 会社説明

  • ランチセッションで会社説明をしてくれた
  • SmartHR社の社内制度が色々充実してていいなあと思った
    • あと、それが導入されるに至った風通しの良さとかも
  • 残念ながら直近は転職できない事情があるんだが、タイミングが合えば詳しい話聞いてみたい

他にも参加したセッションあるのですが、とりあえずこの辺で。

感想

  • 総じてめっちゃいいイベントだったと思う
  • 個人的にしんどかったのが、座りっぱなしで尻が痛くなった件
    • もう少しセッションの間の休憩をとってもよかったのかも?
  • 英語で資料作ってる人多かったなー
    • 国際対応していかねばという危機感が自分の中で上がった
  • esaのトリさんかわいい
  • 2日目のサンドイッチうまかった
  • 事情で両日とも懇親会に出れなかったのが残念だった
    • 次は最後まで入れるように調整したい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

学習記録8 rails(6)

2019/03/24 Ruby on rails の学習記録(6)です。

昨日書き忘れてしまったのですが、3/23がプログラミングスクール初日でした。
午前中にオリエンテーションや自己紹介等々ありまして、午後からそのまま学習に入っています。
年齢も境遇も様々で、いい刺激になりそうです!
 
 
 

【デバッグツール】

binding.pry

  • Rails向けに開発されたデバッグツールである「pry-rails」の機能の中で最も使用するもの。
  • binding.pryという文字列をソースコードの中に記述することで、binding.pryという文字列が存在する部分でRailsの処理を止めることができる
  • 仮に実装を行なった際にエラーが出たばあいは複数個のbinding.pryを記述することで処理が正しく行われているのかを確認することができる。
  • binding.pryで処理を止めた際にはコンソールとほぼ同じことができるので、変数を出力したい際などには変数を入力することで内容を出力できる。
  • 処理を再開させたい場合にはexitというコマンドを入力する。
  • ローカルサーバーを終了させたい場合はexit!と入力する。

 
 
 

【ルートパス】

  • ルートパスとはパスをつけないホスト名だけのURLのこと
  • config/routes.rbにルートパス専用のルーティングを設定する
config/routes.rb
(例)
Rails.application.routes.draw do
  root 'samples#index'
  # ルートパスの指定
end

 
 
 

【並び替え】

orderメソッド

  • orderメソッドはテーブルから取得してきたインスタンスたちを並び替えるメソッド
  • 引数として("テーブルのカラム名 並び替える順序")という形で指定する。
  • 並び替える順序にはASC(昇順)DESC(降順)がある
app/controllers/contents_controller.rb
()
def index
  @contents = Content.all.order("id DESC")
end

 
 
 

【ページネーション】

  • ページネーションとは長い文章を複数のページに分割して、各ページへのリンクを並べアクセスしやすくすること。
  • 「kaminari」というGemをインストールすることで簡単にページネーションを実装することができる。
  • kaminariを導入するとモデルクラスにpageメソッドというページネーションにおけるページ数を指定するメソッドが定義される。
  • ビューのリクエストの際paramsの中にpageというキーが追加されて、その値がビューで指定したページ番号となるので、pageの引数はparams[:page]となる
  • perメソッドは1ページあたりに表示する件数を指定できる。
app/controllers/contents_controller.rb
()
def index
  @contents = Content.all.order("id DESC").page(params[:page]).per(5)
  # pageキーで取得した値のページに表示する
  # 1ページに5件まで表示する
end
  • ページネーションのリンクを表示したいときに使用するのがpaginateメソッド。
  • paginateメソッドの引数はコントローラで定義した変数を指定する。
app/views/tweets/index.html.erb
(例)
<div class="contents row" >
  〜中略〜
  <%= paginate(@samples) %>
  <!-- ページネーションを表示する -->
</div>

 
 
 

【ログイン機能の実装】

  • Railsの場合、「devise」というGemを使用することで簡単に実装が可能。
$ rails g devise:install
# deviseの設定ファイルを作成

$ rails g devise user
# userモデルを作成

Rubyタグ

  • <%=%>で囲まれた部分をRubyタグという。
  • 上記の書き方では処理の返り値をビューに出力する。
  • 閉じタグを-%>とすると余計な改行を取り除くことができる。
  • 開始タグを<%とすると処理の返り値をビューに出力しない。

link_toメソッド

  • 引数を指定することで様々なリンクを生成するメソッド。
  • HTMLコード内でリンクを生成するには通常aタグを使用するが、link_toメソッドを使って記述するとHTMLコードが読み込まれる際にaタグに変換される。
sample.html.erb
(例)
<%= link_to 'トップページへ','/top', class: 'sample' %>
# 作成したaタグに`class="sample"`属性を付与する

user_signed_in?メソッド

  • deviseでログイン機能を実装するとuser_signed_in?メソッドを使用することができる。
  • ユーザーがサインインしている場合にはtrueを返し、サインインしていない場合はfalseを返す。
sample.html.erb
(例)
<% if user_signed_in? %>
  # ユーザーがサインインしている場合に実行する処理
<% end %>

prefix

  • prefixとは、ルーティングのパスが入る変数のことで、コントローラやビューなどで呼び出すことでprefixに入っているパスやURL情報を取得できるようになる。
  • 確認するにはターミナルからrake routesコマンドを実行する。

unless文

  • ifの反対で、条件式がfalseの場合の処理を記述するのに使われる。
sample.rb
(例)
puts 'ログインをしてください' unless user_signed_in?
# 上下の記述は同義
unless user_signed_in?
  puts 'ログインをしてください'
end

ridirect_toメソッド

  • Railsでは通常、アクション内の処理が終了すると自動的にアクション名と同名のビューが表示されるが、ridirect_toメソッドをアクション内で利用するとそこからさらに別のアクションを実行したり、ビューに遷移させたりできる。
  • 引数にはaction: :indexという形で、キーがaction:、バリューが:indexであるハッシュを指定する。
  • このようにバリューにはアクションの名前のシンボル型を利用する。
  • 丁寧に書くと{action: :index}となるが、Railsの内部では特別にはっしゅの括弧{}を省略できる。

before_action

  • コントローラでbefore_action :メソッド名と記述することで、コントローラのアクションが実行される前にそのメソッドを実行することができる。

 
 
 

【devise用のビューファイルの作成】

  • deviseでログイン機能を実装すると、ログイン・サインアップ画面は自動的に生成されるがビューファイルは生成されないため、deviseのコマンドを利用してビューファイルを生成する必要がある。
$ rails g devise:views

【サインアップ時に新たな項目を登録する】

ユーザーテーブルにカラムを追加する

  • カラムを追加するマイグレーションファイルの作成にはrails g migration Addカラム名To追加先テーブル名 追加するカラム名:型と実行する
$ rails g migration AddIntroductionToUsers introduction:text
# usersテーブルにintroductionカラムをtext型で追加するマイグレーションファイルを作成する

スネークケースとキャメルケース

  • プログラムが認識する単語の区切り方が2通りある。
    • 「スネークケース」… add_to_introduction
    • 「キャメルケース」… AddToIntroduction

text_field:maxlengthオプション

  • maxlengthは、text_fieldというinputタグを生成するヘルパーメソッドにつけることができるオプション。
sample.html.erb
<div class="field">
  <%= f.label :nickname %> <em>(6 characters maximum)</em><br />
  <%= f.text_field :nickname, autofocus: true, maxlength: "6" %>
  <!-- text_fieldタグのヘルパーメソッドとしてmaxlength: "6"とすることで
   最大6文字と設定する -->
</div>

configure_permitted_parametersメソッド

  • 初期状態で受け取れる項目をストロングパラメーターで設定してある場合において、追加のパラメーターを許可したい場合はapplication_controllerにおいてbefore_actionconfigure_permitted_parametersメソッドを設定する
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  〜中略〜

  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters
      devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

学習記録8 rails(6) 投稿機能の実装,他

2019/03/24 Ruby on rails の学習記録(6)です。

昨日書き忘れてしまったのですが、3/23がプログラミングスクール初日でした。
午前中にオリエンテーションや自己紹介等々ありまして、午後からそのまま学習に入っています。
年齢も境遇も様々で、いい刺激になりそうです!
 
 
 

【デバッグツール】

binding.pry

  • Rails向けに開発されたデバッグツールである「pry-rails」の機能の中で最も使用するもの。
  • binding.pryという文字列をソースコードの中に記述することで、binding.pryという文字列が存在する部分でRailsの処理を止めることができる
  • 仮に実装を行なった際にエラーが出たばあいは複数個のbinding.pryを記述することで処理が正しく行われているのかを確認することができる。
  • binding.pryで処理を止めた際にはコンソールとほぼ同じことができるので、変数を出力したい際などには変数を入力することで内容を出力できる。
  • 処理を再開させたい場合にはexitというコマンドを入力する。
  • ローカルサーバーを終了させたい場合はexit!と入力する。

 
 
 

【ルートパス】

  • ルートパスとはパスをつけないホスト名だけのURLのこと
  • config/routes.rbにルートパス専用のルーティングを設定する
config/routes.rb
(例)
Rails.application.routes.draw do
  root 'samples#index'
  # ルートパスの指定
end

 
 
 

【並び替え】

orderメソッド

  • orderメソッドはテーブルから取得してきたインスタンスたちを並び替えるメソッド
  • 引数として("テーブルのカラム名 並び替える順序")という形で指定する。
  • 並び替える順序にはASC(昇順)DESC(降順)がある
app/controllers/contents_controller.rb
()
def index
  @contents = Content.all.order("id DESC")
end

 
 
 

【ページネーション】

  • ページネーションとは長い文章を複数のページに分割して、各ページへのリンクを並べアクセスしやすくすること。
  • 「kaminari」というGemをインストールすることで簡単にページネーションを実装することができる。
  • kaminariを導入するとモデルクラスにpageメソッドというページネーションにおけるページ数を指定するメソッドが定義される。
  • ビューのリクエストの際paramsの中にpageというキーが追加されて、その値がビューで指定したページ番号となるので、pageの引数はparams[:page]となる
  • perメソッドは1ページあたりに表示する件数を指定できる。
app/controllers/contents_controller.rb
()
def index
  @contents = Content.all.order("id DESC").page(params[:page]).per(5)
  # pageキーで取得した値のページに表示する
  # 1ページに5件まで表示する
end
  • ページネーションのリンクを表示したいときに使用するのがpaginateメソッド。
  • paginateメソッドの引数はコントローラで定義した変数を指定する。
app/views/tweets/index.html.erb
(例)
<div class="contents row" >
  〜中略〜
  <%= paginate(@samples) %>
  <!-- ページネーションを表示する -->
</div>

 
 
 

【ログイン機能の実装】

  • Railsの場合、「devise」というGemを使用することで簡単に実装が可能。
$ rails g devise:install
# deviseの設定ファイルを作成

$ rails g devise user
# userモデルを作成

Rubyタグ

  • <%=%>で囲まれた部分をRubyタグという。
  • 上記の書き方では処理の返り値をビューに出力する。
  • 閉じタグを-%>とすると余計な改行を取り除くことができる。
  • 開始タグを<%とすると処理の返り値をビューに出力しない。

link_toメソッド

  • 引数を指定することで様々なリンクを生成するメソッド。
  • HTMLコード内でリンクを生成するには通常aタグを使用するが、link_toメソッドを使って記述するとHTMLコードが読み込まれる際にaタグに変換される。
sample.html.erb
(例)
<%= link_to 'トップページへ','/top', class: 'sample' %>
# 作成したaタグに`class="sample"`属性を付与する

user_signed_in?メソッド

  • deviseでログイン機能を実装するとuser_signed_in?メソッドを使用することができる。
  • ユーザーがサインインしている場合にはtrueを返し、サインインしていない場合はfalseを返す。
sample.html.erb
(例)
<% if user_signed_in? %>
  # ユーザーがサインインしている場合に実行する処理
<% end %>

prefix

  • prefixとは、ルーティングのパスが入る変数のことで、コントローラやビューなどで呼び出すことでprefixに入っているパスやURL情報を取得できるようになる。
  • 確認するにはターミナルからrake routesコマンドを実行する。

unless文

  • ifの反対で、条件式がfalseの場合の処理を記述するのに使われる。
sample.rb
(例)
puts 'ログインをしてください' unless user_signed_in?
# 上下の記述は同義
unless user_signed_in?
  puts 'ログインをしてください'
end

ridirect_toメソッド

  • Railsでは通常、アクション内の処理が終了すると自動的にアクション名と同名のビューが表示されるが、ridirect_toメソッドをアクション内で利用するとそこからさらに別のアクションを実行したり、ビューに遷移させたりできる。
  • 引数にはaction: :indexという形で、キーがaction:、バリューが:indexであるハッシュを指定する。
  • このようにバリューにはアクションの名前のシンボル型を利用する。
  • 丁寧に書くと{action: :index}となるが、Railsの内部では特別にはっしゅの括弧{}を省略できる。

before_action

  • コントローラでbefore_action :メソッド名と記述することで、コントローラのアクションが実行される前にそのメソッドを実行することができる。

 
 
 

【devise用のビューファイルの作成】

  • deviseでログイン機能を実装すると、ログイン・サインアップ画面は自動的に生成されるがビューファイルは生成されないため、deviseのコマンドを利用してビューファイルを生成する必要がある。
$ rails g devise:views

 
 
 

【サインアップ時に新たな項目を登録する】

ユーザーテーブルにカラムを追加する

  • カラムを追加するマイグレーションファイルの作成にはrails g migration Addカラム名To追加先テーブル名 追加するカラム名:型と実行する
$ rails g migration AddIntroductionToUsers introduction:text
# usersテーブルにintroductionカラムをtext型で追加するマイグレーションファイルを作成する

スネークケースとキャメルケース

  • プログラムが認識する単語の区切り方が2通りある。
    • 「スネークケース」… add_to_introduction
    • 「キャメルケース」… AddToIntroduction

text_field:maxlengthオプション

  • maxlengthは、text_fieldというinputタグを生成するヘルパーメソッドにつけることができるオプション。
sample.html.erb
<div class="field">
  <%= f.label :nickname %> <em>(6 characters maximum)</em><br />
  <%= f.text_field :nickname, autofocus: true, maxlength: "6" %>
  <!-- text_fieldタグのヘルパーメソッドとしてmaxlength: "6"とすることで
   最大6文字と設定する -->
</div>

configure_permitted_parametersメソッド

  • 初期状態で受け取れる項目をストロングパラメーターで設定してある場合において、追加のパラメーターを許可したい場合はapplication_controllerにおいてbefore_actionconfigure_permitted_parametersメソッドを設定する
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base

  〜中略〜

  before_action :configure_permitted_parameters, if: :devise_controller?

  def configure_permitted_parameters
      devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル(AWSのS3のアクセス権設定)

はじめに

Ruby on Railsチュートリアルの13.4.4の本番環境での画像アップロードについて。
https://railstutorial.jp/chapters/user_microposts?version=5.1#sec-image_upload_in_production

AWSのS3を画像アップロードの場所として使うが、チュートリアルではS3の権限設定の手順が省略されている。ここでは、S3の権限設定の手順を記述する。

チュートリアルの手順に沿って、AWSにサインアップ、IAMユーザ作成、S3バケット作成までは実施している前提。
細かいことは置いておいて、とりあえずチュートリアルがこなせれば良い人向け。

AWSのS3の権限の設定手順

S3バケットへのパブリックアクセスを許可する

S3バケットの一覧ページを開く -> チュートリアルに沿って作成したバケットを選択。
S3のバケット一覧のページ.png

アクセス権限設定タブを選択 -> 編集を押す。
S3バケットの権限設定.png

「パブリックアクセスコントロールリスト (ACL) を管理する」の項目を両方共Offにする。
S3の権限設定2.png

IAMユーザにS3へのアクセス権を付与する

IAMユーザの一覧を開く -> IAMユーザを開く。
IAMユーザ一覧.png

IAMユーザにアクセス権限の追加を押す
IAMユーザアクセス権限の追加.png

既存のポリシーを直接アタッチを選択 -> ポリシーのフィルタに'S3'を入力 -> AmazonS3FullAccessを選択して、権限付与
IAMユーザアクセス権限の追加2.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

13日目(1):Deviseによるログイン機能付きサイトの作成

12日目:12日目:PostgreSQLを用いたログイン機能付きサイトの続き

環境

  • ホストOS: Windows10 Home
  • 仮想環境OS: Ubuntu Bento/Bionic
  • Ruby:2.51
  • Rails: 5.2.2 -主使用gem : devise(参照)
  • DB: PostgreSQL

前回やったこと

  1. nodejsとpostgresqlのインストール
  2. rails new self_univ3 -d postgresql とbundle install (devise)
  3. PostgreSQLのパス設定、DB作成
  4. rails g devise:install
  5. modelsにdeviseを追加。rails g devise Student

今回

  1. controllersとviewsを以前の大学データの方から流用
  2. migrationファイル作成
  3. rooting変更

実作業

controllersフォルダとviewsフォルダのコピー

# cp -r コピーしたいフォルダの場所 ペースト先

migrationファイル作成

rails g migration AddNameToStudents name:string gender:integer age:integer opinion:text
# 実行
 create    db/migrate/20190324043018_add_name_to_students.rb

DBに反映

rails db:migrate

rooting変更

app/confing/routes.rb
# 追加
resources :students
root to: 'students#index'

viewsの変更

app/views/student.html.erb
# 今回不要なExamResultNewのリンク削除
# ログアウトリンクの作成
<% @students.each do |student| %>
      <tr>
        <td><%= student.try(:name) %></td>
        <td><%= student.email %></td>
        <td><%= student.try(:gender) %></td>
        <td><%= student.try(:age) %></td>
        <td><%= student.try(:opinion) %></td>
        <td><%= link_to 'Show', student %></td>
        <td><%= link_to 'Edit', edit_student_path(student) %></td>
        <td><%= link_to 'Destroy', student, method: :delete, data: { confirm: 'Are you sure?' } %></td>
        <%= link_to 'Log Out', destroy_student_session_path, method: :delete %>
      </tr>
    <% end %>

コントローラ変更

app/controllers/student_controller.rb
class StudentsController < ApplicationController
  before_action :authenticate_student!

devise-logout.JPG

次からは、このページに、以前の大学データを組み合わせる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsチュートリアル 第八章

はじめに

railsチュートリアルで理解しにくいところや、詰まったところを書いていく記事になります。
なので、手順を示す記事とはなっていません。

セッション

HTTPはステートレス (Stateless) なプロトコルであり、ブラウザのあるページから別のページに移動したときに、ユーザーのIDを保持しておく手段がHTTPプロトコル内にはまったくない。

よって、ユーザーログインの必要なwebアプリケーションではセッション (Session) と呼ばれる半永続的な接続をコンピュータ間 (ユーザーのパソコンのWebブラウザとRailsサーバーなど) に別途設定する必要がある。

セッションコントローラ

まずはログインのフォームをnewアクションで処理するような、コントローラを作成する。

$ rails generate controller Sessions new

createdestroyといったアクションも扱うが、ビューファイルが必要ないため、ここではnewアクションだけを一緒に生成しておく。

routes.rbでは、Usersリソースのようにresourcesメソッドを使ってRESTfulなルーティングを自動的にフルセットで利用することはないので、「名前付きルーティング」だけを使う。

routes.rb
Rails.application.routes.draw do
  root   'static_pages#home'
  get    '/help',    to: 'static_pages#help'
  get    '/about',   to: 'static_pages#about'
  get    '/contact', to: 'static_pages#contact'
  get    '/signup',  to: 'users#new'
  #追加
  get    '/login',   to: 'sessions#new'
  post   '/login',   to: 'sessions#create'
  delete '/logout',  to: 'sessions#destroy'

  resources :users
end

このように名前付きルーティングが増えてきた場合は、以下のコマンドを打つことで現状のルーティングを確認することができる。

$ rails routes

ログインフォーム

コントローラとルーティングを定義した次はビューファイルを整えていく。

ログインフォームでは、会員登録をまだしていない人のために、登録のリンクも作っておく。

app/views/sessions/new.html.erb
<% provide(:title, "Log in") %>
<h1>Log in</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(:session, url: login_path) do |f| %>

      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>

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

      <%= f.submit "Log in", class: "btn btn-primary" %>
    <% end %>

    <p>New user? <%= link_to "Sign up now!", signup_path %></p>
  </div>
</div>

このソースではform_forタグを使用しているが、form_tagも同じく使用可能

セッションフォームとユーザー登録フォームの違いとして、セッションにはSessionモデルというものがなく、そのため@userのようなインスタンス変数に相当するものもない。

したがって、新しいセッションフォームを作成するときには、form_forヘルパーに追加の情報を独自に渡す必要がある。↓

<%= form_for(:session, url: login_path) do |f| %>

以下に、 HTMLフォームを示しておく。

login.form
<form accept-charset="UTF-8" action="/login" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden"
         value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" />
  <label for="session_email">Email</label>
  <input class="form-control" id="session_email"
         name="session[email]" type="text" />
  <label for="session_password">Password</label>
  <input id="session_password" name="session[password]"
         type="password" />
  <input class="btn btn-primary" name="commit" type="submit"
       value="Log in" />
</form>

コントローラで、それぞれのアクションを定義する。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController

  def new
  end

  def create
    render 'new'
  end

  def destroy
  end
end

また、createアクションでユーザーを検証する処理を書く。

app/controllers/sessions_controller.rb
def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      # エラーメッセージを作成する
      render 'new'
    end
end

セッションでは、ユーザー登録のようにActive Recordのモデルを使っていないため、自分でエラーメッセージを作る必要がある。

app/controllers/sessions_controller.rb
def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # ユーザーログイン後にユーザー情報のページにリダイレクトする
    else
      #追加
      flash.now[:danger] = 'Invalid email/password combination'
      render 'new'
    end
end

↑では、flash[:danger]としてしまうと、一度表示されたメッセージが消えなくなってしまうため
flash.now[:danger]
を使い、一度だけ表示されるようにする。

ログイン

まず、SessionsHelperをコントローラに読みこますことで、セッションに必要なメソッドを使えるようにしておく。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  include SessionsHelper
end

log_inメソッド

railsで定義済みのsessionメソッドを使って、Sessionsヘルパーにlog_inメソッドを定義する。

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end
end

これによりsessionコントローラのcreateアクションを整えることができる。

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

redirect_to user
はrailsでは自動的に以下のルーティングに変換される。
redirect_to user

現在のユーザー

セッションIDに対応するユーザーネームを取り出す、current_userメソッドを定義する。

if @current_user.nil?
  @current_user = User.find_by(id: session[:user_id])
else
  @current_user
end

↑のようにすれば、current_userを定義することができるが、もっとコンパクトに定義することができる。
「||=」を使うことで以下のようになる。

@current_user ||= User.find_by(id: session[:user_id])

「||=」は左辺がnilあれば、右辺を代入するという使い方ができる。

よって、current_userメソッドは以下のように定義することができる。

app/helpers/sessions_helper.rb
# 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

レイアウトリンクの変更

ユーザーがログインしているかしていないかで、レイアウトを変更する必要がある。

下記のようにlogged_in?のメソッドを使えば、レイアウトを変更する必要がある。

<% if logged_in? %>
  # ログインユーザー用のリンク
<% else %>
  # ログインしていないユーザー用のリンク
<% end %>

よって、logged_in?を定義する。
current_userがnilでなければログインしている、という風に解釈できるので↓のように定義できる。

app/helpers/sessions_helper.rb
module SessionsHelper

  # 渡されたユーザーでログインする
  def log_in(user)
    session[:user_id] = user.id
  end

  # 現在ログイン中のユーザーを返す (いる場合)
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end

  # ユーザーがログインしていればtrue、その他ならfalseを返す
  #追加
  def logged_in?
    !current_user.nil?
  end
end

ユーザー登録時にログイン状態

登録が終われば自動的にログインがすんでいる状態になるようにusersコントローラのcreateアクションを追加修正しておく。

app/controllers/users_controller.rb
def create
    @user = User.new(user_params)
    if @user.save
      #追加
      log_in @user
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
end

ログアウト

session.delete(:user_id)
↑で、現在のユーザーをnilにすることができるので、logoutメソッドを以下のように定義する。

app/helpers/sessions_helper.rb
module SessionsHelper
 # 現在のユーザーをログアウトする
  def log_out
    session.delete(:user_id)
    #セキュリティ上、念の為次の一行も追加する
    @current_user = nil
  end
end

次に、Sessionsコントローラのdestroyアクションを定義する。

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
.
.
.

  def destroy
      log_out
      redirect_to root_url
  end
end

終わりに

第八章では、Sessionコントローラでログイン・ログアウトを実装した。

webアプリケーションには必要な機能のためしっかり理解しておこうと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails s 時のエラー (Gem::GemNotFoundException)

Railsアプリ作成中に、rails sでサーバー起動しようとしたら、Gem::GemNotFoundException エラー。
とくになにかしたわけではないが、突如発生。
stack overflowで似たようなエラー事例があったので、参考にし、解決。
参考:stack overflow
rails s 時のエラー。

Traceback (most recent call last):
   4: from /home/ec2-user/.rvm/gems/ruby-2.6.0/bin/ruby_executable_hooks:24:in `<main>'
   3: from /home/ec2-user/.rvm/gems/ruby-2.6.0/bin/ruby_executable_hooks:24:in `eval'
   2: from /home/ec2-user/.rvm/gems/ruby-2.6.0/bin/rails:23:in `<main>'
   1: from /home/ec2-user/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path'
/home/ec2-user/.rvm/rubies/ruby-2.6.0/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': can't find gem railties (>= 0.a) with executable rails (Gem::GemNotFoundException)

以下で解決。

gem install bundler
bundle install
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kubernetes内で実行中のrails(puma)の実行された場所を知る

まずはpodを調べる

$kubectl get pod
NAME                               READY   STATUS    RESTARTS   AGE
mysql-76c755cc88-bv9wf             1/1     Running   5          12m
rails-679bf9b89c-rcl26             1/1     Running   0          2m26s

kubectl exe -it <pod> /bin/bashでログイン

$kubectl exec -it rails-679bf9b89c-rcl26 /bin/bash

pumaのpidを調べる。この例だと、pid=1

root@rails-679bf9b89c-rcl26:/myapp# ps aux | grep puma
root         1  0.3  2.9 1142908 59432 ?       Ssl  03:27   0:02 puma 3.12.1 (tcp://0.0.0.0:3000) [myapp]
root        77  0.0  0.0  11112   852 pts/0    S+   03:41   0:00 grep puma

実行されている場所を調べる

root@rails-679bf9b89c-rcl26:/myapp# ls -l /proc/1/cwd
lrwxrwxrwx. 1 root root 0 Mar 24 03:41 /proc/1/cwd -> /myapp

root@rails-679bf9b89c-rcl26:/myapp# cat /proc/1/cmdline
puma 3.12.1 (tcp://0.0.0.0:3000) [myapp]root@rails-679bf9b89c-rcl26:/myapp#

ref
- kubernetes: コンテナイメージにログインする
- プロセスが実行された場所を知る方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VScodeでRails開発をしていたらerbファイルでEmmet(エメット)が使えなかったので使えるようにした

VScodeRails開発してるんだけど、erbファイルEmmet使えない!という人や
そもそもHTMLファイルでもEmemt 使えないよ! という人は以下を試してみてください。

VScodeの設定

まずVSCodeの左下の歯車マークをクリック
次に、settingsをクリック

検索窓で、

"Trigger Expansion On Tab" と検索
Emmet:Trigger Expansion on tab の左のチェックマークを入れる

これでHtmlファイルではEmmetが使えるようになったはず。

セッティングファイルの記述

さらに続けて、検索窓で

edit in settings.json”と検索
少し小さい文字の edit in settings.jsonをクリック

ファイルが開くので以下を追記

settings.json
{
    "workbench.iconTheme": "vscode-icons",
    "window.zoomLevel": 0,
    "emmet.triggerExpansionOnTab": true,

    # ここから記述
    "emmet.includeLanguages": {
        "erb": "html"
    }
}

これで試しに erbファイル
h1 と打ってからTabキーを叩いてください。

それでうまくいかなければ,

拡張機能のインストール

VScode画面上の左上側にあるアイコンの一番下の四角い拡張機能をクリック
Rails と検索してRailsをインストール

コレです⬇︎
https://marketplace.visualstudio.com/items?itemName=bung87.rails


上記全て終えたら、晴れてEmmetが使用できるはずです。

※EmmetはVScodeじゃなくても使えます。

たくさんコードを書いてみんなで爆速になりましょう

ごあいさつ

実はこの記事がQiitaでの初投稿になります。
1人でも多くの人のお役に立てたらと願っていますが、至らない所あるかもしれません。
その際は遠慮なく教えていただけると幸いです。

参考

https://qiita.com/gorohash/items/bb5c42e0054c83fd4a2d

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

deviseメモ

deviseとは、認証機能を追加できるgem
参考サイト:https://web-camp.io/magazine/archives/16811

必要なファイルの生成
rails g devise:install

deviseで呼び出されるビューの生成
rails g devise:views

認証処理を行うためにユーザー情報を登録するためのテーブルを生成
rails g devise:users

生成されるファイル
invoke active_record
create db/migrate/20180909134145_devise_create_users.rb
create app/models/user.rb
invoke test_unit
create test/models/users_test.rb
create test/fixtures/users.yml
insert app/models/user.rb
route devise_for :users

オーバーライドとは?
参考サイト:https://rails-study.net/extend/

deviseのコントローラーのカスタマイズ
参考サイト:https://madogiwa0124.hatenablog.com/entry/2017/11/26/221657

rails g devise:controllers users
Devise::SessionsControllerを継承したコントローラーが生成される。
元のdeviseのユーザー作成のコントローラーを継承したファイルが生成されるらしい。

satounoMacBook-Pro:freemarket_47nagoya satou$ rake routes
                   Prefix Verb   URI Pattern                                                                              Controller#Action
         new_user_session GET    /users/sign_in(.:format)                                                                 devise/sessions#new
             user_session POST   /users/sign_in(.:format)                                                                 devise/sessions#create
     destroy_user_session DELETE /users/sign_out(.:format)                                                                devise/sessions#destroy
        new_user_password GET    /users/password/new(.:format)                                                            devise/passwords#new
       edit_user_password GET    /users/password/edit(.:format)                                                           devise/passwords#edit
            user_password PATCH  /users/password(.:format)                                                                devise/passwords#update
                          PUT    /users/password(.:format)                                                                devise/passwords#update
                          POST   /users/password(.:format)                                                                devise/passwords#create
 cancel_user_registration GET    /users/cancel(.:format)                                                                  devise/registrations#cancel
    new_user_registration GET    /users/sign_up(.:format)                                                                 devise/registrations#new
   edit_user_registration GET    /users/edit(.:format)                                                                    devise/registrations#edit
        user_registration PATCH  /users(.:format)                                                                         devise/registrations#update
                          PUT    /users(.:format)                                                                         devise/registrations#update
                          DELETE /users(.:format)                                                                         devise/registrations#destroy
                          POST   /users(.:format)                                                                         devise/registrations#create
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create
routes.rb
Rails.application.routes.draw do
  devise_for :users, :controllers => {
    :registrations => 'users/registrations',
    :sessions => 'users/sessions'
  }
end
satounoMacBook-Pro:freemarket_47nagoya satou$ rake routes
                   Prefix Verb   URI Pattern                                                                              Controller#Action
         new_user_session GET    /users/sign_in(.:format)                                                                 users/sessions#new
             user_session POST   /users/sign_in(.:format)                                                                 users/sessions#create
     destroy_user_session DELETE /users/sign_out(.:format)                                                                users/sessions#destroy
        new_user_password GET    /users/password/new(.:format)                                                            devise/passwords#new
       edit_user_password GET    /users/password/edit(.:format)                                                           devise/passwords#edit
            user_password PATCH  /users/password(.:format)                                                                devise/passwords#update
                          PUT    /users/password(.:format)                                                                devise/passwords#update
                          POST   /users/password(.:format)                                                                devise/passwords#create
 cancel_user_registration GET    /users/cancel(.:format)                                                                  users/registrations#cancel
    new_user_registration GET    /users/sign_up(.:format)                                                                 users/registrations#new
   edit_user_registration GET    /users/edit(.:format)                                                                    users/registrations#edit
        user_registration PATCH  /users(.:format)                                                                         users/registrations#update
                          PUT    /users(.:format)                                                                         users/registrations#update
                          DELETE /users(.:format)                                                                         users/registrations#destroy
                          POST   /users(.:format)                                                                         users/registrations#create
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create

routes.rbに記述することで、今まで見えなかった(いじれなかった)deviseのコントローラーをカスタマイズすることができる!!

devise/sessions#new => users/sessions#new
devise/sessions#create => users/sessions#create
devise/sessions#destroy => users/sessions#destroy

---ここから下はどうでもいいので無視してください---

application.html.erb
#これと
    <%= render partial: 'layouts/header' %>

    <% flash.each do |key, value| %>
        <%= content_tag(:div, value, class: "flash flash__#{key}")%>
    <% end %>

#これはどちらも同じかも?
    <% if flash[:notice] %>
        <p class="notice"><%= flash[:notice] %></p>
    <% end %>
    <% if flash[:alert] %>
        <p class="alert"><%= flash[:alert] %></p>
    <% end %>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsチュートリアル 第七章

はじめに

railsチュートリアルで理解しにくいところや、詰まったところを書いていく記事になります。
なので、手順を示す記事とはなっていません。

デバッグ

共通のビューファイルにdebugメソッドを書くことで、各ページでデバッグ用の情報が表示されるようになる。

デバッグを出力することで、ページの状態が把握しやすくなる。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
  .
  .
    <%= debug(params) if Rails.env.development? %>
  </body>
</html>

if Rails.env.development?
としておくことで、開発環境だけで表示されるようになる。

Usersリソース

config/routes.rb
resources :users

routesファイルに↑の一行を追加するだけで、ユーザーのURLを生成するための多数の名前付きルート (5.3.3) と共に、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになる。

HTTPリクエスト URL アクション 名前付きルート 用途
GET /users index users_path すべてのユーザーを一覧するページ
GET /users/1 show user_path(user) 特定のユーザーを表示するページ
GET /users/new new new_user_path ユーザーを新規作成するページ (ユーザー登録)
POST /users create users_path ユーザーを作成するアクション
GET /users/1/edit edit edit_user_path(user) id=1のユーザーを編集するページ
PATCH /users/1 update user_path(user) ユーザーを更新するアクション
DELETE /users/1 destroy user_path(user) ユーザーを削除するアクション

ただ、このルーティング先には表示するページとアクションが用意されていないから、各々準備する必要がある。

ユーザー登録フォーム

ユーザー情報を入力するために、form_forヘルパーメソッドを使う。
このメソッドはActive Recordのオブジェクトを取り込み、そのオブジェクトの属性を使ってフォームを構築する。

まず、新規ユーザーのためのユーザー登録フォーム
は下のようなものになる。

app/controllers/users_controller.rb
def new
    @user = User.newend
end
app/views/users/new.html.erb
% provide(:title, 'Sign up') %>
<h1>Sign up</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_for(@user) do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.email_field :email %>

      <%= f.label :password %>
      <%= f.password_field :password %>

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <%= f.submit "Create my account", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

例えば
<%= f.label :name %>
<%= f.text_field :name %>

という埋め込みruby部分では、
以下のようなHTMLを生成している。

<label for="user_name">Name</label>
<input id="user_name" name="user[name]" type="text" />

ブラウザからソースを表示することもできるがフォーム部分をHTMLで表すと以下のようになる。

<form accept-charset="UTF-8" action="/users" class="new_user"
      id="new_user" method="post">
  <input name="utf8" type="hidden" value="&#x2713;" />
  <input name="authenticity_token" type="hidden"
         value="NNb6+J/j46LcrgYUC60wQ2titMuJQ5lLqyAbnbAUkdo=" />
  <label for="user_name">Name</label>
  <input id="user_name" name="user[name]" type="text" />

  <label for="user_email">Email</label>
  <input id="user_email" name="user[email]" type="email" />

  <label for="user_password">Password</label>
  <input id="user_password" name="user[password]"
         type="password" />

  <label for="user_password_confirmation">Confirmation</label>
  <input id="user_password_confirmation"
         name="user[password_confirmation]" type="password" />

  <input class="btn btn-primary" name="commit" type="submit"
         value="Create my account" />
</form>

<form action="/users" class="new_user" id="new_user" method="post">
の部分では、/users に対してHTTPのPOSTリクエスト送信する、といった指示をしてる。

このフォームの値はcreateアクションに送られるようになる。
createアクション内では、

@user = User.new(params[:user])

のように@userを定義すればいいが、paramsハッシュ全体を初期化するという行為はセキュリティ上よろしくない。

この場合、user_paramsという外部メソッドを使ういことが慣習とされている。
よってcreateアクションは以下のようなものとなる。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  .
  .
  .
  def create
    @user = User.new(user_params)
    if @user.save
      # 保存の成功をここで扱う。
    else
      render 'new'
    end
  end

  private

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

privateキーワードを使って外部からはprivate内のメソッドを使えないようにする。

user_paramsを定義することにより、
paramsハッシュでは:user属性を必須とし、名前、メールアドレス、パスワード、パスワードの確認の属性をそれぞれ許可し、それ以外を許可しないようになる。

flash

ユーザー登録が成功すれば、登録完了後に表示されるページにメッセージを表示し、二度目以降には表示しないというものがよく見かける。

そういったメッセージ(フラッシュメッセージ)を表示したい場合は、flash変数を使う。

例:

app/controllers/users_controller.rb
def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
end

成功時はflash[:success]の:sucessというキーを使うことが慣習とされている。

また、このフラッシュメッセージをサイト全体で表示したい場合はapplication.html.erbは次のようなコードとなる。

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <% flash.each do |message_type, message| %>
        <div class="alert alert-<%= message_type %>"><%= message %></div>
      <% end %>
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
    .
    .
    .
  </body>
</html>

<div class="alert alert-<%= message_type %>"><%= message %></div>
の部分では、メッセージの種類を適応するCSSによって、変更するようにしている。

例えば、

<div class="alert alert-<%= message_type %>"><%= message %></div>
というコードをHTMLで表示すると

<div class="alert alert-success">Welcome to the Sample App!</div>

というふうになる。

おわり

この章では、主にユーザー登録、フラッシュメッセージを見てきた。
もう一度、7章をするときはエラーメッセージやSSLについてもう少し深く見ていこうと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails開発をVScodeしていたらerbファイルでEmmet(エメット)が使えなかったので使えるようにした

VScodeRails開発してるんだけど、erbファイルEmmet使えない!という人や
そもそもHTMLファイルでもEmemt 使えないよ! という人は以下を試してみてください。

VScodeの設定

まずVSCodeの左下の歯車マークをクリック
次に、settingsをクリック

検索窓で、

"Trigger Expansion On Tab" と検索
Emmet:Trigger Expansion on tab の左のチェックマークを入れる

これでHtmlファイルではEmmetが使えるようになったはず。

セッティングファイルの記述

さらに続けて、検索窓で

edit in settings.json”と検索
少し小さい文字の edit in settings.jsonをクリック

ファイルが開くので以下を追記

settings.json
{
    "workbench.iconTheme": "vscode-icons",
    "window.zoomLevel": 0,
    "emmet.triggerExpansionOnTab": true,

    # ここから記述
    "emmet.includeLanguages": {
        "erb": "html"
    }
}

これで試しに erbファイル
h1 と打ってからTabキーを叩いてください。

それでうまくいかなければ,

拡張機能のインストール

VScode画面上の左上側にあるアイコンの一番下の四角い拡張機能をクリック
Rails と検索してRailsをインストール

コレです⬇︎
https://marketplace.visualstudio.com/items?itemName=bung87.rails


上記全て終えたら、晴れてEmmetが使用できるはずです。

※EmmetはVScodeじゃなくても使えます。

たくさんコードを書いてみんなで爆速になりましょう

ごあいさつ

実はこの記事がQiitaでの初投稿になります。
1人でも多くの人のお役に立てたらと願っていますが、至らない所あるかもしれません。
その際は遠慮なく教えていただけると幸いです。

参考

https://qiita.com/gorohash/items/bb5c42e0054c83fd4a2d

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

バックエンド:railsAPI(heroku),フロントエンド:Vue(netlify)で連携

概要

バックエンドでrails、フロントではVueを使いたい。
連携させるにはrailsでAPIサーバを作成してVueで呼ぶのがいいかと考えました。
しかしまとまったやり方が調べても出てこなかったので自分なりのやり方をまとめました。
rails,vue-cliでプロジェクト作成可能な状態であり
heroku,netlifyのアカウントは登録済みとします。

環境

Rails: 5.1.6.2
vue-cli: 3.3.0
heroku: 7.22.7 darwin-x64 node-v11.10.1

手順

railsプロジェクトの準備

railsアプリを作成する
まずはAPI用のrailsプロジェクトを作成

$ rails new ***-api --api
$ cd ***-api

gemファイルの以下を変更し bundle install します。
生成時からの修正点は以下

  • コメントアウト部分を削除
  • gemファイルの本番環境にpostgreを追加
  • sqliteを開発環境に限定
    • herokuはsqliteでなくpostgreを使用するため
  • rack-corsを追加
    • クロスドメイン対策(後述)で使用
Gemfile
source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end

gem 'rails', '~> 5.1.6', '>= 5.1.6.2'
gem 'puma', '~> 3.7'
gem 'rack-cors'

group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'sqlite3'
  gem 'listen', '>= 3.0.5', '< 3.2'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :production do
  gem 'pg'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

config/initializers/cors.rbのコメントアウトを解除し
originsを'*'に修正します。
先ほどのrack-corsの追加と本設定をしないとvueからAPIを呼び出した際にエラーが発生します。
(参考)【Ruby on Rails】「No 'Access-Control-Allow-Origin' header is present on the requested resource」を回避する.

config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*' #コメントアウトを解除し、ここを'*'に修正
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

jsonで渡す用のデータを作成 今回はサンプルとしてUserテーブルを作ります

$ rails generate scaffold user name:string email:string password_digest:string
$ rails db:migrate

適当なデータを入れておきましょう

$ rails console
>user1 = User.new(name:"hoge_name",email:"hoge_email",password_digest:"hoge_pass")
>user1.save
>user2 = User.new(name:"fuga_name",email:"fuga_email",password_digest:"fuga_pass")
>user2.save
>User.all
>exit

取得できるか確認します。

$ curl -X GET  -H 'Content-Type:application/json' http://0.0.0.0:3000/users
#結果表示

以上ができたらherokuにアップロードしておきましょう。

#commit,herokuログイン後
$ git push heroku master
$ heroku run rake db:migrate #herokuのdb準備

herokuにアップロードされたアドレス https://***.herokuapp.com/users
にアクセスしjsonデータが表示されればOKです。

Vueプロジェクトの準備.

Vueプロジェクトを作成します。作成後はとりあえず動くか確認しましょう。
プリセット設定はなんでも大丈夫だと思います。

$ vue create *** 
~~プリセット設定~~
$ cd ***
$ yarn serve #動作確認

今回はサンプルとして初期表示ページでAPIからjsonを取得して表示したいと思います。
json取得のためaxiosをインストールします

$ npm install axios --save

次にsrc/components/HelloWorld.vueを以下のように編集します。

src/components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h1>{{ info }}</h1>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'HelloWorld',
  props: {
    msg: String,
  },
  data() {
    return {
      info: ""
    }
  },
  mounted () {
    axios.get("http://localhost:8080/users").then(response => (this.info = response))
  }
}
</script>

上記を保存し yarn serve すると、ページ上にuserのデータが表示されます。
ここまでできたらvueプロジェクトもgithub等にpushしnetlifyで公開してしまいましょう。
(参考)vue-cliでwebアプリケーションを作って、Netlifyを使って無料で爆速でリリースした話(リリースまででOK)

本番環境の連携

さて、ここまででローカルでの連携は終わりました。
しかしnetlifyで公開したページを見るとjsonデータは表示されずエラーを吐いています。
axios.getでlocalhostを参照しているので当然ですね。
ここからは本番環境(heroku-netlify間)でも連携するように修正していきます。

vueプロジェクトのフォルダに環境変数を設定するフォルダを作成します。

$ touch .env
$ touch .env.development

内容に以下を追記します。

.env
VUE_APP_BASE_API=https://(作成したherokuアプリの名前).herokuapp.com/
.env.development
VUE_APP_BASE_API=http://localhost:3000/

HelloWorld.vueのaxios.getについて、参照先を環境変数から取得するよう修正します

src/components/HelloWorld.vue
  mounted () {
    axios.get(process.env.VUE_APP_BASE_API + "users").then(response => (this.info = response))
  }

process.env.VUE_APP_*** は先ほど作成したenvファイルから値を取得します
yarn serveで起動した時は.env.development、
yarn buildで作成したファイルは.envから値を取得します。
これによりローカルではlocalhost,netlify上ではheroku-appを参照するようになります。
(参考)vue-cli 3.0 で作成したプロジェクトの環境変数(.env)の設定

後述しますが、環境変数には他の人に見られたくないものを書いたりするので
gitignoeに忘れずに登録しておきましょう。

gitignore
.env
.env.*

API認証の設定

現在の状況ではURLを知っていれば誰でもuserの中身がわかってしまいます。
なので認証をつけてVueのプロジェクトからしか見られないようにします。

application_controllers.rbに認証機能を追記します。
これによりいずれのアクションが起動する際も認証が必要となります。

app/controllers/application_controllers.rb
class ApplicationController < ActionController::API
    include ActionController::HttpAuthentication::Token::ControllerMethods
    before_action :authenticate
    def authenticate #環境変数API_TOKENがrailsとvueで一致しないと認証されない
        authenticate_or_request_with_http_token do |token,options|
            token == ENV.fetch('API_TOKEN')
        end
    end
end

API_TOKENは環境変数で設定します。
こんかいは先ほどと違い、自身の端末に設定します。

$ export API_TOKEN=xxxxx(任意の文字列 予測できないランダムなものが望ましい) 

本番環境(heroku)でも同様の文字列を環境変数に設定します。

$ heroku config:set API_TOKEN=xxxxx

次にvueプロジェクトで同様のトークンキーを設定します。

.env
VUE_APP_BASE_API=https://(作成したherokuアプリの名前).herokuapp.com/
VUE_APP_API_TOKEN=xxxxx
.env.development
VUE_APP_BASE_API=http://localhost:3000/
VUE_APP_API_TOKEN=xxxxx

最後にHelloWorld.vueのmountedを編集します
headersにトークンを追加します。

src/components/HelloWorld.vue
  mounted () {
    axios
      .get(process.env.VUE_APP_BASE_API + "users",{
        headers : { "Authorization": "Token " + process.env.VUE_APP_API_TOKEN }
        }).then(response => (this.info = response))
  }

上記で本番環境でもAPI連携が可能になりました。

おわりに

APIサーバ作るのも初めてだったのでなにか不備があればご指摘お願いします。
テスト環境とかも作らなければ…

そのたお世話になった記事

Rails5 APIで認証付きのWebAPIを作ってみる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsAPI+Vue.jsの開発環境構築(heroku,netlify使用)

概要

バックエンドでrails、フロントではVueを使いたい。
連携させるにはrailsでAPIサーバを作成してVueで呼ぶのがいいかと考えました。
しかしまとまったやり方が調べても出てこなかったので自分なりのやり方をまとめました。
rails,vue-cliでプロジェクト作成可能な状態であり
heroku,netlifyのアカウントは登録済みとします。

環境

Rails: 5.1.6.2
vue-cli: 3.3.0
heroku: 7.22.7 darwin-x64 node-v11.10.1

手順

railsプロジェクトの準備

railsアプリを作成する
まずはAPI用のrailsプロジェクトを作成

$ rails new ***-api --api
$ cd ***-api

gemファイルの以下を変更し bundle install します。
生成時からの修正点は以下

  • コメントアウト部分を削除
  • gemファイルの本番環境にpostgreを追加
  • sqliteを開発環境に限定
    • herokuはsqliteでなくpostgreを使用するため
  • rack-corsを追加
    • クロスドメイン対策(後述)で使用
Gemfile
source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end

gem 'rails', '~> 5.1.6', '>= 5.1.6.2'
gem 'puma', '~> 3.7'
gem 'rack-cors'

group :development, :test do
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end

group :development do
  gem 'sqlite3'
  gem 'listen', '>= 3.0.5', '< 3.2'
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

group :production do
  gem 'pg'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

config/initializers/cors.rbのコメントアウトを解除し
originsを'*'に修正します。
先ほどのrack-corsの追加と本設定をしないとvueからAPIを呼び出した際にエラーが発生します。
(参考)【Ruby on Rails】「No 'Access-Control-Allow-Origin' header is present on the requested resource」を回避する.

config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins '*' #コメントアウトを解除し、ここを'*'に修正
    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

jsonで渡す用のデータを作成 今回はサンプルとしてUserテーブルを作ります

$ rails generate scaffold user name:string email:string password_digest:string
$ rails db:migrate

適当なデータを入れておきましょう

$ rails console
>user1 = User.new(name:"hoge_name",email:"hoge_email",password_digest:"hoge_pass")
>user1.save
>user2 = User.new(name:"fuga_name",email:"fuga_email",password_digest:"fuga_pass")
>user2.save
>User.all
>exit

取得できるか確認します。

$ curl -X GET  -H 'Content-Type:application/json' http://0.0.0.0:3000/users
#結果表示

以上ができたらherokuにアップロードしておきましょう。

#commit,herokuログイン後
$ git push heroku master
$ heroku run rake db:migrate #herokuのdb準備

herokuにアップロードされたアドレス https://***.herokuapp.com/users
にアクセスしjsonデータが表示されればOKです。

Vueプロジェクトの準備.

Vueプロジェクトを作成します。作成後はとりあえず動くか確認しましょう。
プリセット設定はなんでも大丈夫だと思います。

$ vue create *** 
~~プリセット設定~~
$ cd ***
$ yarn serve #動作確認

今回はサンプルとして初期表示ページでAPIからjsonを取得して表示したいと思います。
json取得のためaxiosをインストールします

$ npm install axios --save

次にsrc/components/HelloWorld.vueを以下のように編集します。

src/components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <h1>{{ info }}</h1>
  </div>
</template>

<script>
import axios from 'axios'
export default {
  name: 'HelloWorld',
  props: {
    msg: String,
  },
  data() {
    return {
      info: ""
    }
  },
  mounted () {
    axios.get("http://localhost:8080/users").then(response => (this.info = response))
  }
}
</script>

上記を保存し yarn serve すると、ページ上にuserのデータが表示されます。
ここまでできたらvueプロジェクトもgithub等にpushしnetlifyで公開してしまいましょう。
(参考)vue-cliでwebアプリケーションを作って、Netlifyを使って無料で爆速でリリースした話(リリースまででOK)

本番環境の連携

さて、ここまででローカルでの連携は終わりました。
しかしnetlifyで公開したページを見るとjsonデータは表示されずエラーを吐いています。
axios.getでlocalhostを参照しているので当然ですね。
ここからは本番環境(heroku-netlify間)でも連携するように修正していきます。

vueプロジェクトのフォルダに環境変数を設定するフォルダを作成します。

$ touch .env
$ touch .env.development

内容に以下を追記します。

.env
VUE_APP_BASE_API=https://(作成したherokuアプリの名前).herokuapp.com/
.env.development
VUE_APP_BASE_API=http://localhost:3000/

HelloWorld.vueのaxios.getについて、参照先を環境変数から取得するよう修正します

src/components/HelloWorld.vue
  mounted () {
    axios.get(process.env.VUE_APP_BASE_API + "users").then(response => (this.info = response))
  }

process.env.VUE_APP_*** は先ほど作成したenvファイルから値を取得します
yarn serveで起動した時は.env.development、
yarn buildで作成したファイルは.envから値を取得します。
これによりローカルではlocalhost,netlify上ではheroku-appを参照するようになります。
(参考)vue-cli 3.0 で作成したプロジェクトの環境変数(.env)の設定

後述しますが、環境変数には他の人に見られたくないものを書いたりするので
gitignoeに忘れずに登録しておきましょう。

gitignore
.env
.env.*

API認証の設定

現在の状況ではURLを知っていれば誰でもuserの中身がわかってしまいます。
なので認証をつけてVueのプロジェクトからしか見られないようにします。

application_controllers.rbに認証機能を追記します。
これによりいずれのアクションが起動する際も認証が必要となります。

app/controllers/application_controllers.rb
class ApplicationController < ActionController::API
    include ActionController::HttpAuthentication::Token::ControllerMethods
    before_action :authenticate
    def authenticate #環境変数API_TOKENがrailsとvueで一致しないと認証されない
        authenticate_or_request_with_http_token do |token,options|
            token == ENV.fetch('API_TOKEN')
        end
    end
end

API_TOKENは環境変数で設定します。
こんかいは先ほどと違い、自身の端末に設定します。

$ export API_TOKEN=xxxxx(任意の文字列 予測できないランダムなものが望ましい) 

本番環境(heroku)でも同様の文字列を環境変数に設定します。

$ heroku config:set API_TOKEN=xxxxx

次にvueプロジェクトで同様のトークンキーを設定します。

.env
VUE_APP_BASE_API=https://(作成したherokuアプリの名前).herokuapp.com/
VUE_APP_API_TOKEN=xxxxx
.env.development
VUE_APP_BASE_API=http://localhost:3000/
VUE_APP_API_TOKEN=xxxxx

最後にHelloWorld.vueのmountedを編集します
headersにトークンを追加します。

src/components/HelloWorld.vue
  mounted () {
    axios
      .get(process.env.VUE_APP_BASE_API + "users",{
        headers : { "Authorization": "Token " + process.env.VUE_APP_API_TOKEN }
        }).then(response => (this.info = response))
  }

上記で本番環境でもAPI連携が可能になりました。

おわりに

APIサーバ作るのも初めてだったのでなにか不備があればご指摘お願いします。
テスト環境とかも作らなければ…

そのたお世話になった記事

Rails5 APIで認証付きのWebAPIを作ってみる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsで複合主キーをやめるmigration

概要

railsのシステムでは複合主キーは認められてません。全てテーブルがidという自動採番の主キーを持つのが決まりです。

それはわかっていたのですが、多対多の中間テーブル、例えば、モデルで言えば下記ようなものです。

class User < ApplicationRecord
  has_many :user_skills
  has_many :skills, inverse_of: :users, through: :user_skills
end

class Skill < ApplicationRecord
  has_many :user_skills
  has_many :users, inverse_of: :skills, through: :user_skills
end

# これが私のいうところの中間テーブルです。
class UserSkill < ApplicationRecord
  belongs_to :skill
  belongs_to :user
end

これだけは自分の経験上納得がいかないというか、先入観も大きかったと思いますが、下記の理由で複合主キーを使っていました。

  • 必要のないIDの分の無駄な容量が増える
  • 頻繁にすげ替えが行われるとIDが枯渇するんじゃないか?

ただ、それで運用してみていくつかの問題がわかっています。

  • UserSkillをレシーバーにして削除ができない。
  • UserSkillをレシーバーにして更新ができない。
  • UserSkillをeager_loadできない。

他にもあるかもしれませんが、今の所私が把握してるのは上記です。

問題点に関して言えば容量はだいぶ低価格になってきてるし、アクセス速度の早いステレージもだいぶ低価格になってきてます。IDの枯渇に関してはbigintであれば一般的なシステムでは多分問題にならないので気にしなくてもいいかも。

こういうGemもあるのですが、いつまでメンテナンスされるかわからないし、メンテナンスが止まったからといって自分でどうにかならないくらい深いところに手を入れてるGEMだと思いました。

そこでRailsの恩恵を受けた方がいいと思うテーブルに関しては複合主キーをやめてみようと思い、そのmigarationを書いてみました。

class UserSkillToSinglePKey < ActiveRecord::Migration[5.2]
  def change
    remove_foreign_key :user_skill, :skill
    remove_foreign_key :user_skill, :user
    reversible do |change|
      change.up do
        execute 'ALTER TABLE user_skill DROP PRIMARY KEY'
      end

      change.down do
        execute 'ALTER TABLE user_skill ADD PRIMARY KEY (skill_id, user_id)'
      end
    end
    add_column :user_skill, :id, :primary_key, unsigned: true, first: true
    add_foreign_key :user_skill, :skill
    add_foreign_key :user_skill, :user
  end
end

ポイントとしては生SQLのところでreversibleを使ってる点、foreign_keyを外さないと主キーをDROPできないことと、add_column :user_skill, :id, :primary_keyとすると、勝手にAUTOINCREMENTになるところくらいですかね。

もし、運用しているサービスにやるのであれば、テーブルロックがかかるのでレコード数によっては問題になるかもしれません。また、一度外部制約を外すので不整合が起きる可能性があるとは思いますのでその点のも考慮に入れてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む