20200912のRubyに関する記事は20件です。

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で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で続きを読む

【Ruby on Rails】郵便番号から住所を自動入力

目標

map.gif

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

homesコントローラーを作成し、以下を記述済。

config/routes.rb
root 'homes#top'
get 'mypage', to: 'homes#mypage'
app/controllers/homes_controller.rb
class HomesController < ApplicationController
 def top
 end
 def mypage
 end
end

流れ

1 deviseで住所を入力してログインできるようにする
2 gem 'jp_prefecture'、gem 'jquery-rails'を導入
3 jquery.jpostal.jsを導入
4 application.jsの編集

1、deviseでのログイン

  • 参考:こちらで詳しく説明しています。
Gemfile
gem 'devise'
ターミナル
$ bundle install
$ rails g devise:install
$ rails g devise User

下記記述を追加。

db/migrate/xxxxxxxxxxxxx_devise_create_users.rb
...

<-- ここから-->
      t.integer :postal_code, null: false
      t.string :prefecture_code, null: false
      t.string :city, null: false
      t.string :street, null: false
      t.string :other_address # 番地以降の住所がない場合もあるため、null: falseはつけない
<-- ここまでを追加 -->
      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

devise:controller

ターミナル
rails g devise:controllers users
app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

...

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  def configure_sign_up_params
    devise_parameter_sanitizer.permit(:sign_up, keys: [:email, :postal_code, :prefecture_code, :city, :street, :other_address])
  end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  def after_sign_up_path_for(resource)
    mypage_path
  end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
end

devise:model

未入力を防ぐため、下記記述を追加。

app/models/user.rb
  validates :postal_code, presence: true
  validates :prefecture_code, presence: true
  validates :city, presence: true
  validates :street, presence: true

devise:routing

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    sessions: 'users/sessions',
    registrations: 'users/registrations',
  }

  root 'homes#top'
  get 'mypage', to: 'homes#mypage'
end

devise:view

ターミナル
$ rails g devise:views users

form_forの中に記述。

app/views/devise/registrations/new.html.erb
form_for...

  <div class="field">
    <%= f.label :postal_code %><br>
    <%= f.text_field :postal_code %>
  </div>

  <div class="field">
    <%= f.label :prefecture_code %><br>
    <%= f.collection_select :prefecture_code, JpPrefecture::Prefecture.all, :name, :name %>
  </div>

  <div class="field">
    <%= f.label :city %><br>
    <%= f.text_field :city %>
  </div>

  <div class="field">
    <%= f.label :street %><br>
    <%= f.text_field :street %>
  </div>
  <div class="field">
    <%= f.label :other_address %><br>
    <%= f.text_field :other_address %>
  </div>


...

これで住所の入力をする新規登録画面の完成です。

2、gemの追加

Gemfile
gem 'jp_prefecture' # 都道府県コードから都道府県名を変換するgem
gem 'jquery-rails' # RailsでjQueryを使えるようにするgem
ターミナル
$ bundle install

3、jquery.jpostal.jsを導入

https://github.com/ninton/jquery.jpostal.js/
上記URLに遷移後、緑色の「Code」タブを押して、
赤丸部分にてzipファイルをダウンロード。
スクリーンショット 2020-09-11 15.20.22.png
解凍後、「jquery.jpostal.js」のファイルを見つけ、
このファイルをapp/assets/javascripts 下に格納。

4、application.jsの編集

app/assets/javascripts/application.js
//= require rails-ujs <--削除
//= require jquery <--追加
//= require jquery_ujs <--追加
//= require activestorage
//= require turbolinks
//= require jquery.jpostal <--追加
//= require_tree .

$(function() {
  $(document).on('turbolinks:load', () => {
    $('#user_postal_code').jpostal({
      postcode : [
        '#user_postal_code'
      ],
      address: {
        "#user_prefecture_code": "%3", // # 都道府県が入力される
        "#user_city"           : "%4%5", // # 市区町村と町域が入力される
        "#user_street"         : "%6%7" // # 大口事務所の番地と名称が入力される
      }
    });
  });
});


// # 入力項目フォーマット
// #   %3  都道府県
// #   %4  市区町村
// #   %5  町域
// #   %6  大口事業所の番地 ex)100-6080
// #   %7  大口事業所の名称

うまく動作しない時
おそらくturbolinksの挙動がおかしくなっている可能性が高いため、
link前のlink_toに
data: {"turbolinks"=>false}
を記述することで解決できる場合があります。

下記内容を一番上に追加

app/views/devise/registrations/new.html.erb
<script type="text/javascript" src="//jpostal-1006.appspot.com/jquery.jpostal.js"></script>

参考

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

Ractor超入門

はじめに

この記事はRuby3で導入される並列・並行処理のための新機能Ractorに興味のある方向けの記事になります。
簡単なRactorを使ったサンプルコードを解説しつつ、理解を深めることができるように書いてみました(ほとんど未来の僕へ向けた記事になっている感じはありますが……)。

また、Ractor自体でどういったことができるのかを調べた結果をまとめた記事でもあります。
そのため後半はRactorで色々遊んだ時のコードをもとに挙動を解説しています。

環境

  • Windows10 2004
  • WSL2(Ubuntu 18.04)
  • ruby 3.0.0dev (2020-09-07T04:29:42Z master 17a27060a7) [x86_64-linux]

Ractorとは?

そもそもRactorについて知らない人もいると思いますので、簡単に紹介します。

RactorとはRuby3で導入される並列・並行処理のための新機能になります。機能自体の提案は数年前からあり、当時はGuildという名前で提案されていました。

しかし、ゲーム業界から「Guildという名前は使っているので、別の名前にしてほしい」という声があり、現在のRactorへと変わりました。

Actorモデルを参考にしているようで、そのためRactor(Ruby’s Actor)という名前に変更されたようです。

Ractorは並行実行の単位であり、それぞれが並行して実行されます。
たとえば以下のコードではputs :helloputs :helloはそれぞれ並行して実行されます。

Ractor.new do
  5.times do
    puts :hello
  end
end

5.times do
    puts :world
end

このコードを実行すると以下のような結果になります。

world
helloworld

hello
world
helloworld

helloworld

hello

このようにそれぞれの処理を並行して実行できます。

またRactorは別のRactorへとオブジェクトを送受信し、同期しながら実行することもできます。同期の方法としてはpush型pull型の二つがあります。

たとえばpush型の場合は以下のようなコードになります。

r1 = Ractor.new do
    :hoge
end

r2 = Ractor.new do
    puts :fuga, Ractor.recv
end

r2.send(r1.take)

r2.take
# => :fuga, :hoge

Ractorではsendメソッドを使い、別のRactorへとオブジェクトを送ることができます。上記のコードだと

r2.send(r1.take)

の部分でr2へと送信しています。

送信されたオブジェクトはRactor内でRactor.recvで受け取ることができ

r2 = Ractor.new do
    puts :fuga, Ractor.recv # 
end

r2.send(r1.take)で送られたオブジェクトを受け取ってputsメソッドに渡すことができます。

またRactorが実行された結果を受け取る際にtakeメソッドを使います。
ですのでr1.take:hogeを受け取っています。

つまり、r2.send(r1.take)ではr1の実行結果を受け取り、それをr2へと送信しています。
そして、r2内のputs :fuga, Ractor.recvputs :fuga, :hogeとなり、fugahogeがそれぞれ出力されるということです。

これがpush型でのオブジェクトをやり取りしている流れになります。

対してpull型は以下のようなコードになります。

r1 = Ractor.new 42 do |arg|
    Ractor.yield arg
end

r2 = Ractor.new r1 do |r1|
    r1.take
end

puts r2.take

Ractor.newで渡した引数は|arg|のようにブロック内で使用できる変数として受け取ることができます。

例えば以下のコードはr1takeメソッドが実行されるまで処理を待ちます。

r1 = Ractor.new 42 do |arg|
    Ractor.yield arg
end

またRactor.newには別のRactorを渡すことができるため以下のように書くことができます。

r2 = Ractor.new r1 do |r1|
    r1.take
end

これでr1が引数として受け取った42r2の中で受け取ることができます。

最後にputs r2.take42を受け取って出力しています。

pull型はこういった流れになります。

ざっくりと解説すると

  • push型: Ractor#send + Ractor.recv
  • pull型: Ractor.yield + Ractor#take

という感じです。

より詳細なRactorの解説に関しては下記のリンクを参照していただければと思います。

Ractorのコード

Ractorの生成

RactorRactor.newでブロックに実行したい処理を書きます。

Ractor.new do
  # このブロックが並行に実行される
end

このブロック内の処理が並行実行されます。

つまり、以下のようなコードの場合

Ractor.new do
    10.times do
        puts :hoge
    end
end

10.times do
    puts :fuga
end

:hoge:fugaがそれぞれ並行に出力されます。

またRactor.newに実行したい処理をブロックとして渡しますので以下のように書くこともできます。

Ractor.new{
    10.times{
        puts :hoge
    }
}

またキーワード引数nameを使って名前を付けることもでき、Ractor#nameで名前をを受け取ることもできます。

r = Ractor.new name: 'r1' do
    puts :hoge
end

p r.name
# => "r1"

これにより、どのRactorで処理が実行されているかを確認することもできそうです。

Ractorへ引数を渡す

Ractor.newに引数を渡すことでブロック内にオブジェクトを渡すことができます。

r = Ractor.new :hoge do |a|
    p a
end

r.take
# => :hoge

このように引数を経由してオブジェクトを渡すことができます。

また複数の引数を渡すこともできます

r = Ractor.new :hoge, :fuga do |a, b|
    p a
    p b
end

r.take
# => fuga
# => hoge

このようにArrayを渡すこともできます。

r = Ractor.new [:hoge, :fuga] do |a|
    p a.inspect
end

r.take
# => "[:hoge, :fuga]"

ちなみに、|a||a, b|に変更すると

r = Ractor.new [:hoge, :fuga] do |a, b|
    p a
    p b
end

r.take
# => :hoge
# => :fuga

という出力結果になります。これはa, b = [:hoge, :fuga]と同じ挙動と解釈されているようです。

またHashの場合は

r = Ractor.new({:hoge => 42, :fuga => 21}) do |a|
    p a
    p a[:hoge]
end

r.take
# => {:hoge=>42, :fuga=>21}
# => 42

と出力されます。
ちなみに、Ractor.newの後に()で括っていないとSyntaxErrorになるので注意が必要です。

r = Ractor.new({:hoge => 42, :fuga => 21}) do |a|
    p a
    p a[:hoge]
end

r.take
# => SyntaxError

Ractorでの返り値

Ractorでは実行されるブロック内の返り値をtakeメソッドで受け取ることができます。

r = Ractor.new do
    :hoge
end

p r.take
# => :hoge

ちなみに、ブロック内でreturnをするとLocalJumpErrorとなるようです。

r = Ractor.new do
    return :fuga
    :hoge
end

p r.take
# => LocalJumpError

Ractor内での例外

Ractor内での例外は以下のようにして受け取ることができます。

r = Ractor.new do
    raise 'error'
end

begin
    r.take
rescue Ractor::RemoteError => e
    p e.message
end

ちなみに、Ractor内でも例外処理を書くことはできます。

r = Ractor.new name: 'r1' do
    begin
        raise 'error'
    rescue => e
        p e.message
    end
end

r.take

またドキュメントによるとRactorのブロック内から返ってきた値を受け取る領域で例外をキャッチできるようです。
つまり以下のようなコードも書くことができます。

r1 = Ractor.new do
    raise 'error'
end

r2 = Ractor.new r1 do |r1|
    begin
        r1.take
    rescue Ractor::RemoteError => e
        p e.message
    end
end 

r2.take
# => "thrown by remote Ractor."

Ractorでの並行実行

簡単な例

こんな感じでRactorで並行実行を行うことができます。

Ractor.new do
    3.times do
        puts 42
    end
end

3.times do
    puts 21
end

実行すると4221という出力がそれぞればらばらに表示されます。

ちょっとした例

以下のような感じで複数のworkerRactorで生成し、それをpipeで経由して値を渡し、結果をまとめることができます。

require 'prime'

pipe = Ractor.new do
  loop do
    Ractor.yield Ractor.recv
  end
end

N = 1000
RN = 10
workers = (1..RN).map do
  Ractor.new pipe do |pipe|
    while n = pipe.take
      Ractor.yield [n, n.prime?]
    end
  end
end

(1..N).each{|i|
  pipe << i
}

pp (1..N).map{
  r, (n, b) = Ractor.select(*workers)
  [n, b]
}.sort_by{|(n, b)| n}
# => 0 ~ 999 までの数値が素数かどうかの結果を出力

このコードでは10個のworkerを生成し、pipeを経由してそれぞれのworkerにオブジェクトを渡しています。
また、受け取ったオブジェクトをRactor.yield [n, n.prime?]で返しています。

こんな感じでworkerを複数作り、pipe経由で処理させたり、結果を受け取ることができます。

RactorでNumbered Parameterを使う

Ractorでは処理をブロックで渡しますので、Numbered Parameterを使って引数を受け取ることもできます。

r = Ractor.new :hoge do
    puts _1
end

r.take
# => hoge

ちなみに、複数の引数でも動作します。

r = Ractor.new :hoge, :hoge do
    puts _1
    puts _2
end

r.take
# => hoge
# => fuga

複数渡した場合は渡された順番通りに_1から_9に渡されるみたいです。

ちなみに、Hashを渡した場合はこんな感じになります。

r = Ractor.new ({hoge: 1, fuga: 2}) do
    _1.map do |key, value|
        p ":#{key} => #{value}"
    end
end

r.take
# => ":hoge => 1"
# => ":fuga => 2"

=>を使ったHashも同様の結果になりました

r = Ractor.new({:hoge => 1, :fuga => 2}) do
    _1.map do |key, value|
        p ":#{key} => #{value}"
    end
end

r.take
# => ":hoge => 1"
# => ":fuga => 2"

ただし、Arrayの場合は少し挙動が異なります

r = Ractor.new [1, 2, 3] do
    puts _1
    puts _1.class
    puts _2
    puts _2.class
    puts _3
    puts _3.class    
end

r.take
#=> 1
#=> Integer
#=> 2
#=> Integer
#=> 3
#=> Integer

どうやら通常通り複数の引数を渡した時のようにArrayの先頭から順番に渡されるようです。
おそらくは以下のように解釈されているのではないかと思います。

_1, _2, _3 = [1, 2, 3]

ちなみに、Numbered Parameterで受け取れる数より大きなArrayを渡すと

r = Ractor.new [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] do
    puts _1
    puts _2
    puts _3
    puts _4
    puts _5
    puts _6
    puts _7
    puts _8
    puts _9
end

r.take
#=> 1
#=> 2
#=> 3
#=> 4
#=> 5
#=> 6
#=> 7
#=> 8
#=> 9

このように取得できるのはNumbered Parameterが受け取れる範囲までのようです

Ractor内でNumbered Parameterを使う場合はHashを引数に渡した際か

r = Ractor.new ({hoge: 1, fuga: 2}) do |hash|
    hash.map do
        p ":#{_1} => #{_2}"
    end
end

r.take
":hoge => 1"
":fuga => 2"

またはいくつかの引数を渡した時に省略して書きたいときに使うことになりそうです

r = Ractor.new :hoge, :fuga do
    p _1
    p _2
end

r.take
# => :hoge
# => :fuga

おわりに

この記事を読んでRactorについて興味を持って頂ければ幸いです。
今後もRactorを使って試したコードを追加していこうと思います

参考

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

簡単なジャンケン

puts "最初はグー!じゃんけん。。。"
puts "【0】グー【1】ちょき【2】パー"

user_input = gets.to_i #ユーザーに選んでもらう(整数)to_i
computer = rand(3) #コンピュータの選択をランダムで0~2

if user_input >= 3 #3以上の数字が選ばれた際に出力して、以降の処理をストップする
    puts "0~2の数字を入力してください"
exit
end

puts "あなたは #{user_input}"
puts "computerは #{computer}"



if user_input == computer
    puts "あいこです。"

elsif (user_input == 0 && computer == 1) ||
 (user_input == 1 && computer == 2) ||
 (user_input == 2 && computer == 0) 
    puts "あなたの勝ちです"
#ジャンケン勝敗の3パターンを記載 &&(and) ||(or)の使い方

else
    puts "あなたの負けです"


end

#ユーザーが0~2を入力する
#判定する

#1.user_input == computer_input => あいこ
#2.user_input (勝ちパターン)
#3.

#結果を表示する



  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

to_○ 

to_○

x = 50
y = "3"

p x + y.to_i #to int
p x + y.to_f #to floo


scores = {keiichi:400, tomomi:2000}

p scores.to_a #to arry 
p scores.to_h #to hassh 

%記法

puts ("red", "blue")

puts %(hello) #でOK 間に””を使用できる

p ["red", "blue"]

p %W(red, blue) #でOK

if

score = gets.to_i


if score > 80 then
        puts "great!"
else if score > 60 then
    puts "good"

else
    puts "so so..."

end

case

#case

signal = gets.chomp #最後の改行コードを取り除くchomp

case signal
when "red" 
    puts "stop!"
when "green" 
    puts "go!"
when "yello" 
    puts "caution!"

else
    puts "wrong signal"

end

While

#while

i = 0

while i < 10 do

    puts "#{i}: hello" # #{i}は回数を見やすくする為に使用

    i += 1 #i = i + 1を短縮して書ける

end

Times

#times

10.times do |i| #i += 1と同じ効果
    puts "hello"
end

for

for i in 15..20 do
    p i
end

for color in ["red", "blue"] do
    p color
end

for name, score in {taguchi:200, fkoji:400} do
    puts "#{name}: #{score}"
end

for color in %W(red, bule) do
    p color
end

each

(15..20).each do |i|
    p i
end

["red", "blue"].each do |color|
    p color
end

{taguchi:200, fkoji:400}.each do |name, score|
    puts "#{name}: #{score}"
end

%W(red, bule).each do |color|
    p color
end
  • このエントリーをはてなブックマークに追加
  • 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】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で続きを読む

セッションを用いたセキュリティ攻撃(セッションID、クッキー)

前提
プログラミング初学者(2~3ヶ月)が学んだ内容をまとめています。
実際の現場では通用しないことや間違った内容が含まれている可能性があります。
間違っている部分や浅い部分については追記、ご指摘いただけると幸いです。

セッションを用いたセキュリティ攻撃について学んだ内容をまとめました。

セッションを狙った攻撃と対策

セッションとは?

セッションとは、データを一時的に保存しておく仕組みのことです。
この一時保存される保存先はクッキーと呼ばれます。
youtubeやtwitterに同じ端末からアクセスすると自分のアカウントでログインされた状態になっていますよね。
あれはセッションという仕組みでログイン情報がクッキーに一時的に保存されているため、毎回ログインをしなくてもログイン状態になっているということです。
このログイン情報などが一時的に保存されているクッキーを狙った攻撃をセッションハイジャックといいます。

セッションハイジャック

XSSなどを用いて、正規利用者ではないものが正規利用者のセッションを取得する攻撃方法です。

XSSはwebアプリやwebページを開いたらスクリプトが発動し、個人情報が攻撃者へ送信されるといったものです。
以前まとめた記事が以下です。
https://qiita.com/Nako4/items/845da2ed4872524c2a15

クッキーにはセッションIDという識別番号を用いて保存します。
このセッションIDを盗み出されることで、セッションハイジャックが成立します。
セッションID
通信中の正規利用者へ付与される固有の識別情報のこと

攻撃されると危険な理由

危険性については、なんとなく察することができると思います。
セッションを悪意ある攻撃者に取得されるとログイン情報が第三者の手に渡ってしまい、正規利用者のできることが全てできる状態になってしまうということです。
例えば、配送先の住所や購入情報(クレカ番号)を登録している場合
・正規利用者の個人情報を見ることができる
・勝手に物品購入をする
・送金ができる
・メールアドレスを使ってなりすましメールを送る
このように正規利用者への被害はとても大きくなります。

セッションを用いた攻撃方法

具体的にセッションへの攻撃方法は3つあります。
・セッションIDの推測
・セッションIDの盗み出し
・セッションIDの強制

セッションIDの推測

Webアプリケーションに用いられるセッションIDの生成規則に問題があると、第三者にセッションIDが予測されやすくなります。
問題があるセッションID
・ユーザーIDやメールアドレス
・リモートIPアドレス
・日時
セッションIDの推測は外部から参照可能な値を用いて行われる手法です。
対策
独自でセッションID生成は行わない。
推測可能なセッションIDを生成しないために、独自での生成機構は作らず、フレームワークなどのWebアプリケーション開発ツールを利用することが安全とされています。
セッション管理機能に脆弱性が発見された場合、すばやく指摘・改善されることが期待できるためです。
Ruby on Railsもこのアプリケーション開発ツールの1つです。

セッションIDの盗み出し

JavaScriptでは、

<script>document.cookie</script>

というスクリプトで
クッキーの情報が表示できます。
このスクリプトへ細工をして、攻撃側のサーバに正規利用者のクッキーの情報を送付するようにすると、セッションハイジャックが成立します。
インターネット環境のセキュリティ対策が不十分であると、正規利用者とサーバーの通信の際に、故意または偶然にセッションIDが第三者へ受信されることがあります。

対策
・XSSを防ぐ
・安全なインターネット環境を用いる。
インターネット環境の安全性向上のためには、インターネット上の通信を暗号化する仕組みを使います。
この暗号化の仕組をSSLといいます。

SSL(Secure Sockets Layer)
SSLはインターネット上の通信を暗号化してくれる技術です。
暗号化して通信を行うことで、第三者からの情報の盗聴や改ざんを防ぐことができます。
このSSLを使うことで、クッキーのような常用な情報をやりとりする際に安全性を保つことができます。
SSL化されたURLはhttps://から始まるwebページのことです。

セッションIDの強制

セッションを攻撃者が顔分から強制的に固定化する方法です。
具体的な手順は
①攻撃者が正規の利用者としてセッションID(abc)を取得
②正規利用者に対して、①で取得したセッションIDを強制
③正規利用者は標的アプリケーションにログイン
④攻撃者は正規利用者に強制したセッションID(abc)を使って標的アプリケーションにアクセスする

セッションとは利用者の状態を保持するために使われていました。
正規利用者は攻撃者が強制したセッションでログインなどの認証が完了した状態が一時保存されています。
つまりこのセッションを用いて攻撃者もログイン済みのページにアクセスすることができてしまいます。
対策
ログイン後にセッションIDを変更する
セッションIDを固定化する方法は多種多様であるため、固定化されないように常に変更し続けることが重要です。
例えば、ログインした直後にセッションIDを変更するとしておくことで、セッションを強制されていても、ログイン後に更新されるため、攻撃者が強制したセッションを用いてWebアプリケーションを利用することはできません。
Ruby on RailsのGemの1つであるdeviseはこの仕組を採用しています。

まとめ

セッションとはログイン情報を一時保存できる仕組みでクッキーという場所に保管されている。
セッションを用いたセキュリティ攻撃には推測、盗み出し、強制の3つがある。
推測:独自の生成規則を作らず、既存のフレームワークを使うことでセッションIDを推測されにくくなる。
盗み出し:基本的なXSSの対策を行う。
強制:セッションIDをログイン後に変更することでセッションIDの固定化を防ぐ。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

重要な処理(購入処理)における脆弱性と対策(CSRF)

前提
プログラミング初学者(2~3ヶ月)が学んだ内容です。
実際の現場で通用しないことや間違ったことが含まれている可能性があります。
間違っている部分や理解が浅いと思われる部分についてはご指摘や追記いただければ幸いです。

重要な処理に対する攻撃の1つ、CSRF(クロスサイトリクエストフォージェリ)についてまとめました。
重要な処理とは例えば、
掲示板への投稿や、メールの送信、ECサイトでの商品購入といった本来外部から実行されてはいけない処理のことです。
CSRFではこの重要な処理を攻撃者が正規利用者になりすまして使う(リクエストを送る)ことを言います。

CSRFの仕組みと対策

具体的にCSRFはどのようなことをされるのか
Webアプリケーションには以下のような重要な処理が存在します。
・クレジットカードでの決済
・メールの送信
・パスワードの変更
こういった重要な処理を正規利用者になりすまして攻撃者が使えてしまいます。

CSRF(クロスサイトリクエストフォージェリ)

攻撃者がユーザーのログイン情報を盗み出し(セッションハイジャック)脆弱性のあるWebアプリケーションに対して、正規利用者になりすましてリクエストを送る攻撃のこと
なりすましのリクエストは不正なリクエストであり、この不正なリクエストを判別できないWebアプリケーションはCSRFの脆弱性があると言えます。

CSRFの脆弱性があると発生しうる被害

CSRFの脆弱性があると、以下の被害を受ける可能性があります。
・利用者アカウントが不正利用される
・利用者のパスワードやメールアドレスが変更される
・利用者の所持している金銭が利用される

CSRFの仕組み

CSRFが行われる手順
①攻撃者がユーザー情報を盗み出すWebサイトを用意する
攻撃者が、あらかじめ情報を盗むため(セッションハイジャック用)のWebサイトを作成する。
②ユーザーが脆弱性のあるWebアプリケーション上でログインし、セッションIDを取得する
通常通りWebサイトを利用するために脆弱性のあるWebアプリケーションにログインする。
③Webアプリケーションにログインした状態で悪意あるサイトにアクセスする
脆弱性のあるwebサイトから悪意あるWebサイトにアクセスすることでセッションハイジャックが成立し、ログイン情報を攻撃者に盗まれる。
④攻撃者が抜き出したログイン情報をもとに、脆弱性のあるWebアプリケーションへ不正なリクエストを行う
攻撃者がセッションハイジャックによって盗み出したログイン情報を使って、正規利用者になりすまし、不正なリクエスト(商品の購入や金銭の送金など)を行う。

脆弱性のあるWebアプリケーションではこの不正なリクエストを不正か否か判別することができません。
このようにしてCSRFが成立します。
ちなみに、XSSはクライアントサイドでの脆弱性、CSRFはサーバーサイドでの脆弱性の問題です。

CSRFの対策

CSRF対策すべき項目の洗い出し

まずはCSRFの対策を行う必要のあるリクエストを知ることが重要です。
CSRFは全てのリクエストに行う必要はありません。
対策が必要なリクエストは他ユーザーから勝手に実行されては困るようなリクエストです。
例えば、ECサイトの購入処理、パスワードの変更処理といったリクエストです。

具体的な対策方法

正規ユーザーからのリクエストか否か判別する有効な手段が2つあります。
・確定前のパスワード入力
・リスエストへのトークンの埋め込み

対策①パスワードの再入力

重要な処理が確定する前に再度パスワードを入力してもらうようにします。
CSRFの攻撃対象であるパスワード変更に関わるリクエストも現在のパスワードを入力させることにより CSRF攻撃を防ぐことが可能です。
セッションハイジャックによって攻撃者がログインしている状態ではありますが、パスワード自体がわかっているわけではないため使える方法です。

対策②リクエストへのトークン(秘密情報)の埋め込み

登録画面、注文確定画面などのCSRF対策が必要なページに対して、攻撃者が知り得ないトークンを要求するようにすれば、不正リクエストによる重要な処理は実行されません。

トークン
トークンとは1度だけ使用可能なパスワードで、ユーザーのブラウザ上のみに保管されているもの
ユーザーの認証などに用いられるケースが多くあります。
ログインなどの処理を行うと送信者のブラウザで固有のトークンが保存されます。
ほかユーザーから同様のリクエストをするとトークンが異なったり、存在していません。
こうして、不正なリクエストか否かを判別することができます。
ちなみに、Ruby on Railsのフォームであるヘルパーメソッドではこの対策が取られています。

最後に

注意点
お気づきの方もいると思いますが、XSS脆弱性やセッションハイジャック脆弱性が存在することで、このトークンすらも抜き出される可能性があります。
その結果CSRF対策が無意味になってしまいます。
つまり
セキュリティ対策はあらゆる面で総合的に行う必要がある
ということです。
どこか1箇所を強化しても、別の弱い場所からその強化した1箇所を突破する糸口がでてくる可能性があるということです。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【Ruby】even?メソッドを使用して配列の中の偶数の数をカウントする

配列にある値の中から、偶数の数をカウントして出力するメソッドを作ります。
値が偶数かどうかの判定はeven?メソッドを使います。

出力例:
count_evens([2, 1, 4, 6]) → 3

解答1

def count_evens(nums)
  count = 0
  nums.each do |num|
    if num.even?
      count += 1
    end     
  end
  puts count
end
  • 偶数の数を入れておく変数countを用意
  • eachメソッドで配列の中を1つずつ取り出してeven?メソッドで偶数か判定
  • 偶数だったらcountに1を足す
  • これを繰り返して最後にcountを出力する

解答2

解答1と比べるとまどろっこしい感じですが・・・

def count_evens(nums)
  result = []
  nums.each do |num|
    if num.even?
      result << num
    end
  end
  puts result.length
end
  • result = []は偶数の値を入れるための配列
  • 解答1と同じく、eachメソッドで配列の中を1つずつ取り出してeven?メソッドで偶数か判定
  • 偶数ならresult << numで配列の中にその偶数を入れる
  • 最後にresult.lengthで配列の中の要素数(つまりここでは配列の中に入っている偶数)がいくつあるか出力

おまけ

奇数かどうか判定するときはodd?メソッド

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

チャットアプリケーションで一人二役の動作確認を行う

背景

railsでチャットアプリケーションを開発した。
ブラウザで二つのウィンドウを開いて動作確認をしようとしたところ、うまくいかなかった。
具体的には、片方のウィンドウはユーザA、もう片方のウィンドウはユーザBでログインし、ユーザAが投稿したコメントがユーザBのウィンドウに反映されるか確認しようとした。
しかし、片方のウィンドウでユーザAとしてログインした後、もう片方のウィンドウでユーザBとしてログインすると、両方のウィンドウがユーザBとしてログインしている状態になってしまって、やりたいことができなかった。

解決策

片方は通常のウィンドウで起動し、もう片方のウィンドウはシークレットモードで立ち上げることで、それぞれのウィンドウが干渉せず、やりたいことができた。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む