- 投稿日:2020-09-12T23:58:24+09:00
ActiveHashを解説してみた
ActiveHash
職業選択などの変更されないデータをモデルファイル内に直接記述することで、データベースへ保存せずにデータを取り扱うことができる。つまり、Active_Hashを用いることで、モデルファイルに直接記述した変更されないデータに対して、ActiveRecordのメソッドを用いることができる。
導入方法
Gemfile
qiita.rbgem 'active_hash'記述したらbundle installを実行する。
モデル作成時に押さえておくべきこと
結論、--skip-migrationを使用すること。
理由は、データベースを作らない。すなわちマイグレーションファイルは不要となるからだ。ここでrails g modelコマンドを使用してしまうと、マイグレーションファイルも同時に作成されてしまうので注意が必要。% rails g model モデル名 --skip-migrationActiveHash::Base
ActiveRecordと同様のメソッドが使用できる。
つまり、ActiveHash::Baseを継承することで、モデルに定義したオブジェクトに対してActiveRecordのメソッドが使用できるようになる。qiita.rbclass ShippingFee < ActiveHash::Base self.data = [ { id: 0, name: '---' }, { id: 1, name: '着払い(購入者負担)' }, { id: 2, name: '送料込み(出品者負担)' } ] endself.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が取り入れられているのですね。
実装も簡単なので、積極的に使っていきたいと感じました。今後も学習を進めていく中で、役立つ情報をどんどん発信していきたいと思うので、よろしくお願いします。
ここまで記事を読んでいただき、本当にありがとうございました‼
- 投稿日:2020-09-12T21:19:30+09:00
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.jswindow.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イベントの違い[タイミング]
- 投稿日:2020-09-12T21:01:35+09:00
(ギリ)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ハッシュ化して、紐付けた画像がちゃんと表示されるかどうか試してみましょう。
→ 画像登録して、コンソールで新しいユーザー作ったら表示されました。
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.rbvalidates :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.rbassert_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.rbtest "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.rbassert_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と呼ばれる。
- 投稿日:2020-09-12T20:04:58+09:00
<超初学者向け>「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用のチャネルを作成する
①LINE Developersに登録、チャネルを作成
ここの説明は省略させてもらいます。以下に載せているURLから公式サイトのページに飛べます。これ確認しながら作業してください。チャネルの名前は何でも問題ありませんが、友達登録したときに表示される名前です。こだわりがなければ「チャットボット君」でいいと思います。。
https://developers.line.biz/ja/docs/messaging-api/getting-started/#using-consoleここまででチャネルの作成が完了していると思うので、引き続きチャネルの設定を変更していきます。
②チャネルアクセストークン(長期)発行
赤枠内のボタン通してアクセストークンを発行してください。
チャネルアクセストークンとは??となっている方は以下のページで確認してください。
https://developers.line.biz/ja/docs/messaging-api/channel-access-tokens/
つまりまとめると、「アプリからAPIを呼び出すための情報」です。③応答設定の変更
次は応答設定を変更していきます。ひとまず何も考えず、画像の通り設定変更してください。
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/
開発環境の構築
①AWSアカウントの作成
ここも公式サイトにまとめられているので、詳細はこちらで確認して作業をしてください。
https://aws.amazon.com/jp/register-flow/
②Cloud9で開発環境を作成
基本的にはデフォルトのままで問題ありませんが、プラットフォームのみ「Ubuntu Server 18.04 LTS」に変更してください。
次からはチャットボットを実装するため、コードを修正していきます。ここで作成した開発環境を利用して進めていってください。
◇チャットボットの開発
①PostgreSQL用パッケージのインストール
今回は無料利用枠で利用できるHerokuにデプロイするため、データベースはPostgreSQLを利用します。以下コマンドを実行してください。
bashsudo apt install libpq-dev②新規Railsプロジェクトの作成
以下コマンドでインストールするデータベースを指定してプロジェクトを作成してください。
bashrails new <プロジェクト名> -d postgresql
③LINEBOT用のコントローラを作成
"②"の手順で作成したプロジェクトの階層に移動して、以下コマンドを実行してください。
bashrails generate controller linebot④Gemfileの修正
以下をGemfileの末尾に追加して、「bundle install」を実行してください。
Gemfilegem 'line-bot-api' gem 'dotenv-rails'bashbundle install
⑤ルーティング設定
対象のファイルに以下内容をコピペしてください。
config/routes.rbRails.application.routes.draw do post '/callback' => 'linebot#callback' end⑥コントローラ設定
対象のファイルに以下内容をコピペしてください。app/controllers/linebot_controller.rbclass 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のインストール状態を確認します。インストールされていない場合はインストールしてください。
bashgit --version
②Gitの設定を追加
以下コマンドを実行して、Gitの設定を追加してください。
bashgit config --global user.name "ユーザ名" git config --global user.email "メールアドレス" git init◇Herokuにアプリを作成する
⓪Herokuのアカウント作成
今回は作成したアプリをHerokuにデプロイします。まずは以下サイトからアカウントを作成してください。
https://id.heroku.com/login
①Herokuのインストール
以下コマンドでインストール状態を確認してください。bashheroku --version
インストールされていない場合は以下コマンドを実行して、再度インストール状態を確認してください。
bashcurl https://cli-assets.heroku.com/install-ubuntu.sh | sh②Herokuにログイン
以下コマンドを実行して、ログインします。Herokuに登録した際のメールアドレスとパスワードを求められるので、入力してください。
bashheroku login --interactive
③アプリ作成
以下コマンドを実行して、アプリを作成します。アプリケーション名は省略するとランダムな名前で作成されます。
bashheroku create <アプリケーション名>※ここでアプリのURLが作成されるので、控えておいてください。
◇チャネルの設定にアプリの情報を追加する
①先ほど控えたURLをチャネルの設定に追加
②callbackアクションを呼び出すため、URLの末尾に/callbackを追加
◇Herokuにデプロイ
①環境変数の追加
以下コマンドを実行して環境変数を追加します。
bashheroku config:set LINE_CHANNEL_SECRET=チャネル設定画面で確認できるChannel Secret heroku config:set LINE_CHANNEL_TOKEN=チャネル設定画面で確認できるアクセストークン heroku config:set TALK_API_KEY=Talk APIの利用申請で取得したキー②Herokuにデプロイ
以下コマンドを実行してHerokuにデプロイしてください。
bashgit add . git commit -m "linebot" git push heroku master◇動作確認してみよう
①友達追加
QRコードから作成したチャットボットを友達追加してください。
②動作確認
以下のようにメッセージに応じて返信が帰ってきたら成功です!!
◆参考にした記事
僕が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最後に
最後まで見ていただき、ありがとうございます。気になる点などはコメントで指摘いただけるとありがたいです。
- 投稿日:2020-09-12T18:51:05+09:00
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が関係していると思っていて、イベントループ型のサーバはシングルプロセスなのでサーバのリソースを使い切りにくい、という事情も感じます。おわり
- 投稿日:2020-09-12T18:29:54+09:00
【Ruby on Rails】郵便番号から住所を自動入力
目標
開発環境
ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina前提
※ ▶◯◯ を選択すると、説明等が出てきますので、
よくわからない場合の参考にしていただければと思います。homesコントローラーを作成し、以下を記述済。
config/routes.rbroot 'homes#top' get 'mypage', to: 'homes#mypage'app/controllers/homes_controller.rbclass 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でのログイン
- 参考:こちらで詳しく説明しています。
Gemfilegem '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 enddevise:controller
ターミナルrails g devise:controllers usersapp/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 enddevise:model
未入力を防ぐため、下記記述を追加。
app/models/user.rbvalidates :postal_code, presence: true validates :prefecture_code, presence: true validates :city, presence: true validates :street, presence: truedevise:routing
config/routes.rbRails.application.routes.draw do devise_for :users, controllers: { sessions: 'users/sessions', registrations: 'users/registrations', } root 'homes#top' get 'mypage', to: 'homes#mypage' enddevise:view
ターミナル$ rails g devise:views usersform_forの中に記述。
app/views/devise/registrations/new.html.erbform_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の追加
Gemfilegem 'jp_prefecture' # 都道府県コードから都道府県名を変換するgem gem 'jquery-rails' # RailsでjQueryを使えるようにするgemターミナル$ bundle install3、jquery.jpostal.jsを導入
https://github.com/ninton/jquery.jpostal.js/
上記URLに遷移後、緑色の「Code」タブを押して、
赤丸部分にてzipファイルをダウンロード。
解凍後、「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>参考
- 投稿日:2020-09-12T18:04:26+09:00
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 :hello
とputs :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.recv
はputs :fuga, :hoge
となり、fuga
とhoge
がそれぞれ出力されるということです。これが
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|
のようにブロック内で使用できる変数として受け取ることができます。例えば以下のコードは
r1
でtake
メソッドが実行されるまで処理を待ちます。r1 = Ractor.new 42 do |arg| Ractor.yield arg endまた
Ractor.new
には別のRactor
を渡すことができるため以下のように書くことができます。r2 = Ractor.new r1 do |r1| r1.take endこれで
r1
が引数として受け取った42
をr2
の中で受け取ることができます。最後に
puts r2.take
で42
を受け取って出力しています。
pull型
はこういった流れになります。ざっくりと解説すると
push型
:Ractor#send
+Ractor.recv
pull型
:Ractor.yield
+Ractor#take
という感じです。
より詳細な
Ractor
の解説に関しては下記のリンクを参照していただければと思います。
- A proposal of new concurrency model for Ruby 3
- Guild Prototype
- Ruby向け並列化機構Guildの試作
- Guild → Ractor
- [JA] Ractor report / Koichi Sasada ko1
- https://github.com/ko1/ruby/blob/ractor/ractor.ja.md
- Ractor - Ruby's Actor-like concurrent abstraction
- Ractor: a proposal for a new concurrent abstraction without thread-safety issues
Ractorのコード
Ractorの生成
Ractor
はRactor.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 # => SyntaxErrorRactorでの返り値
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 # => LocalJumpErrorRactor内での例外
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実行すると
42
と21
という出力がそれぞればらばらに表示されます。ちょっとした例
以下のような感じで複数の
worker
をRactor
で生成し、それを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
を使って試したコードを追加していこうと思います参考
- A proposal of new concurrency model for Ruby 3
- Guild Prototype
- Ruby向け並列化機構Guildの試作
- Guild → Ractor
- [JA] Ractor report / Koichi Sasada ko1
- https://github.com/ko1/ruby/blob/ractor/ractor.ja.md
- Ractor - Ruby's Actor-like concurrent abstraction
- https://gist.github.com/niku/c19da11edf0b97470af27844b44d12fa
- Ractor: a proposal for a new concurrent abstraction without thread-safety issues
- 投稿日:2020-09-12T17:22:19+09:00
簡単なジャンケン
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. #結果を表示する
- 投稿日:2020-09-12T15:46:33+09:00
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.jsrequire("@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.jsdocument.addEventListener("DOMContentLoaded", function(){}パスワードの表示非表示はできましたが、一度リロードを押さないと反映がされませんでした。なのでjsファイル問題は解決していませんが、とりあえずやりたいことはできたのでよしとします。(可読性はよろしくないですが)
4.ここから学んだこと
このエラーの中でhtml/css/javascript/の読み込む順番やDOMがどの段階で形成されていくのかが大変勉強になりました。tutbolinksやdocumentloadedについてはまだまだ勉強が足りていないですが、流れだけでも把握できたのはかなり大きいです。scriptにしたとたんすぐに解決したのも、書く順番を把握していたおかげでした。
参考にしたURL
"DOMContentLoaded周りの処理を詳しく調べてみました"
"【JS】addEventListenerが機能しない理由についてご教示ください"
"DOMContentLoadedイベントとloadイベントの違い[タイミング]"
- 投稿日:2020-09-12T14:56:42+09:00
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) #でOKif
score = gets.to_i if score > 80 then puts "great!" else if score > 60 then puts "good" else puts "so so..." endcase
#case signal = gets.chomp #最後の改行コードを取り除くchomp case signal when "red" puts "stop!" when "green" puts "go!" when "yello" puts "caution!" else puts "wrong signal" endWhile
#while i = 0 while i < 10 do puts "#{i}: hello" # #{i}は回数を見やすくする為に使用 i += 1 #i = i + 1を短縮して書ける endTimes
#times 10.times do |i| #i += 1と同じ効果 puts "hello" endfor
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 endeach
(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
- 投稿日:2020-09-12T14:44:17+09:00
【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の構文など、もう少し丁寧なコードにできそうな部分を中心に基礎固めをしようと思います。きっかけをくださった回答者の皆さまに、心よりお礼申し上げます。
- 投稿日:2020-09-12T13:48:02+09:00
【Rails】LINEBotでPush送信
概要
RailsからLineBotのPush送信します
以下のように、任意のタイミングで送信するやつです
LINEBotの作成
記事がたくさんあるので、そちらを参照してください
例えば、以下のような記事がありますLINE BOTの作り方を世界一わかりやすく解説(1)【アカウント準備編】
RailsからLINEBotでPush送信
- Gemfileでライブラリ
line-bot-api
をインストールしておいてくださいENV["LINE_CHANNEL_SECRET"]
、ENV["LINE_CHANNEL_TOKEN"]
は、herokuなど、サーバ側で、設定する必要があります。この後、説明します- 自分のuser_idは、チャンネル基本設定->あなたのユーザーID、から確認できます
- push_messageがlinebotでpush送信する部分です
linebot_controller.rbclass 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 endRailsアプリを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送信できました
- 投稿日:2020-09-12T09:55:31+09:00
セッションを用いたセキュリティ攻撃(セッション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の固定化を防ぐ。
- 投稿日:2020-09-12T09:28:35+09:00
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を返す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.rbdef index @aaas = User.paginate(page: params[:page]) endapp/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>こうしなければならないのかな?推測ですけれど、、、
色々とよしなに解釈してくれるのは勿論そのためのルールに則った記述をしてこそですからね、、、
終わりに
今回はリファクタリングに悩まされた章でした。
少し曖昧な部分も残してしまったので、何度か振り返り、明確に理解できるよう学習を進めたいです。
- 投稿日:2020-09-12T09:07:06+09:00
重要な処理(購入処理)における脆弱性と対策(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箇所を突破する糸口がでてくる可能性があるということです。
- 投稿日:2020-09-12T08:29:42+09:00
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まで含むと長くなりすぎるのでここまで。続き
→
【連載目次へ】
- 投稿日:2020-09-12T08:08:02+09:00
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:string3.DB作成
$ rails db:create4.migrate
$ rails db:migrate5.Action Textインストール
$ rails action_text:install6.migrate
$ rails db:migrate7.アソシエーション
Action Textのデータは専用のテーブルに格納されるためarticleモデルに関連づける必要がある。
app/models/article.rbclass Article < ApplicationRecord has_rich_text :content end8.ビューに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 ...終了
- 投稿日:2020-09-12T01:20:18+09:00
【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?メソッド
- 投稿日:2020-09-12T00:30:48+09:00
チャットアプリケーションで一人二役の動作確認を行う
背景
railsでチャットアプリケーションを開発した。
ブラウザで二つのウィンドウを開いて動作確認をしようとしたところ、うまくいかなかった。
具体的には、片方のウィンドウはユーザA、もう片方のウィンドウはユーザBでログインし、ユーザAが投稿したコメントがユーザBのウィンドウに反映されるか確認しようとした。
しかし、片方のウィンドウでユーザAとしてログインした後、もう片方のウィンドウでユーザBとしてログインすると、両方のウィンドウがユーザBとしてログインしている状態になってしまって、やりたいことができなかった。解決策
片方は通常のウィンドウで起動し、もう片方のウィンドウはシークレットモードで立ち上げることで、それぞれのウィンドウが干渉せず、やりたいことができた。
- 投稿日:2020-09-12T00:26:58+09:00
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.rbdef log_in_as(user) session[:user_id] = user.id #使えない end
test/helpers/sessions_helper_test.rbtest "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.rbdef 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 endcurrent_userメソッドは戻り値にモデルのインスタンスを返すモデルオブジェクト。
test/helpers/sessions_helper_test.rbtest "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.rbdef 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 endif user && user.authenticated?(cookies[:remember_token])こちらの検証に置いてcurrent_userがnilとなるか確かめています。
終わりに
こちらの章ではエラーで躓くことも少なく、内容もよく理解出来ました。