20200912のRailsに関する記事は17件です。

ActiveHashを解説してみた

ActiveHash

職業選択などの変更されないデータをモデルファイル内に直接記述することで、データベースへ保存せずにデータを取り扱うことができる。つまり、Active_Hashを用いることで、モデルファイルに直接記述した変更されないデータに対して、ActiveRecordのメソッドを用いることができる。

導入方法

Gemfile
qiita.rb
gem 'active_hash'

記述したらbundle installを実行する。

モデル作成時に押さえておくべきこと

結論、--skip-migrationを使用すること。
理由は、データベースを作らない。すなわちマイグレーションファイルは不要となるからだ。ここでrails g modelコマンドを使用してしまうと、マイグレーションファイルも同時に作成されてしまうので注意が必要。

% rails g model モデル名 --skip-migration

ActiveHash::Base

ActiveRecordと同様のメソッドが使用できる。
つまり、ActiveHash::Baseを継承することで、モデルに定義したオブジェクトに対してActiveRecordのメソッドが使用できるようになる。

qiita.rb
class ShippingFee < ActiveHash::Base
  self.data = [
    { id: 0, name: '---' },
    { id: 1, name: '着払い(購入者負担)' }, { id: 2, name: '送料込み(出品者負担)' }
  ]
end

self.dataでテーブルを作成しているイメージ。データは配列にハッシュ形式で格納されている。

belongs_to_active_hash

通常であればbelongs_to :モデル名となるが、ActiveHashを使って作成したモデルに対してアソシエーションを設定する場合は、belongs_to_active_hashメソッドを使用する。

collection_select

データをプルダウン形式で表示することができるメソッド

記述順 詳細 具体例
第一引数 メソッド名 カラム名
第二引数 オブジェクト 配列データの指定
第三引数 id 参照DBのカラム名
第四引数 name 実際のカラム名
第五引数 prompt プルダウンで一番上に表示したい内容
オプション クラス名 --
qiita.rb
<%= f.collection_select(:shipping_fee_id, Shipping_fee.all, :id, :name, {}, {class:"select-box", id:"item-shipping-fee-status"}) %>

実装例

代表的なものをいくつかあげてみた
・都道府県
・職業選択
・クローズドクエスチョン(yes or noで答えられるようなもの)
・アンケート
・カテゴリー
・商品のステータス

個人的にはこれがあることで利用者側のストレスを大幅に減らすことができ、かつ効率的に情報収集をすることができると感じた。
相手に意見を求めたいときなどに使うといいかもしれない。

最後に

ここまで記事を読んでいただき、ありがとうございました。

普段何気なく使っているものにもActiveHashが取り入れられているのですね。
実装も簡単なので、積極的に使っていきたいと感じました。

今後も学習を進めていく中で、役立つ情報をどんどん発信していきたいと思うので、よろしくお願いします。

ここまで記事を読んでいただき、本当にありがとうございました‼

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

【Rails】ActiveRecordのよく使うメソッド集(モデル検索、テーブル結合など)

ActiveRecordには、データベースからオブジェクトを取り出す
検索メソッドが多数用意されています。

検索メソッドはwhereやgroupといったコレクションを返したり、
ActiveRecord::Relationのインスタンスを返します。

またfindやfirstなどの1つのエンティティを検索するメソッドの場合、
そのモデルのインスタンスを返します。

単一のオブジェクトを取り出す

find

主キーで検索

# 主キーが1のユーザーを検索

User.find(1)

# SELECT * FROM users WHERE (users.id = 1) LIMIT 1

findメソッドで複数のオブジェクトを取得

# 主キーが1と10のユーザーを検索

User.find(1,10) # User.find([1,10])も可

# SELECT * FROM users WHERE (users.id IN (1,10))

findメソッドでマッチするレコードが見つからない場合、
ActiveRecord::RecordNotFound例外が発生します。

first

デフォルトでは主キー順の最初のレコードを取得

User.first

# SELECT * FROM users ORDER BY users.id ASC LIMIT 1

firstメソッドで返すレコードの最大数を指定して取得

User.first(3)

# SELECT * FROM users ORDER BY users.id ASC LIMIT 3

orderを使って順序を変更した場合、
firstメソッドはorderで指定された属性にしたがって最初のレコードを取得

User.order(:name).first

# SELECT * FROM users ORDER BY users.name ASC LIMIT 1

firstメソッドは、モデルにレコードが1つもない場合にnilを返します。
このとき例外は発生しません。

last

デフォルトでは主キーの順序にしたがって最後のレコードを取得

User.last

# SELECT * FROM users ORDER BY users.id DESC LIMIT 1

lastメソッドは、モデルにレコードが1つもない場合にnilを返します。
このとき例外は発生しません。

find_by

与えられた条件にマッチするレコードのうち、最初のレコードを取得

User.find_by(name: "hoge")

# SELECT * FROM users WHERE (users.name = "hoge") LIMIT 1

find_byメソッドは、モデルにレコードが1つもない場合にnilを返します。
このとき例外は発生しません。

条件

where

返されるレコードを制限するための条件を指定します。
SQL文でいうWHEREの部分に相当します。

User.where(name: "hoge")

# SELECT * FROM users WHERE (users.name = "hoge")

・NOT条件
SQLのNOTクエリはwhere.notで表せます。

User.where.not(name: "hoge")

# SELECT * FROM users WHERE (users.name != "hoge")

・OR条件
1つ目のリレーションでorメソッドを呼び出し、そのメソッドの引数に
2つ目のリレーションを渡します。

User.where(name: "hoge").or(User.where(name: "foo"))

# SELECT * FROM users WHERE (users.name = "hoge") OR (users.name = "foo") 

並び順

データベースから取り出すレコードを特定の順序で並び替え

# ひとかたまりのレコードを取り出し、created_atの昇順で並び替え

User.order(:created_at)

# SELECT * FROM users ORDER BY users.created_at ASC
複数のフィールドを指定して並び替え

User.order(name: :asc, created_at: :desc)

# SELECT * FROM users ORDER BY users.name ASC, users.created_at DESC

取り出すレコード数の上限を指定

limit

主キー順の最初から数えて5件のレコードを取得

User.limit(5)

# SELECT * FROM users LIMIT 5

テーブルの結合

joins

デフォルトは内部結合(INNER JOIN)
内部結合は「結合条件に一致するレコードのみを取得」
この場合だとpostsテーブルのuser_idと、usersテーブルのidを指定

# モデル.joins(:関連名)
User.joins(:posts)

# SELECT users.* FROM users INNER JOIN posts ON (posts.user_id = users.id) 

left_joins

左外部結合(LEFT OUTER JOIN)
「結合条件に一致するレコード」と「左側のテーブルにしかないレコード」を取得

User.left_joins(:posts)

# SELECT users.* FROM users LEFT OUTER JOIN posts ON (posts.user_id = users.id) 

関連付けを一括読み込みする

includes

一括読み込み(eager_loading)とは、Model.findによって返されるオブジェクトに関連付けられたレコードを読み込むためのメカニズムであり、できるだけクエリの回数を減らすようにします。

・N+1クエリ問題
以下のコードは、ユーザーを10人検索して郵便番号を表示します。
usersテーブルとaddressesテーブルの関係は1対1とします。

users = User.limit(10)

users.each do |user|
  puts user.address.postcode
end

このコードには問題があります。実行されたクエリの数が無駄に多いことです。
最初にユーザーを10人検索するのにクエリを1回発行し、次にそこから住所を取り出すのに
クエリを10回発行するので、合計11回のクエリが発行されます。

・N+1クエリ問題を解決する
ActiveRecordは、読み込まれるすべての関連付けを事前に指定することができます。
これはincludesを指定することで実現できます。
includesを指定すると、ActiveRecordは指定されたすべての関連付けが最小限のクエリ回数で
読み込まれるようにしてくれます。

上の例でいうと、User.limit(10)を書き直して、住所が一括で読み込まれるようにします。

users = User.includes(:address).limit(10)

users.each do |user|
  puts user.address.postcode
end

最初の例では11回もクエリが発行されましたが、今回の例では2回にまで減りました。

SELECT * FROM users LIMIT 10
SELECT addresses.* FROM addresses 
  WHERE (addresses.user_id IN (1,2,3,4,5,6,7,8,9,10))

参考

Railsガイド

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

mailcatcherが起動しない場合にやること

はじめに

現在、「現場で使えるRuby on Rails5 速習実践ガイド」いわゆる、現場Railsをざっと通して学習しています。途中でmailcatcherを使うシーンが出てくるのですが、そこで詰まったのでメモとして置いておきます。

そもそもmailcatcherって

https://mailcatcher.me/

(訳)公式サイトの訳です
mailcatcherは、ウェブインターフェースから送信されるあらゆるメッセージをキャッチしてくれるシンプルなsmtpサーバです。mailcatcherを起動し、検証したいアプリの送信先をsmtp://127.0.0.1:1025に設定することで、mailcatcherで用意したブラウザでメールを確認することができます。

言ってしまえば、シンプルなメール検証gemです。

インストールと設定

インストールはgem installを公式が推奨しています。

$gem install mailcatcher

インストール後以下の設定を行います。(公式のrails欄を引用)

environments/development.rb
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = { :address => '127.0.0.1', :port => 1025 }
config.action_mailer.raise_delivery_errors = false #ここはすでに記載がある箇所

起動方法とトラブルシューティング

この設定を行えばmailcatcherで起動できると公式にも書いてあるのですが、

$ mailcatcher
-bash: mailcatcher: command not found

コマンドが見つかりませんになってしまいました。

(1)rbenv rehash

$ rbenv rehash
rbenv: cannot rehash: /Users/local/.rbenv/shims/.rbenv-shim exists

ファイルが存在してるためrehashできないと言われる。

(2)rails sを止める
これは完全に凡ミスでした。sを止めないとそもそもrbenvができない。

(3)ファイルの削除
rehashができない原因のファイルの削除を行う

$rm /Users/local/.rbenv/shims/.rbenv-shim

改めてrehashすると、通ったようなので、改めて

$ mailcatcher
Starting MailCatcher
==> smtp://127.0.0.1:1025

無事に通りました!

参考サイト様

以下を参考にさせていただきました。
ありがとうございます。
https://k-koh.hatenablog.com/entry/2020/02/20/095445
https://qiita.com/ironsand/items/2f9f20e6e77ab1877160

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

RailsでJavaScript(バニラ)が反応しない。

rails5では初期からjQueryのgemが導入されており、削除しない限りはデフォルトで利用できる状態です。
Railsを学び始めて、いつの間にか素のJavaScript(以降”バニラ”)よりもjQueryを使う方が慣れてしまっていたので、今回は改めてバニラを学習するためあえてjQueryを使わない方法を模索しました。

環境

Ruby 2.5.7
Rails 5.2.4

経緯

改めてバニラでコードを書き始めると割とハマるところが多くありました。
jQueryで書けるならそれでいいと言われればそれまでなのですが、そのままにしておくのは気持ち悪かったので少し向き合うことにしました。

即時関数が効かない

手始めにクリックイベントでコンソールログを確認しようとしました。
jQueryでは下記のように$(function() {処理});
という書き方をする必要があります。(理由は後述します。)

application.js
// jQuery
$(function() {
  $("セレクタ名").on('クリック', function() {
    console.log('クリックされました。');
  });
});

jQueryを使わないバニラの即時関数は下記のような書き方になります。

application.js
// バニラ
(function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
}());

上記二つのコードはどちらも処理は同じなのですが、バニラの方はなぜかエラーになり、コンソールログに出力されません。。。

以前はここでjQueryに切り替えていたのですが、どうしても解決したくて今回の模索に至りました。

なぜjQueryは動いて、バニラは動かないのか?

それは$(function() {});に隠されていました。(隠されていません。)
$(function() {});は省略された書き方で、本来の形は下記のようになります。

application.js
$(document).ready(function {
 //処理
});

readyメソッドはHTMLの読み込みが終わったタイミングで中の処理が行われるメソッドです。
つまりjsの処理を全て$(function() {処理});の中に書くことで、何も考えなくても処理を行うことができるということになります。

バニラではこの"htmlの読み込み待ち"をjQueryを使わずに書く必要があるということです。

DOMContentLoaded

バニラではaddEventListenerメソッドのイベントにDOMContentLoadedを指定することで、jQueryのreadyメソッドと同じことができるようになり、コードは以下のようになります。

application.js
window.addEventListener('DOMContentLoaded', function() {
  // 処理
});

バニラでクリックイベントをするには?

ここで最初のjQueryとバニラを比較したコードを振り返ってみます。

application.js
// jQery
$(function() {
  $("セレクタ名").on('click', function() {
    console.log('クリックされました。');
  });
});

このjQueryは省略せずに書くと

application.js
// jQery
$(document).ready(function() {
  $("セレクタ名").on('クリック', function() {
    console.log('クリックされました。');
  });
});

という感じになります。

次にバニラをみていきます。

application.js
// バニラ
(function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
}());

これは即時関数なのですが、実はこの書き方でも動作する場合もあります。

方法1.外部jsファイル読み込みのscriptタグをbody要素の一番下に記述する。

通常、Railsでは外部jsファイルの読み込みタグはheadタグの中に書かれていることが多いです。
しかし、headの中でjsファイルを読み込んでしまうと、bodyの中にあるエレメントをとる前にjsの関数が発動してしまうため、エラーになってしまいます。
なので、外部jsファイルの読み込みタグをbodyタグの最後に書く方法です。

application.html.erb
<head>
...
<%#= javascript_include_tag 'application' %>
...
<head>
<body>
...
<%= javascript_include_tag 'application' %>
</body>

こうすることで、bodyが読み込み終わったタイミングでjsファイルが読み込みを開始するので、要素の読み込みもできるということになります。
しかし、そのほかの外部ファイルやサービス、APIの読み込み設定などは全てheadタグ内に書かれるので、できれば外部jsファイルの読み込みもhead内で統一したいところです。

方法2.DOMContentLoadedを使う

先ほども触れましたが、addEventListenerメソッドのDOMContentLoadedイベントを使う方法です。
jsの外部ファイル読み込みタグはhead内のままに、jsファイルの方だけを書き換える必要があります。

application.js
// バニラ
window.addEventListener('DOMContentLoaded', function() {
  document.getElementById("セレクタ名").addEventListener('click', function() {
    console.log('クリックされました。');
  });
});

これでHTMLが読み込まれてからfunctionが実行される形になったので、問題なく動作するようになりました。

注意

ちなみに、DOMContentLoadedイベントはHTMLの読み込み完了を待つだけなので、cssや画像ファイルの読み込みは含まれていません。
画像やcssの操作がある場合はaddEventListenerメソッドのloadイベントを使えば、全ての読み込みが完了してからfunctionを発火させることができます。

まとめ

ここまで散々バニラで記述する方法をお伝えしてきましたが、jQueryと相性がいいならjQueryのままでも良いと、個人的には思います。
実際バニラを書いてみて、jQueryならどれほど簡単に書くことができるのかがわかったので・・・笑
これはRailsにも標準搭載されているという観点からも推察できます。

質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

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

参考サイト

JavaScriptで即時関数を使う理由
【jQuery】$(function() {...}) について 「意味や実行されるタイミング」
DOMContentLoadedイベントとloadイベントの違い[タイミング]

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

(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第7章】

前提

・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者

基本方針

・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。

第7章はログインと認証システムの開発・第2段回目、ユーザー登録機能を追加していきます。

雨が激しい昼刻(執筆時)、本日のBGMはこちらです。
羊文学 "Blue.ep"
再び羊文学。最近のお気に入り。一曲目の「雨」、いいかんじです。

 

【7.1.1 デバッグとRails環境 演習】

1. ブラウザから /about にアクセスし、デバッグ情報が表示されていることを確認してください。このページを表示するとき、どのコントローラとアクションが使われていたでしょうか? paramsの内容から確認してみましょう。
→ controller: static_pages
  action: about

 
2. Railsコンソールを開き、データベースから最初のユーザー情報を取得し、変数userに格納してください。その後、puts user.attributes.to_yamlを実行すると何が表示されますか? ここで表示された結果と、yメソッドを使ったy user.attributesの実行結果を比較してみましょう。
→ 下記。この書き方がYAMLということ。結果は一緒です。

>> user = User.first
  User Load (0.6ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "nakamura", email: "mhartl@example.com", created_at: "2020-09-10 02:37:56", updated_at: "2020-09-10 03:07:11", password_digest: "$2a$10$A5n.HFBigQfwnWVJZw2N0e4M9sxPaR8ndLZwqtZWYS7...">

>> puts user.attributes.to_yaml
---
id: 1
name: nakamura
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2020-09-10 02:37:56.040628000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2020-09-10 03:07:11.190666000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$A5n.HFBigQfwnWVJZw2N0e4M9sxPaR8ndLZwqtZWYS7gJGH/Ulohe"
=> nil

>> y user.attributes
---
id: 1
name: nakamura
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2020-09-10 02:37:56.040628000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2020-09-10 03:07:11.190666000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$A5n.HFBigQfwnWVJZw2N0e4M9sxPaR8ndLZwqtZWYS7gJGH/Ulohe"
=> nil

 

【7.1.2 Usersリソース 演習】

1. 埋め込みRubyを使って、マジックカラム (created_atとupdated_at) の値をshowページに表示してみましょう (リスト 7.4)。
2. 埋め込みRubyを使って、Time.nowの結果をshowページに表示してみましょう。ページを更新すると、その結果はどう変わっていますか? 確認してみてください。
→ まとめてドン!(くっついて表示されるのがうっとうしかったのでpタグで改行してます) ページ更新すると現時刻が更新されて表示されます。

show.html.erb
<p>
  <%= @user.name %>, <%= @user.email %>
</p>
<p>
  <%= @user.created_at %>, <%= @user.updated_at %>
</p>
<p>
  <%= Time.now %>
</p>

 

【7.1.3 debuggerメソッド メモと演習】

 よく分からない挙動があったら、トラブルが起こっていそうなコードの近くにdebeggerを差し込もう!

1. showアクションの中にdebuggerを差し込み (リスト 7.6)、ブラウザから /users/1 にアクセスしてみましょう。その後コンソールに移り、putsメソッドを使ってparamsハッシュの中身をYAML形式で表示してみましょう。ヒント: 7.1.1.1の演習を参考にしてください。その演習ではdebugメソッドで表示したデバッグ情報を、どのようにしてYAML形式で表示していたでしょうか?
→ 下記

(byebug) puts @user.attributes.to_yaml
---
id: 1
name: nakamura
email: mhartl@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &1 2020-09-10 02:37:56.040628000 Z
  zone: &2 !ruby/object:ActiveSupport::TimeZone
    name: Etc/UTC
  time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
  utc: &3 2020-09-10 03:07:11.190666000 Z
  zone: *2
  time: *3
password_digest: "$2a$10$A5n.HFBigQfwnWVJZw2N0e4M9sxPaR8ndLZwqtZWYS7gJGH/Ulohe"
nil

 
2. newアクションの中にdebuggerを差し込み、/users/new にアクセスしてみましょう。@userの内容はどのようになっているでしょうか? 確認してみてください。
→ 下記

(byebug) @user
Started GET "/users/new" for 49.104.6.138 at 2020-09-10 06:51:27 +0000
#<User id: 1, name: "nakamura", email: "mhartl@example.com", created_at: "2020-09-10 02:37:56", updated_at: "2020-09-10 03:07:11", password_digest: "$2a$10$A5n.HFBigQfwnWVJZw2N0e4M9sxPaR8ndLZwqtZWYS7...">

 

【7.1.4 Gravatar画像とサイドバー 演習】

 ここでエラーが。Gravatarの画像が表示されない。念のため手書きしたコードをコピペで上書きしても表示されない。ということは…。
 SCSSでdisplay: none;でもしてたか?と思いつつ、検索してみるとやっぱり。第5章の演習で画像を消したときのが残ってました。kittenの仕業じゃ!!でも可愛いから許す!!imgタグのコードを消したら解決しました。

1. (任意) Gravatar上にアカウントを作成し、あなたのメールアドレスと適当な画像を紐付けてみてください。メールアドレスをMD5ハッシュ化して、紐付けた画像がちゃんと表示されるかどうか試してみましょう。
→ 画像登録して、コンソールで新しいユーザー作ったら表示されました。
gravatar.png

 
2. 7.1.4で定義したgravatar_forヘルパーをリスト 7.12のように変更して、sizeをオプション引数として受け取れるようにしてみましょう。うまく変更できると、gravatar_for user, size: 50といった呼び出し方ができるようになります。重要: この改善したヘルパーは10.3.1で実際に使います。忘れずに実装しておきましょう。
→ リスト7.12のとおり書くだけ。

 
3. オプション引数は今でもRubyコミュニティで一般的に使われていますが、Ruby 2.0から導入された新機能「キーワード引数 (Keyword Arguments)」でも実現することができます。先ほど変更したリスト 7.12を、リスト 7.13のように置き換えてもうまく動くことを確認してみましょう。この2つの実装方法はどういった違いがあるのでしょうか? 考えてみてください。
→ んー、いろいろ調べたけど挙動の違いがいまいち分からない。表記を簡潔にできるのがメリットだってのは分かった。それぞれの用語の意味も調べてわかった。

キーワード引数:引数にキーを設定した引数。何を引数に入れてるのか明確。
オプション引数:柔軟にいろいろな値を渡せる。拡張性が高い。が、ゆえに渡す値が増えると後々のメンテが大変に。可読性も下がる。

 んで、キーワード引数は後のバージョンから導入されたということは、コードを簡潔に書くことが目的にあるのか。オプション引数の方の書き方だと、sizeの定義で余分にコード書いてるもんね。そんな回答で良かろうか。

 

【7.2.1 form_forを使用する 演習】

1. 試しに、リスト 7.15にある:nameを:nomeに置き換えてみましょう。どんなエラーメッセージが表示されるようになりますか?
→ undefined method `nome' for #User:0x00007f6b8d40b370
 
2. 試しに、ブロックの変数fをすべてfoobarに置き換えてみて、結果が変わらないことを確認してみてください。確かに結果は変わりませんが、変数名をfoobarとするのはあまり良い変更ではなさそうですね。その理由について考えてみてください。
→ 単純に書くのがめんどくさいのと、fはformのfでしょ。脈絡のない単語を使うと読みにくくなる。(証拠は貼ってないけど、わざわざ全部foobarに書き直したよ!)

 

【7.2.2 フォームHTML 演習】

1. Learn Enough HTML to Be DangerousではHTMLをすべて手動で書き起こしていますが、なぜformタグを使わなかったのでしょうか? 理由を考えてみてください。
→ 読んでへんから知らんけど、入力・送信を使用してないからですって。(そら使わんやろ)

 

【7.3.2 Strong Parameters メモと演習】

 7.3.1で出てきたマスアサインメントの脆弱性はこちらの記事が分かりやすいかも。

1. /signup?admin=1 にアクセスし、paramsの中にadmin属性が含まれていることをデバッグ情報から確認してみましょう。
→ これか。
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
admin: '1'
controller: users
action: new
permitted: false

  

【7.3.3 エラーメッセージ メモと演習】

empty?メソッド:オブジェクトが空であればtrue、それ以外はfalse
any?メソッド:要素が一つでもあればtrue、ない場合false
pluralizeメソッド:単語の意味は「複数(形)にする」。意味のとおり、来所に与えた引数の整数に基づいて、後に与えた引数の英単語を複数形にしてくれる。

1. 最小文字数を5に変更すると、エラーメッセージも自動的に更新されることを確かめてみましょう。
→ 下記。Password is too short (minimum is 5 characters)が表示されます。

/models/user.rb
validates :password, presence: true, length: { minimum: 5 }

 
2. 未送信のユーザー登録フォーム (図 7.12) のURLと、送信済みのユーザー登録フォーム (図 7.18) のURLを比べてみましょう。なぜURLは違っているのでしょうか? 考えてみてください。
→ 送信前: ~/signup  ※ ~はAWSのアドレス
  送信後: ~/users
 ユーザー登録のためにPOSTリクエストしてるから、usersリソースに応じて/usersに飛んでるって理解でいいかな?(チュートリアル中の表7.1参照)

 

【7.3.4 失敗時のテスト メモと演習】

 countメソッドはあらゆるActive Recordクラスで使用可能。
 ここらへんからテストの内容がややこしくなってきます。一つ一つのコードの意味を追っていきましょう。ここの演習はヘビーですが、じっくり考えてみます。(間違っててもご容赦を)

1. リスト 7.20で実装したエラーメッセージに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.25にテンプレートを用意しておいたので、参考にしてください。
→ テンプレに従うと、エラーメッセージのCSSidとclassが表示されているかテストするわけだから、該当箇所を下記のとおり書き換えればテストはGREENです。

users_signup_test.rb
assert_select 'div#error_explanation'
assert_select 'div.field_with_errors'

 
2. ユーザー登録フォームのURLは /signup ですが、無効なユーザー登録データを送付するとURLが /users に変わってしまいます。これはリスト 5.43で追加した名前付きルート (/signup) と、RESTfulなルーティング (リスト 7.3) のデフォルト設定との差異によって生じた結果です。リスト 7.26とリスト 7.27の内容を参考に、この問題を解決してみてください。うまくいけばどちらのURLも /signup になるはずです。あれ、でもテストは greenのままになっていますね...、なぜでしょうか? (考えてみてください)
→ リスト7.26のルーティング設定によって、登録失敗後に2つの目的地がある状態(URLが/usersと/signup)。それにより、どっちもテストで検知できる状態なので、REDにならない。

 
3. リスト 7.25のpost部分を変更して、先ほどの演習課題で作った新しいURL (/signup) に合わせてみましょう。また、テストが greenのままになっている点も確認してください。
→ users_pathをsignup_pathに変更。テストで検知するものを変えただけなのでGREENのまま。

users_signup_test.rb
  test "invalid signup information" do
    get signup_path
    assert_no_difference 'User.count' do
      post signup_path, params: { user: { name: "",
  # (以下略)

 
4. リスト 7.27のフォームを以前の状態 (リスト 7.20) に戻してみて、テストがやはり greenになっていることを確認してください。これは問題です! なぜなら、現在postが送信されているURLは正しくないのですから。assert_selectを使ったテストをリスト 7.25に追加し、このバグを検知できるようにしてみましょう (テストを追加して redになれば成功です)。その後、変更後のフォーム (リスト 7.27) に戻してみて、テストが green になることを確認してみましょう。ヒント: フォームから送信してテストするのではなく、'form[action="/signup"]'という部分が存在するかどうかに着目してテストしてみましょう。
→ form_for(@user, url: signup_path)から「, url: signup_path」を取り除く。この状態で実際に登録失敗するとURLは/usersに。これじゃダメだよってことで、検知できるように下記の一文をテストに追加。結果はREDに。そこでformの目的地を再び/singupにすべくリスト7.27の状態に戻すと、テストはGREEN。
 理解するのに時間がかかりました。演習2が理解できないと、その続きも理解できませんね。あきらめず理解できるまで調べて考えて解いていきましょう。

users_signup_test.rb
assert_select 'form[action="/signup"]'

 

【7.4.1 登録フォームの完成 メモと演習】

 redirect_to @user → redurect_to user_url(@user)
とRailsfが勝手に解釈して実行してくれる。ここで、redirect_toとrederの違いって何なん?って気になりませんか?気になりますよね。気になったので調べました。
 redirec_toはURLを指定するため、ルーターをとおす一連の動作を行っているみたいですね。データ更新等がある場合に使用。renderは直でviewを表示していると。データ変更を伴わない単純な処理の場合です。

1. 有効な情報を送信し、ユーザーが実際に作成されたことを、Railsコンソールを使って確認してみましょう。
→ 実際の登録画面からユーザー登録し、コンソールで適当にfindすればOK。

2. リスト 7.28を更新し、redirect_to user_url(@user)とredirect_to @userが同じ結果になることを確認してみましょう。
→ 同じ結果になります。
 

【7.4.2 flash 演習】

1. コンソールに移り、文字列内の式展開 (4.2.2) でシンボルを呼び出してみましょう。例えば"#{:success}"といったコードを実行すると、どんな値が返ってきますか? 確認してみてください。
→ 下記。文字列が返ってきます。

>> "#{:success}"
=> "success"

 
2. 先ほどの演習で試した結果を参考に、リスト 7.30のflashはどのような結果になるか考えてみてください。
→ いまいち何を求めているのか分からないけど、こういうこと?

>> flash = { success: "It worked!", danger: "It failed." }
=> {:success=>"It worked!", :danger=>"It failed."}
>> "#{flash[:success]}"
=> "It worked!"
>> "#{flash[:danger]}"
=> "It failed."

 

【7.4.3 実際のユーザー登録 演習】

1. Railsコンソールを使って、新しいユーザーが本当に作成されたのかもう一度チェックしてみましょう。結果は、リスト 7.32のようになるはずです。
→ やってみるだけ。

 
2. 自分のメールアドレスでユーザー登録を試してみましょう。既にGravatarに登録している場合、適切な画像が表示されているか確認してみてください。
→ これもやってみるだけ。

 

【7.4.4 成功時のテスト 演習】

1. 7.4.2で実装したflashに対するテストを書いてみてください。どのくらい細かくテストするかはお任せします。リスト 7.34に最小限のテンプレートを用意しておいたので、参考にしてください (FILL_INの部分を適切なコードに置き換えると完成します)。ちなみに、テキストに対するテストは壊れやすいです。文量の少ないflashのキーであっても、それは同じです。筆者の場合、flashが空でないかをテストするだけの場合が多いです。
→ FILL_INにはempty?メソッドを入れればOK。

2. 本文中でも指摘しましたが、flash用のHTML (リスト 7.31) は読みにくいです。より読みやすくしたリスト 7.35のコードに変更してみましょう。変更が終わったらテストスイートを実行し、正常に動作することを確認してください。なお、このコードでは、Railsのcontent_tagというヘルパーを使っています。
→ 指示通り実行。

 
3. リスト 7.28のリダイレクトの行をコメントアウトすると、テストが失敗することを確認してみましょう。
→ 失敗します。

 
4. リスト 7.28で、@user.saveの部分をfalseに置き換えたとしましょう (バグを埋め込んでしまったと仮定してください)。このとき、assert_differenceのテストではどのようにしてこのバグを検知するでしょうか? テストコードを追って考えてみてください。
→ 下記のエラーが。ユーザー数増えてねえよってことか。

 "User.count" didn't change by 1.
        Expected: 1
          Actual: 0

 

【7.5.3 成功時のテスト 演習】

1. ブラウザから本番環境 (Heroku) にアクセスし、SSLの鍵マークがかかっているか、URLがhttpsになっているかどうかを確認してみましょう。
→ 鍵かかってました。

 
2. 本番環境でユーザーを作成してみましょう。Gravatarの画像は正しく表示されているでしょうか?
→ 表示されてました。

 

第7章まとめ

・やっぱりSass便利。
・RESTfullなルートと個別設定のルートの違い・使い分けに気をつけよう。
・Gravatarってどこまで一般的なの?wordpressやから普及してるの?
・form_forはform_withに置き換わってるようなので参考までに。
・renderとredirect_toの使い分け。
・flashメッセージを表示。
・Strong Parametersでマスアサインメントの脆弱性対策。
・SSLでセキュリティ向上。

 
 この章は演習が歯応えありました。なぜそうなるのか、一つ一つのコードと動作を考える必要がありますね。考えることをあきらめずに取り組んでいきましょう。
 次は第8章、ログイン機構を備えていきましょう。

 
⇦ 第6章はこちら
学習にあたっての前提・著者ステータスはこちら
 

なんとなくイメージを掴む用語集

・assert_difference(assert_no_difference)
 (この章の使い方で想定すると)引数で与えられた数値(結果)が、ブロックの処理を実行する前後で違いがあるかどうかテストする。前者が違いがある、no_differenceは違いがないことを確かめている。

・SSL(Secure Sockets Layer)
 セキュリティを要求される通信を行うためのプロトコル。現在はTLS(Transport Layer Security)に置き換わっているが、昔の名残でSSLと呼ばれる。

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

<超初学者向け>「Talk API」を利用してチャットボットを作ってみませんか??【Ruby on Rails】

◆この記事の対象者

☆Rails on Railsを学び始めた初学者
☆ハンズオン形式でLINEBOTを作成してみたい方
☆WEBシステム開発に挫折してしまった方(※フロントエンド側の挫折)

◆はじめに

こんにちは!タイトルの通り、チャットボットを開発したので、その過程を記載しています。
この記事を投稿する経緯にもつながりますが、昔の僕はProgateを1周して、さっそくWEB開発を進めていこうとやる気に満ちていました。しかし、必然であったかのように挫折します。原因は、「フロントエンド側まで手が回らない」でした。
バックエンドのプログラミング言語を学ぶ目的で、Ruby、Railsの基礎を学び、理解度を深めるために何か作ってみようぐらいの気持ちなのに、そこに新たに「フロントエンド」という壁が生まれるのはしんどかったです。Bootstrapを利用して極力手抜きにしようと思いましたが、僕はそれにすら耐えられない、ゆとり世代なんです。
一度挫折して、心を入れ替えて再挑戦しましたが、2度目の挫折を味わいました、、、そんな時に目を付けたのがLINEBOTです。

「LINEBOTの開発はフロントエンドを意識する必要がないし、LINEのアプリで成果をすぐに確認できる点も、何かやった気になりたい僕にはぴったりじゃん!!」と思いましたw

この記事は、「とりあえずこの記事と同じ環境で同じ作業をすると誰でも成果物の作成ができますよ!」という点に特化しています。
それなので技術的な内容の説明は薄いです。(そもそも僕も初学者なので、詳しい説明はできないです。)

フロントエンドがネックでWEB開発に挫折してしまった方にこの記事を読んでもらいたいです。一緒にチャットボットを作ってみませんか??

◆前提条件

以下に記載していることを前提に記事を作成しています。

・AWSに登録できること(クレジットカード情報の登録が必須になります。)
※僕がCloud9を使用していたので前提条件にしました。ローカルに開発環境を利用しても問題ありませんが、初学者に環境問題のエラーで苦しんでほしくないので、Cloud9を利用することをお勧めします。

◆作業手順

◇LINE Developersに登録してLINE BOT用のチャネルを作成する

pic008.png

①LINE Developersに登録、チャネルを作成


ここの説明は省略させてもらいます。以下に載せているURLから公式サイトのページに飛べます。これ確認しながら作業してください。チャネルの名前は何でも問題ありませんが、友達登録したときに表示される名前です。こだわりがなければ「チャットボット君」でいいと思います。。
https://developers.line.biz/ja/docs/messaging-api/getting-started/#using-console

ここまででチャネルの作成が完了していると思うので、引き続きチャネルの設定を変更していきます。

②チャネルアクセストークン(長期)発行

赤枠内のボタン通してアクセストークンを発行してください。
pic001.png
チャネルアクセストークンとは??となっている方は以下のページで確認してください。
https://developers.line.biz/ja/docs/messaging-api/channel-access-tokens/
つまりまとめると、「アプリからAPIを呼び出すための情報」です。

③応答設定の変更

次は応答設定を変更していきます。ひとまず何も考えず、画像の通り設定変更してください。
pic002.png
pic003.png
Webhookとは??という好奇心旺盛な方はこの記事を読んでみてください。「子供でも分かる」というのが大げさでないくらい、丁寧に教えてもらえます。
https://kintone-blog.cybozu.co.jp/developer/000283.html

◇「Talk API」のAPIキーを取得する

タイトルにある通り、株式会社リクルートテクノロジーズさんが開発した「Talk API」というAPIを利用してチャットボットを開発しています。オウム返しやアンケートBOTでもよかったのですが、APIを利用してみたい!!という初学者である僕なりの目線でチャットボットにしました。

①公式サイトにアクセスしてAPIキー発行

APIを使用するにはAPIキーというものが必要になります。今回はメールアドレスの登録のみでキーが取得できます。
以下の公式サイトから特に詰まることなく取得できると思うので、取得してみてください。
https://a3rt.recruit-tech.co.jp/product/talkAPI/
pic004.png

開発環境の構築

①AWSアカウントの作成

ここも公式サイトにまとめられているので、詳細はこちらで確認して作業をしてください。
https://aws.amazon.com/jp/register-flow/

②Cloud9で開発環境を作成


基本的にはデフォルトのままで問題ありませんが、プラットフォームのみ「Ubuntu Server 18.04 LTS」に変更してください。
pic009.png

次からはチャットボットを実装するため、コードを修正していきます。ここで作成した開発環境を利用して進めていってください。

◇チャットボットの開発

①PostgreSQL用パッケージのインストール

今回は無料利用枠で利用できるHerokuにデプロイするため、データベースはPostgreSQLを利用します。以下コマンドを実行してください。

bash
sudo apt install libpq-dev

②新規Railsプロジェクトの作成

以下コマンドでインストールするデータベースを指定してプロジェクトを作成してください。

bash
rails new <プロジェクト名> -d postgresql

③LINEBOT用のコントローラを作成

"②"の手順で作成したプロジェクトの階層に移動して、以下コマンドを実行してください。

bash
rails generate controller linebot

④Gemfileの修正

以下をGemfileの末尾に追加して、「bundle install」を実行してください。

Gemfile
gem 'line-bot-api'
gem 'dotenv-rails'
bash
bundle install

⑤ルーティング設定

対象のファイルに以下内容をコピペしてください。

config/routes.rb
Rails.application.routes.draw do
  post '/callback' => 'linebot#callback'
end

⑥コントローラ設定
対象のファイルに以下内容をコピペしてください。

app/controllers/linebot_controller.rb
class LinebotController < ApplicationController
  require 'line/bot'

  protect_from_forgery except: [:callback]

  def client
    @client ||= Line::Bot::Client.new do |config|
      config.channel_secret = ENV['LINE_CHANNEL_SECRET']
      config.channel_token = ENV['LINE_CHANNEL_TOKEN']
    end
  end

  def callback
    body = request.body.read

    signature = request.env['HTTP_X_LINE_SIGNATURE']
    head :bad_request unless client.validate_signature(body, signature)

    events = client.parse_events_from(body)

    events.each do |event|
      case event
      when Line::Bot::Event::Message
        case event.type
        when Line::Bot::Event::MessageType::Text
          require 'net/http'
          require 'uri'
          require 'json'

          key = ENV['TALK_API_KEY']
          uri = 'https://api.a3rt.recruit-tech.co.jp/talk/v1/smalltalk'

          params = { apikey: key, query: event.message['text'] }
          uri = URI.parse(uri)
          response = Net::HTTP.post_form(uri, params)

          data = JSON.parse(response.body)

          message = {
            type: 'text',
            text: data['results'][0]['reply']
          }
          client.reply_message(event['replyToken'], message)
        end
      end
    end

    head :ok
  end
end

◇Gitのセットアップをする

①インストール状態の確認

以下コマンドを実行して、Gitのインストール状態を確認します。インストールされていない場合はインストールしてください。

bash
git --version

②Gitの設定を追加

以下コマンドを実行して、Gitの設定を追加してください。

bash
git config --global user.name "ユーザ名"
git config --global user.email "メールアドレス"
git init

◇Herokuにアプリを作成する

⓪Herokuのアカウント作成

今回は作成したアプリをHerokuにデプロイします。まずは以下サイトからアカウントを作成してください。
https://id.heroku.com/login

①Herokuのインストール


以下コマンドでインストール状態を確認してください。
bash
heroku --version

インストールされていない場合は以下コマンドを実行して、再度インストール状態を確認してください。

bash
curl https://cli-assets.heroku.com/install-ubuntu.sh | sh

②Herokuにログイン

以下コマンドを実行して、ログインします。Herokuに登録した際のメールアドレスとパスワードを求められるので、入力してください。

bash
heroku login --interactive

③アプリ作成

以下コマンドを実行して、アプリを作成します。アプリケーション名は省略するとランダムな名前で作成されます。

bash
heroku create <アプリケーション名>

※ここでアプリのURLが作成されるので、控えておいてください。

◇チャネルの設定にアプリの情報を追加する

①先ほど控えたURLをチャネルの設定に追加

②callbackアクションを呼び出すため、URLの末尾に/callbackを追加

pic005.png

◇Herokuにデプロイ

①環境変数の追加

以下コマンドを実行して環境変数を追加します。

bash
heroku config:set LINE_CHANNEL_SECRET=チャネル設定画面で確認できるChannel Secret
heroku config:set LINE_CHANNEL_TOKEN=チャネル設定画面で確認できるアクセストークン
heroku config:set TALK_API_KEY=Talk APIの利用申請で取得したキー

②Herokuにデプロイ

以下コマンドを実行してHerokuにデプロイしてください。

bash
git add .  
git commit -m "linebot"
git push heroku master

◇動作確認してみよう

①友達追加
QRコードから作成したチャットボットを友達追加してください。
pic006.png

②動作確認
以下のようにメッセージに応じて返信が帰ってきたら成功です!!
pic007.png

◆参考にした記事

僕がLINEBOTを作成していた時に、とても有益な情報となった記事や資料を紹介します。

□【Rails】1時間ぐらいで簡単にLINEのBot開発をしよう-アンケート集計Bot基礎-【画像付き】
https://qiita.com/noriya1217/items/00d6461e9f54900377a3
□LINE Messaging API SDKリポジトリ(Ruby)
https://github.com/line/line-bot-sdk-ruby
□RubyのHTTPリクエストをできるだけシンプルに実装する
https://qiita.com/takano-h/items/dd10818eb7e09161bc29

最後に

最後まで見ていただき、ありがとうございます。気になる点などはコメントで指摘いただけるとありがたいです。

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

mailcatcher はなぜthinサーバを使っているのか

mailcatcher とは

開発環境でメール送信するとlocalhostでメールを受け取り、受け取ったメールを読むためのWEBインターフェースを提供してくれるgemです。
つまり、今までだと「開発環境でメールを送信するとログに出力されていたテキストを拾っていた」のを、「WEBメーラーで見れる」ようになるのです。

thinとは

rackサーバと呼ばれる種類のgemで、同じ箱だとwebrick, unicon, puma, passenger, falconなどがあります。
それぞれのrackサーバには特徴があるのですが、rails5ではpumaが同梱されるようになったり、本番環境での使用実績も着実に積んできているしで、デファクトスタンダードはpumaになりつつあると思っている。

正直、thinは最近下火のように思う。では、mailcatcherはなぜpumaを使わずにthinを使っているのか。

thinを使っている理由

thinを使っている理由はsinatraでwebsocketを動かすためなのです。
(mailcatcherは、sinatraを使ったWEBインターフェースにwebsocketを使ってメールを受信するとリアルタイムに画面が更新されるようになっている。)

sinatraとpumaの組み合わせではwebsocketが動きません。thinだとwebsocketが動く理由は、EventMathineを使ったイベントループ型だからです。
https://github.com/sinatra/sinatra/issues/1035

所感

ここからは私の想像と感想なんですが、pumaのようなワーカースレッドモデルの場合、workerごとにリソースを確保するためwebsocketのコネクションを保持しておく場所を確保することができず、websocketのコネクションを張ってもリクエストを終えると破棄してまっている。一方、thinの場合は、イベントループ型なので切断されるまでコネクションを維持が可能、ということなんだと思いました。sinatraとunicornでも同じことが起きるのではないでしょうか。

あれ、railsのActionCableってpuma使ってない?pumaとwebsocketって動かないでしょ?と思ったので調べました。
ActionCableには、Redisのpubsubを使ってフロントとバックエンドを繋ぎ、websocketの接続をrails層で保持し続けるEventMathine相当の仕組みが実装されていました。

イベントループ型とワーカースレッド型はどっちを使うべき?

railsがワーカースレッド型の使用を前提とした機能を盛り込んできているあたりを見ると、これからもワーカースレッド型のサーバが使われていくように思います。
また、この意思決定の背景には、GVLが関係していると思っていて、イベントループ型のサーバはシングルプロセスなのでサーバのリソースを使い切りにくい、という事情も感じます。

おわり

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

packsファイルの.jsに記載したのに反応しない

【概要】

1.結論

2.どのように使うか

3.なぜjsファイルに書き込まなかったのか

4.ここから学んだこと

1.結論

直にhtml.erbファイルで"script"を記述しscript内にjavascript言語を記載する!

2.どのように使うか

任意のhtml.erb内で

****.html.erb
<script>
.
.
.

</script>

と記載してJavascriptを記載するだけです!
注意点としてapplication.html.erbで
<%=yiled%>を使用してヘッダーフッダーを適用している場合は反映させる順番に注意です。
HTMLが読み込まれるよりも前にJavascriptを適用させてしまうとブラウザの検証ツールのconsoleでエラーがでます。


3.なぜjsファイルに書き込まなかったのか

結論からいうと、jsファイルを読み込んでくれなかったり、リロードを一回挟まないと読み込んでくれませんでした。
内容としてはパスワード表示非表示を行っていましたが、検索するとたくさん出てくるのでここでは割愛します。

Rails6.0
Vscode
を使用しています。

app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("./security") #➡︎該当のファイル

と記載し、secrurity.jsにjavascript言語を記載し、

app/layouts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

と記載してあるのでjsファイルが適用されるはずと思いきや全く反映されませんでした。

また、jsファイルにてパスワードの表示非表示をプログラムしていましたが反応がないのでいろいろ参考にしました。下記のプログラムを最初に記載しました。

app/javascript/packs/security.js
document.addEventListener("DOMContentLoaded", function(){}

パスワードの表示非表示はできましたが、一度リロードを押さないと反映がされませんでした。なのでjsファイル問題は解決していませんが、とりあえずやりたいことはできたのでよしとします。(可読性はよろしくないですが)


4.ここから学んだこと

このエラーの中でhtml/css/javascript/の読み込む順番やDOMがどの段階で形成されていくのかが大変勉強になりました。tutbolinksやdocumentloadedについてはまだまだ勉強が足りていないですが、流れだけでも把握できたのはかなり大きいです。scriptにしたとたんすぐに解決したのも、書く順番を把握していたおかげでした。

参考にしたURL
"DOMContentLoaded周りの処理を詳しく調べてみました"
"【JS】addEventListenerが機能しない理由についてご教示ください"
"DOMContentLoadedイベントとloadイベントの違い[タイミング]"

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

Railsの本番環境でなぜかセッションが保存できない問題を解決した経緯

概要

railsで本番環境にプッシュしたアプリがある日セッションを使った機能の全てが動作しなくなるという絶望的な問題にぶち当たった。めちゃくちゃ苦戦したし記事も全然なかったのでその解決方法を記しておく。

最終的にこの記事から設定しなおして解決しました。
https://www.cotegg.com/blog/?p=1850

環境

・EC2にデプロイ
・サーバー環境はWebサーバーにnginx、アプリケーションサーバーにPuma
・AWSのELBにACMのSSL証明書をアタッチしてSSL化↓

クライアント→ELB→nginx→puma

セッションが保存されない原因

まず、セッションが保存されない原因

RailsではCSRF(クロスサイトリクエストフォージェリー)という脆弱性からサイトを守るための対策として
protect_from_forgeryというメソッドでサイトを保護している
application.html.erbのheadタグ内にcsrf_meta_tagという記述をしているが
ここで認証用のauthenticate_tokenを生成しておりこの値を使ってサイトを認証している。

この辺りの説明は腐る程ググれば出てくるため割愛。
このprotect_from_forgeryメソッドのデフォルトの設定でこのauthenticate_tokenが正しくないと
セッションを空にする。(:null_sessionオプション)が指定されている。
こいつのせいでセッションは空になっている。
※これが原因かどうか一発でわかる方法として、application_controller.rb
protect_from_forgery with: :exception←CSRFトークンが正しくなければエラーを返す
を記述してサイトにアクセスする
ActionController::InvalidAuthenticityTokenというエラーが出たら原因はそれ

つまりなんらかの原因でCSRFの認証が通っていない

なぜこんなエラーがでるのか

冒頭に環境を載せておいたが
クライアント→ELB→nginx→puma
という経路でアプリケーションにアクセスするようになっている
ELBにはSSL証明書をアタッチしてあり、自分のこの構成だと
ELBまではhttpsアクセス
ELBとnginxはhttpアクセスになっている。

実際にユーザーがアクセスするのはhttpsなのにrailsアプリ側にはhttpでアクセスしていると
情報が伝わってしまい、ここで矛盾が生じるためクロスサイト認定され
エラーが起きてしまっている模様。

対策は
nginxなら
/etc/nginx/nginx.confに

location / {
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; #これ追加
  proxy_set_header X-Forwarded-Proto https; #これ追加
  proxy_pass http://www.xxxx.com;
}

としてやるとアクセスするURLと伝わるヘッダー情報が一致してエラーが出なくなる

つまり解決

補足

自分なりの理解で対処した問題の内容をまとめただけのため
間違いがあればご指摘いただけますと幸いです。
Twitterアカウント↓
https://twitter.com/TakeWeb1

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

【Rails】チェックされた項目だけDBに格納する

teratailで質問した後、自己解決しました。
サイトがすごく重くて(?)投稿できなかったので、先にこっちに書いておきます。

やりたかったこと

映画にまつわるアプリを作っており、データベースに映画作品についての簡単な情報を格納する必要がありました。
ターミナルから入力するのはダルかったので、ver.1では公開せず後々ユーザー向けに改良するつもりで、アプリ内に投稿フォームを作成。newメソッドで下記の映画情報をデータベースに登録できるようにしました。

  • title:作品タイトル
  • directed:監督の名前
  • story:あらすじ
  • service:あらすじの引用元の配信サービス名
  • time:再生時間

この中の配信サービス名を四つのチェックボックスの中から選択して、チェックの入ったサービス名をデータベースに格納するということがやりたかったのですが、参考にした記事がチェックボックスの項目とチェック状況を配列に格納するという内容だったため、実現するためには「チェックされた値だけを取り出す」必要がありました。

チェックボックスはcheck_boxタグを使って配置しています。

<div class="box form4">
  <%= check_box "service", "PrimeVideo" %>PrimeVideo
  <%= check_box "service", "Netflix" %>Netflix
  <%= check_box "service", "Hulu" %>Hulu
  <%= check_box "service", "U-NEXT" %>U-NEXT
</div>

コントローラー内で直接

service: params[:service]

と書くと、生成されたハッシュがDBのserviceカラムに格納されます。
チェックが無ければ0、されていれば1が値になります。

{"PrimeVideo"=>"0", "Netflix"=>"1", "Hulu"=>"0", "U-NEXT"=>"0"}

ここから、チェックされた項目のサービス名(つまり値が1のキー)だけ抽出して保存したい!
基礎知識がそもそも足りていないためか、ずっと方法が分からず放置していました。

解決方法

def req
 params[:service].each do | di1 , di2 |
  if di2 == "1"
   @movie = Movie.new(
      title: params[:title],
      directed: params[:directed],
      story: params[:story],
      service:di1,
      time: params[:time]
   )
   @movie.save
  end
 end
end

元々参考にしていた記事のコードを流用させていただきました。

  • params[:service]で値を受け取って、二つの変数にそれぞれサービス名チェック状態(0 or 1)を格納する
  • チェック状態が1である場合、相方の変数に格納されているサービス名をserviceカラムに保存する

という手順を踏みます。

サービス名のチェックがついていないとsaveメソッドがそもそも実行されないという雑な作りにしてしまいましたが……時間がないので今は前進できただけいいってことで…
落ち着いたらselectメソッドkeysメソッド、あるいはSQLの構文など、もう少し丁寧なコードにできそうな部分を中心に基礎固めをしようと思います。

きっかけをくださった回答者の皆さまに、心よりお礼申し上げます。

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

Rails:日本語化の仕方

手順

application.rb内に以下を記述

config.i18n.default_locale = :ja

config/locales内に「ja.yml」ファイルを作成し
以下のRailsが推奨しているコードを記述

ja:
  activerecord:
    errors:
      messages:
        record_invalid: 'バリデーションに失敗しました: %{errors}'
        restrict_dependent_destroy:
          has_one: "%{record}が存在しているので削除できません"
          has_many: "%{record}が存在しているので削除できません"
    attributes:
      モデル名:
        カラム名: '表示したいカラム名'
        カラム名: '表示したいカラム名'
  date:
    abbr_day_names:
    - 日
    - 月
    - 火
    - 水
    - 木
    - 金
    - 土
    abbr_month_names:
    -
    - 1月
    - 2月
    - 3月
    - 4月
    - 5月
    - 6月
    - 7月
    - 8月
    - 9月
    - 10月
    - 11月
    - 12月
    day_names:
    - 日曜日
    - 月曜日
    - 火曜日
    - 水曜日
    - 木曜日
    - 金曜日
    - 土曜日
    formats:
      default: "%Y/%m/%d"
      long: "%Y年%m月%d日(%a)"
      short: "%m/%d"
    month_names:
    -
    - 1月
    - 2月
    - 3月
    - 4月
    - 5月
    - 6月
    - 7月
    - 8月
    - 9月
    - 10月
    - 11月
    - 12月
    order:
    - :year
    - :month
    - :day
  datetime:
    distance_in_words:
      about_x_hours:
        one: 約1時間
        other: 約%{count}時間
      about_x_months:
        one: 約1ヶ月
        other: 約%{count}ヶ月
      about_x_years:
        one: 約1年
        other: 約%{count}年
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1秒
        other: "%{count}秒"
      x_minutes:
        one: 1分
        other: "%{count}分"
      x_days:
        one: 1日
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1年
        other: "%{count}年"
    prompts:
      second: 秒
      minute: 分
      hour: 時
      day: 日
      month: 月
      year: 年
  errors:
    format: "%{attribute}%{message}"
    messages:
      accepted: を受諾してください
      blank: を入力してください
      confirmation: と%{attribute}の入力が一致しません
      empty: を入力してください
      equal_to: は%{count}にしてください
      even: は偶数にしてください
      exclusion: は予約されています
      greater_than: は%{count}より大きい値にしてください
      greater_than_or_equal_to: は%{count}以上の値にしてください
      inclusion: は一覧にありません
      invalid: は不正な値です
      less_than: は%{count}より小さい値にしてください
      less_than_or_equal_to: は%{count}以下の値にしてください
      model_invalid: 'バリデーションに失敗しました: %{errors}'
      not_a_number: は数値で入力してください
      not_an_integer: は整数で入力してください
      odd: は奇数にしてください
      other_than: は%{count}以外の値にしてください
      present: は入力しないでください
      required: を入力してください
      taken: はすでに存在します
      too_long: は%{count}文字以内で入力してください
      too_short: は%{count}文字以上で入力してください
      wrong_length: は%{count}文字で入力してください
    template:
      body: 次の項目を確認してください
      header:
        one: "%{model}にエラーが発生しました"
        other: "%{model}に%{count}個のエラーが発生しました"
  helpers:
    select:
      prompt: 選択してください
    submit:
      create: 登録する
      submit: 保存する
      update: 更新する
  number:
    currency:
      format:
        delimiter: ","
        format: "%n%u"
        precision: 0
        separator: "."
        significant: false
        strip_insignificant_zeros: false
        unit: 円
    format:
      delimiter: ","
      precision: 3
      separator: "."
      significant: false
      strip_insignificant_zeros: false
    human:
      decimal_units:
        format: "%n %u"
        units:
          billion: 十億
          million: 百万
          quadrillion: 千兆
          thousand: 千
          trillion: 兆
          unit: ''
      format:
        delimiter: ''
        precision: 3
        significant: true
        strip_insignificant_zeros: true
      storage_units:
        format: "%n%u"
        units:
          byte: バイト
          eb: EB
          gb: GB
          kb: KB
          mb: MB
          pb: PB
          tb: TB
    percentage:
      format:
        delimiter: ''
        format: "%n%"
    precision:
      format:
        delimiter: ''
  support:
    array:
      last_word_connector: "、"
      two_words_connector: "、"
      words_connector: "、"
  time:
    am: 午前
    formats:
      default: "%Y/%m/%d"
      long: "%Y/%m/%d %H:%M"
      short: "%m/%d %H:%M"
    pm: 午後

この後、ファイルを読み込ませるためにrails serverを再起動で日本語化が完了

補足

attributes:
      モデル名:
        カラム名: '表示したいカラム名'
        カラム名: '表示したいカラム名'

この部分だけはコピペしないように!
自分が使用したいテーブルで設計する

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

Rails:createアクションの仕組み

動機

newとcreateの役割に困惑したためのメモ作成

new

新しく作成する場合は
new.html.hamlとnewアクションがまず使用される

create

newの中のformで作成した内容を元にDBへ登録するアクションがcreate

以下のコードが一連の流れです。

def create
    @task = Task.new(task_params)
    if @task.save
      redirect_to task_path(@task), notice: 'Task is create'
    else
      render :new
end

データの取得

@task = Task.new(task_params)

private
  def task_params
    params.require(:task).permit(:title, :content)
  end

newで取得した内容を@taskに引数として渡している
引数にはprivateメソッドで取得した値をいれる

requireは必要という意味
permitは許可という意味
なので:taskモデルの:titleと:contentは許可するけど他の値は許可しないよというようなデータを取得している(ストロングパラメータ)

save

if @task.save
      redirect_to task_path(@task), notice: 'Task is create'
    else
      render :new

取得したデータが保存できたら
showページに遷移する
できなかったらnewページに遷移する

重要

createアクション内もインスタント変数にしている理由は
createからnewページへ遷移する際newページで使用されるインスタンス変数はcreateアクション内にインスタンス数があればこれを利用できる

すなわち
保存されなかったデータも残したままnewページを呼び出すことができる

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

【Rails】LINEBotでPush送信

概要

RailsからLineBotのPush送信します
以下のように、任意のタイミングで送信するやつです
Screenshot_20200912-131255.png

LINEBotの作成

記事がたくさんあるので、そちらを参照してください
例えば、以下のような記事があります

LINE BOTの作り方を世界一わかりやすく解説(1)【アカウント準備編】

RailsからLINEBotでPush送信

  1. Gemfileでライブラリline-bot-apiをインストールしておいてください
  2. ENV["LINE_CHANNEL_SECRET"]ENV["LINE_CHANNEL_TOKEN"]は、herokuなど、サーバ側で、設定する必要があります。この後、説明します
  3. 自分のuser_idは、チャンネル基本設定->あなたのユーザーID、から確認できます
  4. push_messageがlinebotでpush送信する部分です
linebot_controller.rb
class LinebotController < ApplicationController  
    require 'line/bot'  # gem 'line-bot-api'  

    def client  
      @client ||= Line::Bot::Client.new { |config|  
        config.channel_secret = ENV["LINE_CHANNEL_SECRET"]  
        config.channel_token = ENV["LINE_CHANNEL_TOKEN"]  
      }  
    end  

    def push

        message={
            type: 'text',
            text: "hello"
           }
        user_id =  '[送信先のLINEアカウントのユーザID]'
        response = client.push_message(user_id, message)
    end

  end  

Railsアプリをherokuにデプロイ

Railsアプリのデプロイの記事がたくさんあるので、そちらを参照してください
例えば、以下のような記事があります

HerokuにRailsアプリをデプロイする手順

この時、以下のように、チャネル基本設定->チャネルシークレット、Messaging API設定->チャネルアクセストークン、をherokuに必ず設定してください

$ heroku config:set LINE_CHANNEL_SECRET="[チャネルシークレット]"
$ heroku config:set LINE_CHANNEL_TOKEN="[チャネルアクセストークン]"

結果

herokuにデプロイした、Railsアプリのlinebot_controller.rbのアクションpushを実行します
以下のように「hello」がpush送信できました
Screenshot_20200912-131255.png

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

rails tutorial 第10章

はじめに

独学でrails tutorialを進めていく過程を投稿していきます。

進めていく上でわからなかった単語、詰まったエラーなどに触れています。

個人の学習のアウトプットなので間違いなどあればご指摘ください。

初めての投稿なので読みにくいところも多々あるかと思いますがご容赦ください。

第10章 ユーザーの更新・表示・削除

10.1.1 編集フォーム

演習2

app/views/users/_form.html.erb
#リスト 10.5: newとeditフォーム用のパーシャル
<%= form_with(model: @user, local: true) do |f| %>
  <%= render 'shared/error_messages', object: @user %>

  <%= f.label :name %>
  <%= f.text_field :name, class: 'form-control' %>

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

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

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

  <%= f.submit yield(:button_text), class: "btn btn-primary" %>
<% end %>

しれっと

object: @user

が追記されています、、、
おそらくshared/error_messagesに渡すオブジェクトを指定しているとは思うのですが
shared/error_messagesは下記のように

app/views/shared/_error_messages.html.erb
<% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-danger">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

@userオブジェクトを指定しているから必要ない気がするのですが、、、

参考
https://teratail.com/questions/112178

また、

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

の第2引数が消えています、、、

第2引数はおそらくラベルのテキストだと予想は出来ましたが念のため調べました。

参考
https://teratail.com/questions/112172

第2引数を指定しなくなった事により、ラベルのテキストが
ConfirmationからPassword confirmationと表示されています。

意味はどちらもわかるけれどなぜサイレントで修正されたのか、、、

10.2.3 フレンドリーフォワーディング

Q.
requestオブジェクトって?
A.
request.original_url
現在のリクエストURLを返す
request.get?
HTTPメソッドがGETの時trueを返す

参考
https://railsguides.jp/action_controller_overview.html#request%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88

10.3.2 サンプルのユーザー

問題発生!!
rails db:migrate:resetを実行するとエラーが発生する

Permission denied @ apply2files - C:/environment/sample_app/db/development.sqlite3
Couldn't drop database 'db/development.sqlite3'
rails aborted!
Errno::EACCES: Permission denied @ apply2files - C:/environment/sample_app/db/development.sqlite3
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => db:drop:_unsafe
(See full trace by running task with --trace)

こちらの記事を参考に解決しました。
https://qiita.com/Toshiki23/items/f366504844fd22ad87d9

以前参考にしましたね。windowsでは自らがアクセスしているファイルを削除できないようです。

10.3.3 ページネーション

User.paginate(page: 1)

paginateはキー:pageで値ページ番号を取る
User.paginateは、:pageパラメーターに基いて、データベースからひとかたまりのデータ(デフォルトでは30)を取り出す
上のコマンドをコンソールで実行するとページ番号1のオブジェクトを出力します(出力結果が11となりますが、Active Record自身のコンソール上限によるものです。lengthメソッドを呼べばこの制約を回避できます)

@users = User.paginate(page: params[:page])

params[:page])が1なら1~30のユーザー、params[:page])が2なら31~60のユーザーが取り出され@usersに代入されるようになります。
ちなみにページがnilの場合paginateは単に最初のページを返すとのことです。

10.3.5 パーシャルのリファクタリング

app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

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

<%= will_paginate %>
app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>

さらにリファクタリング。

app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

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

<%= will_paginate %>

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

またまた初学者には難しい省略が、、、
コレクション、、、つまり複数のデータのようなものを渡すということでしょうか。

省略出来ることは理解できましたがそうなると_user.html.erbの

app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>

eachメソッドで作った変数userはこのままでよいのでしょうか、よいとすれば何をもって変数userという名前に決まったのでしょう?

変数名は何でもよいのでしょうか?
試しに名前を変えてテストをしてみました。

app/views/users/_user.html.erb
<li>
  <%= gravatar_for aaa, size: 50 %>
  <%= link_to aaa.name, user %>
</li>

結果
エラー。

undefined local variable or method `aaa' 

aaaという変数は定義されていないとのこと。

ここまでで推測出来ることは
①render で渡したオブジェクトを単数形にしたものを自動設定している?
②Userクラスのオブジェクトだから同じ名前としたものを自動設定している?

試しに

app/controllers/users_controller.rb
def index
  @aaas = User.paginate(page: params[:page])
end
app/views/users/index.html.erb
<% provide(:title, 'All users') %>
<h1>All users</h1>

<%= will_paginate %>

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

<%= will_paginate %>

結果

ActionView::Template::Error: The @users variable appears to be empty. Did you forget to pass the collection object for will_paginate?

@users変数が空とのこと。will_paginateにコレクションオブジェクトを渡し忘れていないか?というメッセージも見られました。

ということはコレクションオブジェクトの変数名は@usersとするべきなのでしょうか?

そしてwill_pagineteにもそれ(@usersという名前の付いたコレクションオブジェクト)を渡さなければならないのでしょう、、、多分、、、

railsではUserオブジェクトのコレクションオブジェクトをusersという変数名にする(コレクションオブジェクトはクラスオブジェクトの複数形にする)ことで色々と良しなに解釈してくれる設定があるということでしょうか?

そうすることで変数名をuserとすると、そのコレクションを自動で列挙するといような便利な動作をするとか、、、なので

app/views/users/_user.html.erb
<li>
  <%= gravatar_for user, size: 50 %>
  <%= link_to user.name, user %>
</li>

こうしなければならないのかな?推測ですけれど、、、

色々とよしなに解釈してくれるのは勿論そのためのルールに則った記述をしてこそですからね、、、

終わりに

今回はリファクタリングに悩まされた章でした。
少し曖昧な部分も残してしまったので、何度か振り返り、明確に理解できるよう学習を進めたいです。

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

Rails 6で認証認可入り掲示板APIを構築する #7 update, destroy実装

Rails 6で認証認可入り掲示板APIを構築する #6 show, create実装

updateテストの実装

残り2アクションupdateとdestroyを実装します。

updateは更新処理です。
createができていたら似たようなものなので、大きく詰まることはないはずです。

spec/requests/v1/posts_controller.rb
...

+  describe "PUT /v1/posts#update" do
+    let(:update_param) do
+      post = create(:post)
+      update_param = attributes_for(:post, subject: "update_subjectテスト", body: "update_bodyテスト")
+      update_param[:id] = post.id
+      update_param
+    end
+    it "正常レスポンスコードが返ってくる" do
+      put v1_post_url({ id: update_param[:id] }), params: update_param
+      expect(response.status).to eq 200
+    end
+    it "subject, bodyが正しく返ってくる" do
+      put v1_post_url({ id: update_param[:id] }), params: update_param
+      json = JSON.parse(response.body)
+      expect(json["post"]["subject"]).to eq("update_subjectテスト")
+      expect(json["post"]["body"]).to eq("update_bodyテスト")
+    end
+    it "不正パラメータの時にerrorsが返ってくる" do
+      put v1_post_url({ id: update_param[:id] }), params: { subject: "" }
+      json = JSON.parse(response.body)
+      expect(json.key?("errors")).to be true
+    end
+    it "存在しないidの時に404レスポンスが返ってくる" do
+      put v1_post_url({ id: update_param[:id] + 1 }), params: update_param
+      expect(response.status).to eq 404
+    end
+  end
...

例によってcontroller未実装なのでテストはコケます。

updateの実装

app/controllers/v1/posts_controller.rb
...

     def update
-      # TODO
+      if @post.update(post_params)
+        render json: { post: @post }
+      else
+        render json: { errors: @post.errors }
+      end
     end
...

ここももはや特に語ることなし。
これでテスト通過するはずです。

destroyテストの実装

spec/requests/v1/posts_controller.rb
...

+  describe "DELETE /v1/posts#destroy" do
+    let(:delete_post) do
+      create(:post)
+    end
+    it "正常レスポンスコードが返ってくる" do
+      delete v1_post_url({ id: delete_post.id })
+      expect(response.status).to eq 200
+    end
+    it "1件減って返ってくる" do
+      delete_post
+      expect do
+        delete v1_post_url({ id: delete_post.id })
+      end.to change { Post.count }.by(-1)
+    end
+    it "存在しないidの時に404レスポンスが返ってくる" do
+      delete v1_post_url({ id: delete_post.id + 1 })
+      expect(response.status).to eq 404
+    end
+  end
...

postの時と逆で、実行寺にレコード数が1減ることを確認します。
なおポイントとしては、
expectの前にdelete_postを呼んでいることです。

以前記載した通りletは遅延評価をされるので、もしexpectの前にdelete_postが無かった場合、expect内のdelete_post.idでレコードが生成され、delete v1_post_urlで1レコード消える。つまりexpectブロック内で+1-1=0となり、レコード数に変化が生まれません。

そのためexpect前にレコードを生成し、expectの中でdeleteが実行されることにより-1レコードとなるわけです。

destroyの実装

app/controllers/v1/posts_controller.rb
...

     def destroy
+      @post.destroy
+      render json: { post: @post }
     end
...

こちらも非常にシンプル。
今回は短めですが、次にやるseedまで含むと長くなりすぎるのでここまで。

続き

連載目次へ

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

rails Action Textの実装

はじめに

自分用メモとして残します。

環境

  • Rails v6.0.3.2
  • ruby v2.6.6p146
  • node v12.18.3
  • yarn v1.22.4

手順

1.アプリ作成

MySQLを使ったアプリを作成する。

$ rails new actionText -d mysql

作成したアプリに移動

2.scaffold

scaffoldでアプリの雛形を作成する。
※articleはtitleとcontentを持つが、後で追加するためここではtitleのみでOK

$ rails g scaffold article title:string

3.DB作成

$ rails db:create

4.migrate

$ rails db:migrate

5.Action Textインストール

$ rails action_text:install

6.migrate

$ rails db:migrate

7.アソシエーション

Action Textのデータは専用のテーブルに格納されるためarticleモデルに関連づける必要がある。

app/models/article.rb
class Article < ApplicationRecord
  has_rich_text :content
end

8.ビューにcontent追加

app/views/articles/_form.html.erb
...
  <div class="field">
    <%= form.label :title %>
    <%= form.text_field :title %>
    <%= form.label :content %>
    <%= form.rich_text_area :content %>
  </div>
...
app/views/articles/show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>
<p>
  <strong>Content:</strong>
  <%= @article.content %>
</p>

<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>

9.コントローラにcontent追加

contentをparamsパラメータを読み出すメソッドに追加する。

app/controllers/articles_controller.rb
...
    def article_params
      params.require(:article).permit(:title, :content)
    end
...

終了

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

rails tutorial 第9章

はじめに

独学でrails tutorialを進めていく過程を投稿していきます。

進めていく上でわからなかった単語、詰まったエラーなどに触れています。

個人の学習のアウトプットなので間違いなどあればご指摘ください。

初めての投稿なので読みにくいところも多々あるかと思いますがご容赦ください。

第9章 発展的なログイン機構

9.1.1 記憶トークンと暗号化

attr_accessorの復習

参考
https://qiita.com/Hassan/items/0e034a1d42b2335936e6
.remember_tokenが使えるようになります(remember_token属性が扱えるようになった)

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

最後の(remember_token)はself.が省略されています。

9.1.2 ログイン状態の保持

BCrypt::Password.new(remember_digest) == remember_token

このコードをじっくり調べてみると、実に奇妙なつくりになっています。bcryptで暗号化されたパスワードを、トークンと直接比較しています。(rails tutorial 9章より引用)

ふむふむ、確かに、、、

==で比較する際にダイジェストを復号化しているのでしょうか。
しかし、bcryptのハッシュは復号化できないはずなので、復号化しているはずはありません。(rails tutorial 9章より引用)

なるほど、、では何が起こっているのでしょう?

BCrypt::Password.new(remember_digest).is_password?(remember_token)

どうやら上の2つのコードは同じ意味のようです。
また、is_password?メソッドでオブジェクトと渡された引数の検証をし、一致している場合trueを返しているようです。

9.3.2 [Remember me]をテストする

また、リスト 9.24で定義したlog_in_asヘルパーメソッドでは、session[:user_id]と定義してしまっています。このままでは、current_userメソッドが抱えている複雑な分岐処理を統合テストでチェックすることが非常に困難です。(rails tutorial 9章より引用)

ん?なぜでしょう?
A.
統合テストではsessionメソッドが扱えないから

test/test_helper.rb
def log_in_as(user)
    session[:user_id] = user.id #使えない
end

test/helpers/sessions_helper_test.rb
test "current_user returns right user when session is nil" do
    assert_equal @user, current_user
    assert is_logged_in?
end

このcurrent_userとは
current_userメソッドが実行された上でモデルオブジェクトとなっているようです。多分、、、

つまり
log_in user
なども行われており、sessionメソッドも実行されているのだと思います、、、

app/helpers/sessions_helper.rb
def current_user
  if user_id = session[:user_id] #false
    @current_user ||= User.find_by(id: user_id)
  elsif user_id = cookies.signed[:user_id] #true
    user = User.find_by(id: user_id)
    if user && user.authenticated?(cookies[:remember_token])
      log_in user #ログインされる
      @current_user = user
    end
  end
end

current_userメソッドは戻り値にモデルのインスタンスを返すモデルオブジェクト。


test/helpers/sessions_helper_test.rb
test "current_user returns nil when remember digest is wrong" do
    @user.update_attribute(:remember_digest,User.digest(User.new_token))
    assert_nil current_user
end
@user.update_attribute(:remember_digest, User.digest(User.new_token))

remember_digestを新しいものに更新。

assert_nil current_user

app/helpers/sessions_helper.rb
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]) #falseとなりcurrent_userはnilとなる
      log_in user
      @current_user = user
    end
  end
end
if user && user.authenticated?(cookies[:remember_token])

こちらの検証に置いてcurrent_userがnilとなるか確かめています。

終わりに

こちらの章ではエラーで躓くことも少なく、内容もよく理解出来ました。

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