- 投稿日:2020-04-09T23:42:55+09:00
正規表現(Ruby)
\d は「半角数字1文字」を表す
\d は「1個の半角数字(0123456789)」を意味するメタ文字です(文字の集合を表しているので、特に 「文字クラス」 と呼ばれます)。
{n,m} は「直前の文字が n 文字以上、m 文字以下」であることを表す
/\d{1,3}/
=>1桁以上3桁以下
○ 1, 43, 576
✖️ 3451, 05969
{n} は「直前の文字がちょうど n 文字」であることを表す
/\d{4}/ => 4桁
/3{5}/ => 33333
[AB] は「AまたはBが1文字」であることを表す
/[OK]/ => OかK
[a-z] と [-az] ではハイフンの意味が異なる
/[a-c]/ => abc
/[-ac]/ => -かaかc
正規表現の正確さと複雑さはトレードオフになることが多い
メールアドレスの正規表現は下記の通り。
複雑で考えていると莫大な時間を使ってしまうため、ある程度に抑えることもしばしばある。(?:[a-z0-9!#$%&'+/=?^_`{|}~-]+(?:.[a-z0-9!#$%&'+/=?^_`{|}~-]+)|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])")@(?:(?:a-z0-9?.)+a-z0-9?|[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\[\x01-\x09\x0b\x0c\x0e-\x7f])+)])
区切り文字が1文字、 もしくは区切り無し
「~が1文字、または無し」を表現するためには ? というメタ文字を使います。(文字量を指定するので 量指定子 のひとつです)
/フランシスコ・?ザビエル/ => フランシスコ・ザビエルかフランシスコザビエル
任意の1文字
「任意の1文字」を表す . というメタ文字(文字クラス)があります。
/オリバー.カーン/ => オリバー カーンやオリバー・カーンやオリバー@カーン
対象の言葉を含む1行を抜き出す
オリバーカーン等を含む1行を文章から抜き出す。
rubytext.split(/\n/).grep(/オリバー.?カーン/)
- 投稿日:2020-04-09T23:19:28+09:00
-これからポートフォリオを作成する方へ- 設計図の作成方法:前半
ポートフォリオを作成する際に、何から始めれば良いかわからない・・・という方に向けて。
第1歩目として設計図を作成する事を強くオススメします。理由は以下の通りです。・少しずつ目標に近く感覚を可視化でき、モチベーションの維持に繋がる
そのためにどのようなツールを使用し、どのような物を作成すれば良いかまとめました。
●使用ツール
こちらはCacooというサービスをオススメします。
https://cacoo.com/会員登録から一定期間は無料ですので、無料期間中にさくっと作ってしまいましょう。
またPDF出力が可能なので、作成した設計図をPDFとして保存してしまえば無料期間中だけでも事足ります!●作成すべき物① -ER図-
詳しい作成方法はこちらの投稿が非常にわかりやすかったのでオススメです。
「やさしい図解で学ぶ ER図 表記法一覧」
https://qiita.com/ramuneru/items/32fbf3032b625f71b69dあくまでポートフォリオ作成時の準備なので、ここに学習コストを割く必要はないかと思います。
ポートフォリオ作成レベルであれば、上記の記事にかかれている内容を把握すれば十分記載できるかと思います。●作成すべき物② -テーブル設計図-
テーブル設計は以下のようにまとめるとわかりやすいかと思います。
ポートフォリオは就職活動用に作成する物なので、期限も限られていますし他に労力を割く必要もあるかと思います。
ですので、自分が作成する最低限のサービスをまず作成し、その後に状況にあわせ機能追加していく形で計画すると良いと思います。カラムの型の種類や、カラムの追加・削除・編集方法を簡単にまとまっているのがこちらです。
「カラムの型」
https://qiita.com/mokku/items/39ced66550e9b623b5a8railsを勉強してる人にとってDB周りは苦手意識のある方が多いと思うので(というか自分ですが)、まずは基本的なことから。
残すはサイトマップとワイヤーフレームです。そちらに関しては今後またまとめたいと思います。
- 投稿日:2020-04-09T22:30:11+09:00
2020年版 VSCodeの良さげな拡張機能紹介
日々VSCodeをより気持ちよく使用するために拡張機能の探求は欠かせません。
今回は私の独断と偏見で良さげな拡張機能を紹介します。見た目編
Peacock
https://marketplace.visualstudio.com/items?itemName=johnpapa.vscode-peacock
VSCodeのウィンドウをおしゃれにできます。
私は複数の環境(プロジェクトやプログラミング言語)を使い分けるので、ウィンドウごと色を変えることで気持ちも一緒に切り替えています。Ubuntu VSCodeTheme
https://marketplace.visualstudio.com/items?itemName=ThiagoLcioBittencourt.ubuntuvscode
見た目をUbuntuっぽくできます。ただの気分です。
私はMacの壁紙をUbuntuにしています(Ubuntu使えというツッコミは受け付けません)Rainbow CSV
https://marketplace.visualstudio.com/items?itemName=mechatroner.rainbow-csv
CSVを開いたときに色分けしてくれて見やすくなります。
indent-rainbow
https://marketplace.visualstudio.com/items?itemName=oderwat.indent-rainbow
インデントがカラフルになってちょっと見易くなります。
共通機能系
Settings Sync
https://marketplace.visualstudio.com/items?itemName=Shan.code-settings-sync
複数のPCでVSCodeを使用する場合、各PCでVSCodeの設定を行う必要がありますが、こちらの拡張機能を使えば設定を共有できます!
Vim
https://marketplace.visualstudio.com/items?itemName=vscodevim.vim
VSCodeをVimっぽく使いたい方におすすめ。ただし、コテッコテのVimmerは痒いところに手が届かない可能性あり。
私は普段VimとVSCodeのハイブリットなので重宝しています。GitLens
https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens
Git使うならまず入れた方が良いです。
Git操作をせずにいつ誰が該当のソースを修正したのかがパッと分かります。言語別
ここからは言語別の拡張機能紹介です。
デバッガーやLanguage Supportは書くまでも無いと思うので割愛します。PHP
PHP Intelephense
https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client
Language Supportは書かないって言ったくせに早速書きました。
PHPは他のLanguage Supportもあるのですが、こちらの拡張機能の方が頭が良い気がしたので紹介します。PHP DocBlocker
https://marketplace.visualstudio.com/items?itemName=neilbrayfield.php-docblocker
PHPDocを自動で作ってくれます。intって書いてもintegerと書かれるお茶目さがあります。
(多分自分で設定すれば修正できますが)PHP Namespace Resolver
https://marketplace.visualstudio.com/items?itemName=MehediDracula.php-namespace-resolver
Classのインポートを自動でやってくれます。(IDEから乗り換えた人はとりあえず入れて見てください。ストレスが減ります)
一括インポートもできるので結構良いです。Ruby
endwise
https://marketplace.visualstudio.com/items?itemName=kaiwood.endwise
自分で
end
って書くの面倒ですよね?
こちらの拡張機能は自動で付与してくれます。ruby-rubocop
https://marketplace.visualstudio.com/items?itemName=misogi.ruby-rubocop
別途Gemの追加が必要ですが、静的チェックとフォーマットをやってくれるので入れておきましょう。
自分の悪いところも容赦無く教えてくれたり、修正してくれるので勉強にもなります。ifじゃなくてunless使いな〜とか、ネスト減らせるよ〜とか、
eachじゃなくてmap使いなーとか言ってくれるのでレビュワーの負担も減ると思います。JavaScript
Prettier
https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode
JS触っている人なら当たり前だと思いますが、自動でコード整形してくれるので便利です。
ただ、保存時に整形する設定を入れると差分がめっちゃ出るリポジトリもあったりすると思うので程々に。Debugger for Chrome
https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome
デバッガーも書かないと言ったくせに書いてしまいました。
JSのデバッグをVSCode上で行えるので便利です。もうconsole.log
デバッグとはおさらば。その他
Remote-SSH
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh
SSH先のPC上のソースを編集することができます。
私は自宅のUbuntuや、Vagrantに対してSSHして開発する際に使用しています。
ただソースを編集できるだけでなく、ポートフォワードも行えるのでリモート先の8080ポートをローカルの8080にすることもできたりします。
とても便利なのでSSHで開発する方はぜひ使ってみてください。Remote-Container
https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers
Dockerコンテナの中のソースをVSCodeから修正できちゃいます。
もちろんコンテナなのでVolumeマウントしないと修正内容が飛んでしまいますが、開発環境も含めてコンテナ化しちゃうような時は良さげ。昔別記事書いてます。
https://qiita.com/MasanoriIwakura/items/fe1bb1fade98c0aa65d2
https://qiita.com/MasanoriIwakura/items/e7a2045b2de28c76ccd9REST Client
https://marketplace.visualstudio.com/items?itemName=humao.rest-client
なんと、VSCode上からREST APIを試すことができます!
デバッグや外部APIの確認に便利。
まだまだ拡張機能は沢山あるので、気が向いたタイミングで更新していきます!
- 投稿日:2020-04-09T22:23:35+09:00
Rails メール自動配信機能をActionMailerとwheneverを使用して実装する
Railsで毎日メールを送るためのバッチ処理を、
ActionMailer
とgemのwhenever
を使用して実装しました。手順
- gem
'whenever'
導入schedule.rb
にバッチ処理を記述schedule.rb
の内容を登録- メールを送信するメソッドを定義
- viewに送信するメール本文を作成
- logファイルを確認して実行されているか確認
環境
ruby '2.5.7'
gem 'rails', '~> 5.2.4', '>= 5.2.4.1'前提
- Userテーブルにemailカラムがあること
ActionMailer
で送信するサーバーの設定が出来ていること- メール送信するために必要な
ApplicationMailer
を継承するクラスを作成していること※ (
UserMailer
という名前で作成済)1. gem
'whenever'
導入
'whenever'
って?
自動的にメール送信するなど、バッチ処理を行うためにはcronというものを使ってcrontabにそのための記述をします。
でもその小難しいものを簡単に書けるようにするgemが'whenever'です。それではgem 'whenever'をインストールします。
gemファイルに入力。Gemfilegem 'whenever', require: falseターミナルでGemをインストールします。
$ bundle install2.
schedule.rb
にバッチ処理を記述するまずは
schedule.rb
ファイルを作成します。
以下をターミナルに入力する$ wheneverize . [add] writing './config/schedule.rb' [done] wheneverized!今作った
schedule.rb
の中にバッチ処理を記述しますconfig/schedule.rb# Use this file to easily define all of your cron jobs. # # It's helpful, but not entirely necessary to understand cron before proceeding. # http://en.wikipedia.org/wiki/Cron # Example: # # 絶対パスから相対パス指定 env :PATH, ENV['PATH'] # ログファイルの出力先 set :output, 'log/cron.log' # ジョブの実行環境の指定 set :environment, :development # # every 12.hours do # command "/usr/bin/some_great_command" # runner "MyModel.some_method" # rake "some:great:rake:task" # end # every 1.days, at: '9:00 am' do # Rails内のメソッド実行 runner "UserMailer.notify_user" end # Learn more: http://github.com/javan/whenever絶対パスから相対パス指定
env :PATH, ENV['PATH']
ENV['PATH']
を特に何も定義してないけど書いたら上手くいった。
これがないとエラーになるので注意。ログファイルの出力先
set :output, 'log/cron.log'
log/cron.log
が新規作成され、バッチ処理の記録が入る。ジョブの実行環境の指定
set :environment, :development
environment
が環境という意味。
:development
rails内の開発環境。今回はこれを指定してます。
:production
デプロイ後の本番環境
:test
テスト実行環境(Rspecなど)バッチ処理の記述
every 1.days, at: '9:00 am' do
英文そのまま。毎日朝の9時に行います。
runner "UserMailer.notify_user"
UserMailer
内のnotify_user
メソッドを実行します。
※ApplicationMailer
を継承するクラスの中のメソッドを指定しています。
runner
Railsに定義されたメソッドを呼び出すために使用します。
今回の場合はUserMailer
のnotify_user
メソッドを呼び出すのでこれに当てはまる。
(UserMailer
は以前に自分で作成したものです)
※ユーザーに直接関係のある機能の場合に使用する。コントローラやモデルを呼び出せる。
rake
実行させたいrakeファイルを指定する場合に使用します。
今回は使ってません。使用する場合は別途rakeファイルを作成する。
※システムの管理上必要な機能の場合に使用する。定時になると自動的にデータを削除するなど。3.
schedule.rb
の内容を登録する現状の
crontab
に何が記述されているのかを確認します。$ crontab -e中はいろいろ書いてるけど、先ほど記述した
UserMailer.notify_user
の一文があればOK※nanoエディタが起動されます。
終了する時は焦らずにCtrl + x
問題がなければ以下のコマンドを実行して登録します。
$ bundle exec whenever --update-crontab上記のコマンドで、作成したジョブが
crontab
に登録されました。
反映されているか確認します。$ crontab -lちゃんと
UserMailer.notify_user
の記述があれば登録されてます4. メールを送信するメソッドを定義
メールを送信する
notify_user
メソッドを定義します。
user全員にメールを送信するようにしてます。mailers/user_mailerclass UserMailer < ApplicationMailer def notify_user default to: -> { User.pluck(:email) } mail(subject: "everyday Bookers!yay!") end end
default to: -> { User.pluck(:email) }
宛先を指定します。pluckでUserのemailを全て取得する。mailers/application_mailerclass ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' end
application_mailer
には全部のメールに共通する設定を記述します。
default from: 'from@example.com'
サーバーに登録してあるメールアドレスが勝手に入ります。5. viewに送信するメール本文を作成する
user_mailer
内にnotify_user.text.erb
と新規作成する。
※textで作成しましたがhtmlでも良い。user.nameなど変数が呼び出せます。user_mailer/notify_user.text.erb内容を書きます
6. logファイルで実行されているか確認する
実行されているか確認したい場合は、1分毎にメールが送られる設定に書き換えてみましょう。logを確認するためです、仕方ない。
every 1.days, at: '9:00 am' do
をコメントアウトして、以下の一文に書き換えます。
every 1.minutes do
config/schedule.rb# Use this file to easily define all of your cron jobs. # # It's helpful, but not entirely necessary to understand cron before proceeding. # http://en.wikipedia.org/wiki/Cron # Example: # # 絶対パスから相対パス指定 env :PATH, ENV['PATH'] # ログファイルの出力先 set :output, 'log/cron.log' # ジョブの実行環境の指定 set :environment, :development # # every 12.hours do # command "/usr/bin/some_great_command" # runner "MyModel.some_method" # rake "some:great:rake:task" # end # every 1.minutes do ##ここを追加 # every 1.days, at: '9:00 am' do ##ここをコメントアウト # Rails内のメソッド実行 runner "UserMailer.notify_user" end # Learn more: http://github.com/javan/whenever忘れずにターミナルで設定を登録します。
$ bundle exec whenever --update-crontablogファイルを見ると1分毎にメールが送られているのが確認できます。
下記の通りに1分毎に一行増えて表示されていたらOK
Userにめっちゃメールが送られているのが分かる。log/cron.logRunning via Spring preloader in process 8692 Running via Spring preloader in process 8714 Running via Spring preloader in process 8731 Running via Spring preloader in process 8748おまけ
そんな頻繁にメールが送られてきたらいやだから
crontab
の中身を消しましょう。
以下のコマンドで消せます。$ whenever --clear-crontabバッチ処理を再開したい時は以下のコマンドを入力。
またメールが送られるようになります。$ bundle exec whenever --update-crontab参考にさせて頂きました
https://freecamp.life/rails-whenever/
https://qiita.com/Esfahan/items/e7a924f7078faf3294f2
https://www.school.ctc-g.co.jp/columns/masuidrive/masuidrive22.html
- 投稿日:2020-04-09T22:06:35+09:00
【初心者でもできる】Rubyで簡単なガチャアプリを作ってみた
学習の段階で作成したメモアプリを応用して、よくあるソシャゲのガチャっぽいアプリをRubyで作ってみました。
あくまでもガチャが回せるだけのものなので、表記も使っているものもごくシンプルです。
初心者の方でも作成できるので、参考にしていただければ幸いです。仕様
・単発ガチャ、10連ガチャを回すことができる。
・レアリティはレア,アンコモン,コモンの3段階。
・レア=10%,アンコモン=30%,コモン=60%の確率で引ける。
・R以上とUC以下で引けた時のメッセージが変わる.
・繰り返しガチャが引ける使用したメソッドなど
以下のものです。
・配列
・gets
・puts
・sample
・times文
・if文(if,elsif,else)
・while文
・比較演算子(今回使ったのは○○以上、□□以下、AとBは等しい、の3種類)パーツ作り
では、必要なパーツを作っていきます。
まずはガチャの基礎となる「ガチャを引く」というアクションを確認します。ガチャ本体とガチャの中身とガチャを引くアクション
実際のソシャゲガチャだと「ガチャを引く!」ボタンをタップすれば、キャラクターやカードが手に入りますよね?
まずはそのガチャの箱と、中に入っているキャラクターを用意します。
(これは実際のガチャポンとカプセルで考えた方が想像しやすいかもしれませんね)ガチャを入れる箱を変数として用意し、その変数の中に入れる配列をガチャのキャラ、もしくは中身入りカプセルと考えます。
chara = [1,2,3,4,5,6,7,8,9,10]「chara」がガチャを入れる箱
[1,2,3,4,5,6,7,8,9,10]がキャラ、もしくは中身入りカプセルの配列ですね。
(100まで書くのは長いので10までとしています)次は、このガチャから中身を引く行為を作成します。
chara = [1,2,3,4,5,6,7,8,9,10] chara.sample「sample」メソッドで1〜10の数字をランダムにピックアップします。
このままでは結果が表示されないので「puts」にて何を引いたか?を表示しましょう。chara = [1,2,3,4,5,6,7,8,9,10] puts chara.sampleこの状態で実行すると、1〜10という名前のキャラ、もしくは番号付きカプセルを1個引くことができます。
中身は無限なので、どれも何個でも引けます。
これで「ガチャを入れる箱」「ガチャの中身」と「ガチャを引く」というアクションを用意出来ました。「ガチャの中身」の設定
次はガチャの中身を作ります。
ソシャゲのガチャであれば「SSR」とか「☆4」など、レアリティが設定されていますね?
細かいことを言えば更にキャラクター名や属性、クラスなどがありますが、今回はシンプルに作るのでレアリティのみを設定します。先ほどの変数と配列で「chara」という名前のガチャボックスに「1〜10」という名前のキャラを入れています。
シンプルに、1が引けたらレア、2か3か4が引けたらアンコモン、それ以外はコモンとします。まずはガチャを引くアクションを「ガチャを引いた結果」という変数に代入します。
chara = [1,2,3,4,5,6,7,8,9,10] gachakekka = chara.sample「ガチャを引いた結果」変数をif文の中に入れて「この数字のキャラを引いたらどのレアリティだった」というコードにします。
ここでは比較演算子も使用します。chara = [1,2,3,4,5,6,7,8,9,10] gachakekka = chara.sample if gachakekka == 1 puts "レアカードをゲットしました!" end※ a == b は「aはbと等しい」という意味となります。
このように、「ガチャを引いた結果」の変数である「gachakekka」を条件分岐出来るif文に組み込み「ガチャを引いた結果が何番のキャラだった」という内容を作り、その結果の文字列をputsにて表示します。
では、アンコモンとコモンも同じように作成します。if gachakekka == 1 puts "レアカードをゲットしました!" elsif gachakekka >= 2 && gachakekka <= 4 puts "アンコモンカードをゲットしました!" elsif gachakekka >= 5 && gachakekka <= 10 puts "コモンカードをゲットしました" end※ >= a && <= b は「a以上 かつ b以下」という意味となります。
これで、1~10の数字を引いた時にどんなレアリティのカードを引いたか?が分かるようになりました。
では、次に実行するメソッドを作成していきます。実行メソッド「gachagacha」を作る
これを実行できるようにメソッドとてまとめます。「gachagacha」というメソッド名にします。
def gachagacha chara = [1,2,3,4,5,6,7,8,9,10] gachakekka = chara.sample if gachakekka == 1 puts "レアカードをゲットしました!" elsif gachakekka >= 2 && gachakekka <= 4 puts "アンコモンカードをゲットしました!" elsif gachakekka >= 5 && gachakekka <= 10 puts "コモンカードをゲットしました" end endメソッドを作ったところで、実行してみましょう。
def gachagacha chara = [1,2,3,4,5,6,7,8,9,10] gachakekka = chara.sample if gachakekka == 1 puts "レアカードをゲットしました!" elsif gachakekka >= 2 && gachakekka <= 4 puts "アンコモンカードをゲットしました!" elsif gachakekka >= 5 && gachakekka <= 10 puts "コモンカードをゲットしました" end end gachagachaこれで
「レアカードをゲットしました!」
「アンコモンカードをゲットしました!」
「コモンカードをゲットしました」
の、どれかが表示されればOKです。もしエラーが出るのであれば「全角スペースが入ってないか」「スペルミスがないか」「変数の入れ忘れがないか」など、チェックしてみてください。
また、「puts 〜ゲットしました」の文章は一例なので、何のカードをゲットしたか分かるように好きなように書いてみて下さい!ガチャの基本的要素はこれで作成することが出来ました。
しかし、10連ガチャの機能がまだ作れていないので次はそれを実装します。10連ガチャ機能を作る
こちらはシンプルです。
単純に、先ほど作成した「gachagacha」メソッドを10回繰り返すだけです。10.times do gachagacha endtimesメソッドを使えば、作成したgachagachaメソッドを指定の回数分繰り返すことが出来ます。
ただしこのままだと単発ガチャか10連ガチャか、どちらを回すか選べません。
最初に単発か10連かをプレイヤーに選択させてあげる必要がありますね。
まずは単発と10連を選択できるコードを作ります。
識別する方法は「10」と打つか「1」と打つかでどちらかが出来るようにします。mode = gets.chomp if mode == "10" 10.times do gachagacha end elsif mode == "1" gachagacha endここでもif文を使って「10と打った場合は10連ガチャ」「1と打った場合は単発ガチャ」と、分けます。
「gets」メソッドにて打ち込んでいるので、「数値」ではなく「文字列」となっています。
なので、""(ダブルクォーテーション)で囲むのを忘れないようにして下さい。
ただ、このままでは打ち損じした場合に何が起きたかわかりにくいので、そうした時に「エラー」と出るようにします。while true mode = gets.chomp if mode == "10" 10.times do gachagacha end elsif mode == "1" gachagacha else puts "エラーです。もう一度打ち直してください。" end endgetsメソッドを使って「mode」という変数に「10」か「1」を打ち込んだ場合は10連か単発ガチャを回すモード。
間違えてそれ以外を打ち込んだ場合はエラーとなり、最初からやり直す形としました。
また、while trueを追記することにより、ctrl + cを押さない限り無限ループするようにしています。putsの後の文章は一例です。エラーと分かれば何でも良いので、ご自身でも文章を考えてみて下さい!
全体構成
では、最終形を作ります。
ガチャを回す導入文章も一緒に入れます。def gachagacha chara = [1,2,3,4,5,6,7,8,9,10] gachakekka = chara.sample if gachakekka == 1 puts "レアカードをゲットしました!" elsif gachakekka >= 2 && gachakekka <= 4 puts "アンコモンカードをゲットしました!" elsif gachakekka >= 5 && gachakekka <= 10 puts "コモンカードをゲットしました" end end puts "運試しガチャマシンへようこそ!" while true puts "10連ガチャを引きますか?単発ガチャを引きますか?" puts "10連であれば「10」単発であれば「1」と入力して下さい。" mode = gets.chomp if mode == "10" 10.times do gachagacha end elsif mode == "1" gachagacha else puts "エラーです。もう一度打ち直してください。" end endgachagachaメソッドを先に作り、下に実行するためのif文を組み立てます。
putsで「運試しガチャマシンへようこそ!」と記載し、ガチャアプリが始まることを示します。これは1回だけ表示されれば良いのでwhile分にはいれません。
10連ガチャか単発ガチャかは常に選択する必要があるので、while文の中に入れてループ表示させます。「1」「10」「それ以外」どの場合も正常に動作していますね。
これにて完成です!
最後に使ったメソッドのおさらいをしておきます。使ったメソッド
・gets→ターミナル上で直接文字列を打ち込む
・puts→文章表示
・sample→配列の中からランダムでピックする
・times文→指定したことを回数分繰り返す(5.times doなら5回)
・if文(if,elsif,else)→条件に合致した場合、その内容を実行する。
・while文→指定した範囲のコードを繰り返す。良ければ試してみて下さい!
ありがとうございました。
- 投稿日:2020-04-09T21:57:40+09:00
Kinx ライブラリ - Double
Double
はじめに
「見た目は JavaScript、頭脳(中身)は Ruby、(安定感は AC/DC)」 でお届けしているスクリプト言語 Kinx。言語はライブラリが命。ということでライブラリの使い方編。
今回は Double です、... と言いつつ、Double 組み込み特殊メソッドは少ないので、短い記事です。
- 参考
- 最初の動機 ... スクリプト言語 KINX(ご紹介)
- 個別記事へのリンクは全てここに集約してあります。
- リポジトリ ... https://github.com/Kray-G/kinx
- Pull Request 等お待ちしております。
Double 特殊オブジェクト
Double オブジェクトに対して関数定義する例は以下の通り。
Double.minus1 = function(value) { return value - 1; }; var val = 100.5.minus1(); System.println(val);実行してみよう。
99.5他の特殊オブジェクトと同様、レシーバーが第 1 引数に来ます。
Double
組み込み特殊メソッド
メソッド 意味 Double.toString(val, format) val
を文字列に変換する。format
は%
で始まり、a
、A
、e
、E
、f
、F
、g
、G
のいずれかで終わる文字列。省略時は%g
Double.toInt(val) val
を Integer に変換する。
format
は現在プレビュー版ではサポートされていません。正式版ではサポート予定です。というのも、これを書いていてあったほうが良いなー、と思ったということで。Math オブジェクト・メソッド
Double オブジェクトには Math オブジェクトと同じ特殊メソッドが存在する。詳細は以下を参照。
具体例で書くと、例えば以下のように書ける。
var a = 2.0.pow(10); // Math.pow(2.0, 10) と同じ => 1024 var b = (-10.8).abs(); // Math.abs(-10.8) と同じ => 10.8単項マイナス(
-
)は関数呼び出しより優先順位が低いため、カッコで括る必要があることに注意。おわりに
Integer 同様、ここ を見ながら今サポートしてないメソッドとかを順次サポートしていこうかなー。
ではまた次回。
- 投稿日:2020-04-09T21:37:55+09:00
Railsのscopeを読む
Railsのscopeのソースコードを読んでいきます。
u = User.new(name: 'sample name')scopeを読んでいくために、上記のようなオブジェクトをサンプルとして作成します。
1: class User < ApplicationRecord 2: 3: binding.pry => 4: scope :recent, -> { order(id: :desc).limit(5) } 5: end中を読んでいきます。
165: # We are able to call the methods like this: 166: # 167: # Article.published.featured.latest_article 168: # Article.featured.titles 169: def scope(name, body, &block) => 170: unless body.respond_to?(:call) 171: raise ArgumentError, "The scope body needs to be callable." 172: end 173: 174: if dangerous_class_method?(name) 175: raise ArgumentError, "You tried to define a scope named \"#{name}\" " \bodyがcallを呼べなかったら、例外を返す。
今はbodyがcallを呼べるので次へ。169: def scope(name, body, &block) 170: unless body.respond_to?(:call) 171: raise ArgumentError, "The scope body needs to be callable." 172: end 173: => 174: if dangerous_class_method?(name) 175: raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ 176: "on the model \"#{self.name}\", but Active Record already defined " \ 177: "a class method with the same name." 178: end 179:nameがクラスメソッドとして定義されていないか判定。
今は定義されていないので次へ。175: raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ 176: "on the model \"#{self.name}\", but Active Record already defined " \ 177: "a class method with the same name." 178: end 179: => 180: if method_defined_within?(name, Relation) 181: raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ 182: "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \ 183: "an instance method with the same name." 184: end 185:nameがインスタンスメソッドとして定義されていないか判定。
今は定義されていないので次へ。181: raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ 182: "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \ 183: "an instance method with the same name." 184: end 185: => 186: valid_scope_name?(name) 187: extension = Module.new(&block) if block 188: 189: if body.respond_to?(:to_proc) 190: singleton_class.define_method(name) do |*args| 191: scope = all._exec_scope(name, *args, &body)scopeの名前として有効かどうか判定する。nameがメソッドとして定義されていないので、次へ。
182: "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \ 183: "an instance method with the same name." 184: end 185: 186: valid_scope_name?(name) => 187: extension = Module.new(&block) if block 188: 189: if body.respond_to?(:to_proc) 190: singleton_class.define_method(name) do |*args| 191: scope = all._exec_scope(name, *args, &body) 192: scope = scope.extending(extension) if extensionblockが存在しているなら、blockを引数としたてModuleを新しく作る。今回はblockが存在しないので、次へ。
184: end 185: 186: valid_scope_name?(name) 187: extension = Module.new(&block) if block 188: => 189: if body.respond_to?(:to_proc) 190: singleton_class.define_method(name) do |*args| 191: scope = all._exec_scope(name, *args, &body) 192: scope = scope.extending(extension) if extension 193: scope 194: endbodyがto_procメソッドをを呼べるか判定。呼べるので、if分の中へ。
185: 186: valid_scope_name?(name) 187: extension = Module.new(&block) if block 188: 189: if body.respond_to?(:to_proc) => 190: singleton_class.define_method(name) do |*args| 191: scope = all._exec_scope(name, *args, &body) 192: scope = scope.extending(extension) if extension 193: scope 194: end 195: elsebodyで与えられた処理を実装する、nameというクラスメソッドを定義する。
198: scope = scope.extending(extension) if extension 199: scope 200: end 201: end 202: => 203: generate_relation_method(name) 204: end
- 投稿日:2020-04-09T21:22:39+09:00
可読性 のある書き方
モデル
人の年齢を表すカラムに「歳」を加えたり、重さを表すカラムに「キログラム」を加えるメソッドは、ビューでのみ使用される見た目を整えるロジックです。このような見た目に関わるロジックは、ヘルパーかデコレーターに記述します。
# ☓:ビューでしか使用しないメソッドをモデルに定義している # 例) Userモデルにcreated_atを年, 月, 日の形に変換するメソッドを定義している class User < ApplicationRecord def adjust_created_at created_at.to_date.strftime("%Y年%m月%d日") end end # ◯:ヘルパーファイルにビューで使用するロジックを定義している module ApplicationHelper def set_created_at(created_at) created_at.to_date.strftime("%Y年%m月%d日") end end # ◎:ビューで使用するロジックのうち、表示フォーマットを整えるものを、DraperもしくはActiveDecoratorを導入して実装している # 例) ActiveDecoratorを使用してcreated_atを年, 月, 日の形に変換するメソッドを定義している module UserDecorator def set_created_at created_at.to_date.strftime("%Y年%m月%d日") end end1つのメソッドで多くのことをやりすぎていないかの確認
例として、Userモデルに定義されたクラスメソッドchange_attributesが、tweet, commentの変更まで行なってしまっています。
1つのメソッドで行う処理はなるべく簡潔にまとめます。# X:Userモデルに定義された、change_attributesメソッドが、Tweet, Commentまで変更してしまっている class User < ApplicationRecord def self.change_attributes(user, tweet, comment) user.attributes = { family_name: "Hoge", first_name: "Fuga" } tweet.attributes = { body: Foo } comment.attributes = { body: Bar} end endロジックをスコープとしてモデルに定義します。
# ◯:コントローラでよく使用するロジックをスコープとしてモデルに定義している class User < ApplicationRecord # 〜省略〜 scope :team_red, -> { where(team: 0) } scope :captain, ->{ where(captain: true) } end # 上記のスコープにより、User.team_red.captainのように直感的にロジックを記述できるコントローラ
不必要な自作アクションの確認
基本的にはrailsの7つのアクションで実装します。
アクションの内部の記述が複雑になってしまう場合は、モデルやモジュールを活用して、別のファイルに処理を移します。# X:makeアクションはcreateとやっていることが変わらない def create @user.create(create_params) end def make @user.create(create_params) end # ◯:なるべく7つのアクションの範囲でルーティングを定義する def create @user.create(create_params) endN+1問題は起こっていないかを確認します。
has_many, belongs_toなどアソシエーションを設定しているモデルを扱う際には、includeなどの対策を忘れずに行います。# X:1対Nの関係で、includeをしていない # 1つのCompanyが複数のEmployeeをもっている例 # includeをしないとEmployeeの数だけCompanyをSELECTするSQLが発行されるため、レコードが増えるほど重くなる def index @employees = Employee.all end # ◯:変数を定義する際にincludeしている # includes(:employee)と記述することで、SQLの発行回数を抑えて動作を軽くできる def index @employees = Employee.all .includes(:employee) endビュー
部分テンプレートの活用
特定のビューを繰り返したい時は、eachより動作の軽い部分テンプレートを活用します。# X:レコードの数だけ同じビューを繰り返す部分で、eachを使用している h2 - @users.each do |user| .user-container = user.name = user.profile = user.image end # ◯:繰り返しを部分テンプレートで記述している # 部分テンプレートを使用する理由は動作が軽いから h2 = render partial: 'user-contaier', collection @usersルーティング
不要なルーティングを定義していないかの確認
不要なルーティングを定義してしまうと、万が一該当するパスにユーザーが遷移した時に500エラーが表示されてしまいます。また、意図せずupdate, destroyなどのメソッドが動いてしまう危険性もあるため、必要なルーティングのみを定義するようにします。# X : indexしか必要ないのに、resourcesで丸ごと定義している resources :items # ◯:only, exceptを使用して必要なルーティングのみ定義している resources: items, only: :index可読性
スペースの数、ネストの深さの確認
# X:スペースの数がバラバラになっている # fugafugaもhogehogeも同じクラスのインスタンスメソッドなのに、スペースの数が多い def hogehoge puts "こんにちは" end def fugafuga puts "Hello" end # ◯:スペースの数が揃っている def hogehoge puts "こんにちは" end def fugafuga puts "Hello" end命名規則
変数の名前は適切か
変数の名前は、一目見れば変数の中身が分かるような命にします。// X:アルファベット1文字 // ※Javascriptのローカル変数ではたまに1文字だけの変数名を定義するが、rubyではあまり使わない p = Person.first s = Ship.all // X:変数の中身が分からない名前 // 「一体どのモデルのeverythingなのか?」となってしまい、変数の目的が伝わらない @everything = Tweet.all // X:略語 // 略語は元の単語を想像しづらく、略し方も人それぞれ異なってしまうので使うべきでない // 変数の中身が単数なのか複数なのかも分かりづらい @au = User.admin // ◯: first_person = Person.first ships = Ship.all @tweets = Tweet.all @admin_users = User.adminキャメルケースとスネークケースを使い分けているか
# キャメルケースは先頭が小文字で、単語の区切りを大文字で表す # アッパーキャメルケースは先頭から大文字を使用する キャメルケース: adminUserCommentCreator アッパーキャメルケース: AdminUserCommentCreator # スネークケースは単語の区切りをアンダースコアで表す スネークケース: admin_user_comment_creator Railsの慣習的な命名規則として、下記のように使い分ける。 クラス名:アッパーキャメルケース メソッド名:スネークケース 変数名:スネークケース また、Javascriptでは一般的にキャメルケースを用いて記述する。ハイフンとアンダースコアを使い分けとBEMでクラス名を定義できているかの確認
ハイフンとアンダースコアを明確に使い分けて命名を行うと、可読性が大きく向上します。CSSも書きやすくなる、BEM記法を用いてクラス名を定義します。// X:ハイフンとアンダーバーを使い分けていない .review_box %ul.reviews-list %li.review_1 %li.review_2 %li.review_3_red %li.review_4 // ◯:ハイフンとアンダーバーを使い分け、BEMでクラス名を定義している // 親要素のクラス名からアンダースコアを2つ並べて、クラス名を定義している // 同じ要素のうち、差分を定義するクラス名をハイフンを使って定義している(-red) .reviews %ul.reviews__list %li.reviews__list__row %li.reviews__list__row %li.reviews__list__row.reviews__list__row-red %li.reviews__list__row設計
単一責任原則
単一責任原則とは、あるクラスを変更するべき理由は2つ以上あるべきでないというオブジェクト志向の原則の1つです。当該原則を満たしていないコードは、バグの原因となりやすく、将来の変更にも弱くなってしまいます。
参考サイト# ☓:Gearクラスが車輪に関係するリム、タイヤの情報を持っている # 自転車のギアを表すGearクラスが、「車輪のリムの直径、タイヤの厚み」まで責任を持ってしまっている # Wheelクラスを作り、rim, tireの定義を移すべき class Gear attr_reader :chainring, :cog, :rim, :tire def initialize(chainring, cog, rim, tire) @chainring = chainring @cog = cog @rim = rim @tire = tire end def ratio chainring / cog.to_f end def gear_inches ratio * (rim + (tire * 2)) end end # 下記のコードはギアに関する情報しか渡していないにも関わらずエラーになる puts Gear.new(52, 11).ratio # ArgumentError: wrong number of arguments (2 for 4) # ◯:Wheelクラスを作り、車輪に関する定義を移す class Gear attr_reader :chainring, :cog, :wheel def initialize(chainring, cog, wheel=nil) @chainring = chainring @cog = cog @wheel = wheel end def ratio chainring / cog.to_f end def gear_inches ratio * wheel.diameter end end class Wheel attr_reader :rim, :tire def initialize(rim, tire) @rim = rim @tire = tire end def diameter rim + (tire * 2) end end @wheel = Wheel.new(26, 1.5) puts @wheel.circumference # -> 91.106186954104 puts Gear.new(52, 11, @wheel).gear_inches # -> 137.090909090909単一責任原則に違反していないかチェックする方法
(1)クラスの持つメソッドを質問に置き換える「Gearさん、あなたのgear_inchesを教えてください」 # Gearにギアに関する質問をしているのでセーフ 「Gearさん、タイヤの厚みとリムの直径を教えてください」 # Gearと無関係なタイヤの質問をしているのでアウト # 該当する定義、メソッドを別クラスに切り出すべき(2)1つの文章でクラスを説明してみる
「Gearクラスはチェーンリングとコグ、その比率を持っています」 # ギアの役割を短い文章で表現できているのでセーフ 「Gearクラスはチェーンリングとコグ、その比率を持っており、更に関連した車輪のタイヤの厚みとリムの直径を持っていて、ギアインチを計算することがで計算することができます」 # 文章が長くなりすぎている # 上手く文章にできない場合は、1つのクラスで多くのことをやりすぎている可能性が高いマジックナンバーを使用していないかの確認
変数を定義する際、メソッドで数値をやり取りする際などに、生のまま扱われている数字をマジックナンバーといいます。
マジックナンバーを含むコードは、数字を見ただけでは「その数字が何を意味するのか」が分からないため、コメントアウトで数字の説明をする、定数を使う、といった工夫をして、なるべく数字に意味を持たせます。# X:インスタンスを作成する際にマジックナンバーを用いて値を代入している # コードを見ただけは「type: 1」がどういった商品タイプを示しているのか分からない Product.create {name: 'hogehoge', type: 1} # ◯:コメントアウトで数字の説明をしている # type: 1は日本産の商品 Product.create {name: 'hogehoge', type: 1} # ◯:定数を定義して値を代入している # コードを読むだけで「type: 1」が日本に関連した商品であることがわかる JAPANESE_PRODUCT_TYPE = 1.freeze Product.create {name: 'hogehoge', type: JAPANESE_PRODUCT_TYPE}変数にnilや0が入っても大丈夫な構造か
バリデーションをかけていない、null制約を定義していないなどの理由で、変数にnil、0が入ってしまうことがあります。このままnilの変数に対してメソッドの呼び出しを行うと、Undefined method <メソッド名> for for nil:NilClassというエラーが出てしまいます。nilの場合は呼び出しが行われないようにするなどの対策を取ります。# X:非ログイン時に表示されるビューで、current_user.idを指定している # current_userはログイン時のみ使用できるdeviseのヘルパーなので、非ログイン時に使おうとするとエラーになる = link_to "プロフィール", "users/profile/#{current_user.id}" # ◯:該当箇所がログイン時のみ読み込まれるように条件を設ける - if user_signed_in? = link_to "プロフィール", "users/profile/#{current_user.id}"
- 投稿日:2020-04-09T21:13:13+09:00
Ruby AtCoder向けVSCode設定
はじめに
普段は、Perl で AtCoder を楽しんでいます。
しかし、いつ何時 Perl がロックダウンされるかもしれませんのでそれに備え、Ruby の環境を整えたいと思います。Ruby to Perl
Ruby のロックダウンを心配されている方は、
Perl Git による入園式
PerlのAtCoder向けVSCode設定(tasks.json)
にて、簡単に環境構築できます。構築順
VSCode インストール
Visual Studio Code (Windows版) のインストールmsys2 インストール
msys2をWindowsにインストールしてPerlを使うRuby インストール
上記、msys2 インストール後に、pacman で Ruby をインストールします。>pacman -Syu mingw-w64-x86_64-ruby-native-package-installermsys/mingw32/mingw64 の3種類があると思われますが、Windowsが64bitの場合、mingw64 を選択します。
VSCode task.json 設定
Ctrl + Shift +B で、Ruby を実行できるように task.json を設定します
設定例task.json{ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { "label": "ruby", "type": "shell", "command": "C:\\msys64\\mingw64\\bin\\ruby.exe", "args": [ "${workspaceFolder}\\main.rb" ], "presentation": { "echo": true, "reveal": "always", "focus": true, "panel": "shared", "showReuseMessage": false, "clear": true }, "group": { "kind": "build", "isDefault": true } } ] }"command": "C:\msys64\mingw64\bin\ruby.exe"、ruby.exe のパスを設定します。
これで Ruby が実行可能になり AtCoder や もっとプログラム脳を鍛える数学パズル を楽しむことができます。
デバッグの構築
上記構成で、とりあえず Ruby が実行可能になりますが、動作確認でデバッグも実行可能になるよう構築を続けます。
gcc インストール
pacman で gcc をインストールします。
これを入れないと、後程の gem インストールに失敗します。>pacman -Syu mingw-w64-x86_64-gccbundler のインストール
>gem install bundlerwindows版の gem は Windows コマンドスクリプトです
Gemfile の編集
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } # gem "rails" gem "ruby-debug-ide" gem "debase"ruby-debug-ide と debase を追記します
bundle install の実行
>bundle installlaunch.json の設定
設定例launch.json{ // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "Debug Local File", "type": "Ruby", "request": "launch", "program": "${workspaceRoot}/main.rb", "stopOnEntry": true } ] }課題
デバッグ実行時に、標準入力の受け取りができない状態です。
出来次第、追記します。まとめ
- Windows10 の VSCode の Ruby でデバッグができるようになった
- これで Perl がロックダウンされても安心
- 後半走りすぎ
- RubyInstaller を使用していないけど、まあいいや
参照したサイト
VSCodeでRubyを気軽に実行する環境を作る。
Visual Studio Codeで「プロを目指す人のためのRuby入門」の例題を動かしてみた(動画付き)
VSCodeでRubyコードのデバッグができる環境を構築する(2017年2月現在)
PerlのAtCoder向けVSCode設定(tasks.json)
- 投稿日:2020-04-09T20:50:35+09:00
#4 Ruby?うん楽しいよ?【ちょっとマニアック】
こんにちは、あつぎです
プログラミング言語のRuby楽しいです。
javaやCから入った自分の感想なんですが「なんだこのシンプルでわかりやすい言語は…」と、絶望を通り越して感動するレベルでわっかりすいんですな。そこで、もっとやりたい…と思い、progateから卒業しつつ開発環境を作ろうと思います。
簡単に言うと、授業をただ受けてる状態から、自分で道具を使って実際に作ってみるみたいな感じです。■Rubyのオススメ開発環境は何?
初心者なので、まずどこで開発したらいいのかわかりませんね。
そこでGoogle先生に、詳しく教えてもらいました
もう大好きです、Google。さて、Rubyの開発環境でおススメなのは、3つらしいです。
IDE よさげな部分 悩むところ Eclipse (+RadRails) 学習コスト低め Javaやってた時と代わり映えしない RubyMine 情報量が豊富だからググればよさげ 有料か… ? VS Code 今人気。使ってるだけで最前線感バリバリ。 まだ情報が蓄積されていないみたい+バージョン依存のトラブルシュートが引っかかる
ざっくりで調べた感じなので、網羅できてないと思います。
「RailsでこのIDEいいよ!」って意見、お待ちしておりますので、ご自由にコメントどうぞ?♂️導入出来たら、記事書きます。
では、またの
- 投稿日:2020-04-09T20:44:39+09:00
rspecで実行している動作をbinding.pryを確認する方法【メモ】
$ pry(..)> valhave_content()メソッドでビューのテストもしている場合、テストでは画面が出てこないので、変数の中身がわかるだけではデバッグしようがない。
そんなときは今見ているURLを確認。なんらかの原因で違うURLを見てる場合もある。
$ pry(..)> current_urlURLは問題ないとなれば、実際にHTMLをチェック。
$ pry(..)> page.htmlか、もしくはテキストだけをチェックもできる。
$ pry(..)> page.text
- 投稿日:2020-04-09T20:37:17+09:00
Windows10でのRailsの導入がうまくいかず、WSLを用いた話
はじめに
Windows10でRailsの環境構築をしたところ、
rails server
コマンドを実行したときにページをうまく表示できなかったので、WSLを用いることにしたときの備忘録です。環境
- Windows10(64bit)
- Surface Pro 4
- Ruby 2.6.5
Windows10での環境構築(失敗)
結局解決せずに諦めたので、興味がない方は読み飛ばしてください。
まず gem 経由で Rails をインストールします。
$ gem install rails -v 5.0.7.2Rails プロジェクト用のディレクトリを作成し、そのディレクトリに移動してプロジェクト
new-app
を作成します。$ mkdir workspace $ cd workspace $ rails new new-app
new-app
ディレクトリに移動してrails server
を実行したところ、次のエラーが発生しました。cannot load such file -- sqlite3/sqlite3_native (LoadError)そこで、色々調べた結果、以下の方法を実行しました。
1. sqlite3.dllとsqlite3.exeをPathがとおったディレクトリに配置
SQLite Download Page
の「Precompiled Binaries for Windows」欄にある「sqlite-dll-win64-x64-3310100.zip」をダウンロード・解凍し、入っている「sqlite3.dll」をC:\Ruby26-x64\bin
に配置します。また、同じ欄にある「sqlite-tools-win32-x86-3310100.zip」をダウンロード・解凍し、入っている「sqlite3.exe」を
C:\Ruby26-x64\bin
に配置します(圧縮ファイル名の数字が変わっているときもあるので、適時読み替えてください)。2. sqlite3_native.soファイルを生成して配置
同様にSQLite Download Pageの「Source Code」欄にある「sqlite-amalgamation-3310100.zip」をダウンロード・解凍して
C:
下に配置し、以下のコマンドを実行してsqlite3_native.so
ファイルを生成します(ファイル名の数字やRubyのバージョンは適宜読み替えてください)。gem install sqlite3 --platform=ruby -- --with-sqlite3-include=C:/sqlite-amalgamation-3310100 --with-sqlite3-lib=C:\Ruby26-x64\binこれで
C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems\sqlite3-1.3.14\lib\sqlite3\sqlite3_native.so
にファイルが生成されます。次に、
C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems\sqlite3-1.3.13-x64-mingw32\lib\sqlite3
に新しくディレクトリ2.6
を作成します。このディレクトリの名前はRubyのバージョンに対応させてください。そしてこの中に先ほど生成したsqlite3_native.so
をコピーします。上記の作業の後にもう一度
rails server
コマンドを実行します。すると今度は以下のように表示され、ローカルサーバーが立ち上がりました。=> Booting Puma => Rails 5.0.7.2 application starting in development on http://localhost:3000 => Run `rails server -h` for more startup options *** SIGUSR2 not implemented, signal based restart unavailable! *** SIGUSR1 not implemented, signal based restart unavailable! *** SIGHUP not implemented, signal based logs reopening unavailable! Puma starting in single mode... * Version 3.12.4 (ruby 2.6.5-p114), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://localhost:3000 Use Ctrl-C to stopところが、http://localhost:3000/ をブラウザで開くと、以下のエラーページが表示されました。
そこで、Gemfile中の
gem 'sqlite3'の部分を
gem 'sqlite3', '1.3.13'に書き換え、
bundle install
を実行した後に再度試したところ、以下のエラーページに変わりました。
これを解決しようとしたのですが、 Windows10 で結局 Rails の環境構築に失敗したという記事が多数あり、解決できない可能性が高いと思ったのでWSL( Windows Subsystem for Linux ) を用いてみることにしました。
WSLを用いたRailsの環境構築
WSLの導入
まずWSLをインストールします。
Windowsの検索ボックスから「Windowsの機能の有効化または無効化」のウィンドウを出し、「Windows Subsystem for Linux」のチェックボックスにチェックを入れます。インストールが始まるので、それが終わったら再起動します。再起動後、Microsoft StoreでUbuntuを検索し、「入手」ボタンを押してインストールします。その後「起動」ボタンを押すと、そこそこ時間がかかったあとにユーザー名とパスワードの設定を求められます。これでWSLの導入が終わりました。次にいよいよ Rails の環境構築を行います。Railsの環境構築
1. Rubyのインストール
Rubyのインストールに必要な各種ライブラリを入れておきます。
$ sudo apt-get update $ sudo apt-get install make $ sudo apt-get install gcc $ sudo apt-get install -y libssl-dev libreadline-dev zlib1g-devRubyのバージョン管理を楽にしてくれる rbenv を導入してPATHをとおし、初期設定を行います。
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc $ ~/.rbenv/bin/rbenv init # Load rbenv automatically by appending # the following to ~/.bashrc: eval "$(rbenv init -)" $ echo 'eval "$(rbenv init -)"' >> ~/.bashrcNode.jsも導入しておきます。ここでもバージョン管理用ツールである n を導入します。
$ sudo apt-get install npm $ sudo npm install -g n $ sudo n stableRuby を導入します。
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build $ rbenv install 2.6.5 $ rbenv rehash $ rbenv global 2.6.5Rails を導入します。
$ gem install rails -v 5.0.3Railsを用いるのに必要なSQlite3とbundlerを導入します。
$ sudo apt-get install libsqlite3-dev $ gem install sqlite3 -v 1.3.13 $ gem install bundler以上で準備が整いました。ここからは、失敗したときと同じようにプロジェクトを作成し、サーバーを立てることを試みます。
$ mkdir workspace $ cd workspace $ rails new new-app $ cd new-app $ bundle install $ rails serverすると先ほどと同じエラーページ
が表示されるので、同様にGemfile中のgem 'sqlite3'の部分を
gem 'sqlite3', '1.3.13'
- 投稿日:2020-04-09T20:30:40+09:00
素人がWebサービスを作る備忘録(実装編)①
はじめに
今回は前準備編、設計編の続きです。そちらに筆者のスキルレベルやこの記事の目的などが書いてありますので、先にお読みください。
実装にあたって
ここから実装に入っていくわけですが、稚拙な設計となっている為、かなりの回数の手戻り作業が発生することが見込まれます。その辺りもうまくいかなかった理由と改善策をまとめつつまとめていきたいと思います。
そして、実装にあたり個人的にまとめておきたいと思った昨日の追加などは別途記事にしてまとめていきたいと思います。(その方があとで見返すのとかも楽だし、、、)そして差し当たり、
・ユーザー管理機能
・投稿機能
・投稿一覧、投稿詳細機能
・画像ファイルアップロード機能
・ページネーション機能or無限スクロール機能
・DBテーブルのリレーション管理
・単体、統合テストこの辺のrailstutorialで行った内容を実装し、Webサービスの全体像を作りたいと思います。
基本的に開発環境やデプロイはrailstutorialをなぞる感じで行っていきます。
Railsをインストール
まずはじめに
gem install rails -v 5.1.6
というコマンドで使用する。railsのバージョンを指定してインストールします。アプリケーションを立ち上げよう
さて、railsのインストールが終わったらやることはアプリの骨組みを作ることです。
ここではrails new
コマンドを使用して骨組みを作成します。
しかし、ここで
No value provided for required arguments 'app_path'
というエラーメッセージが出てきました。これは要は「rails newしかしていません」という意味です。
rails newコマンドは出力先を指定してやらないと実行することはできません。なのでここでは
rails new app
などとしてやるのが正しいやり方です。
(このミスをしたということで筆者のレベル感を分かって頂きたい。。。)Bundler
そしたらここでbundlerを実行して必要なgemをインストールしていきましょう。
「???」
初めて見たときは私はこうなりました。分解して見ていきましょう。
gemというのは誰かが作ってくれた、コンピューターの機能(例えばEnter押すだけで勝手にテストしてくれたり)です。
そして、これをインストールしたりアップデートしたりするgemがbundlerという機能(以下ライブラリと言います。)です。
面白いのはこのbundlerはgemfileとかgemfile.lockなどのgemを使用し他のgemを管理してくれる。ライブラリなのですが自身もgemというところです。はい!ではgemfileをインストールしていきましょう。gemfileにインストールしたいgemとバージョンを書いて(今回はrails tutorialと同じgemで基本行っていきます。)
bundle install
を実行します。
ここで「bundle updateをしてください」というエラーメッセージが出た場合はbundle update
した後にもう一度bundle install
しましょう。Gitで管理
rails tutorialに倣いGitを使用してバージョン管理を行なっていきたいと思います。
Gitって何や?ってことは記事は書こうと思っています。
とりあえず
ルートディレクトリに戻ってgit init
をします。これでローカルリポジトリが作成されました。
そしてリポジトリに追加したいファイルをgit add -A
でインデックスに追加します。(追加されたか確かめたいときはgit status
で確認できます。)
そして、git commit
でインデックスに追加しておいたファイルをリモートリポジトリに追加します。この時にgit commit -m "コメント"
とすることでlogにコメントを残しておくことができます。Github
rails tutorialではソースコードの完全なバックアップと他の開発者との共同作業のためにはbitbucketを使用していましたが、新しい取り組みとしてGithubを使用して行なっていきたいと思います。
まず、Githubで登録します。そしたら公開鍵と秘密鍵を作成します。
ターミナル$ cd #ホームディレクトリに移動 $ cd .ssh #sshを作るディレクトリに移動 $ ssh-keygen #鍵を作成 Generating public/private rsa key pair. Enter file in which to save the key (/home/ec2-user/.ssh/id_rsa): ここにid_rsaと入力 Enter passphrase (empty for no passphrase): パスフレーズを入力 Enter same passphrase again: 確認の為もう一度入力 Your identification has been saved in id_rsa. Your public key has been saved in id_rsa.pub. . . . #変な絵が出る . . . /.ssh $ ls #ここで秘密鍵と公開鍵が生成されていればおっけい作った後はGithubに公開鍵をコピーしましょう。
ターミナル$ cd ~/.ssh ec2-user:~/.ssh $ ls authorized_keys id_rsa id_rsa.pub $ cat id_rsa.pub #ここに出てくる奴をコピーしてGithubに登録これで公開鍵と秘密鍵の作成とGithubへの登録ができました。
次に新規リポジトリをを作成しました。
そしたらターミナル$ git add -A $ git commit -m "new my_app" $ git remote add origin リモートリポジトリのアドレス $ git push origin masterこれでおっけい
コマンド確認
・git remote add origin ~
リモートリポジトリに反映させる前にリモートリポジトリの情報を追加しておいて、どのリモートリポジトリに反映させるのか明らかにさせておく。
・git remote rm origin ~
上記のコマンドの逆でリモートリポジトリの情報を削除する場合に使う。
・git push origin master
このコマンドでローカルリポジトリに貯めておいた変更履歴をリモートリポジトリにpushする。Heroku
デプロイはHerokuで行います。頻繁に本番環境にデプロイすることによって早い段階でサービスの問題点が発見できます。
Gemfilegroup :development, :test do gem 'sqlite3', '1.3.13' . . end . . . group :production do gem 'pg', '0.20.0' endHerokuはSQLiteをサポートしていないためgemを上記のようにいじっておいてローカルではSQLite、本番環境ではpostgreSQLを使用できるようにしておく。
ターミナル$bundle install --without productionこのようにすることにより開発環境でのpg gem が作動しないようにしておく。
さてHerokuをインストールするにあたり以下のコマンドを実行しておく
ターミナル$ source <(curl -sL https://cdn.learnenough.com/heroku_install)意味は理解できませんがこれでインストールできます。
ターミナル$ heroku login # Herokuにログイン $ heroku create # Herokuに実行環境を構築 $ git push heroku master # Herokuにデプロイこれで作成したURLでブラウザを立ち上げて公開できていたら成功です。
参考
今さら聞けない!GitHubの使い方【超初心者向け】
git remoteを使ってリモートリポジトリの追加と削除を行う方法【初心者向け】
Ruby on Rails チュートリアル 第1章 MVCモデルからGitやHeroku 鍵生成までの流れを解説
- 投稿日:2020-04-09T20:09:50+09:00
Rails入門5: deviseのユーザー情報を利用しよう
Rails入門5: deviseのユーザー情報を利用しよう
#01:掲示板でユーザー情報を使おう
このレッスンでは、Scaffoldで作成した1行掲示板に、deviseを使ってユーザー認証機能を追加します。まずは、どのような機能を作るのか整理しましょう。このレッスンで作る掲示板
- deviseで作成したユーザー認証に、1行掲示板を組み合わせる
- ログインしている時だけ投稿できる
- 投稿者名をdeviseのユーザー名にする
- 自分の投稿だけ、編集・削除できる作成手順
- ログイン時だけ投稿できる掲示板を作る
- 1行掲示板の記事に、Emailを表示する
- Userモデルに、nameカラムを追加する
- Userモデルに、ユーザー名を保存する
- 投稿時にログインユーザー名を保存
- 自分の記事だけを編集・削除参考になるWebページ
- [ASCII.jp:ユーザー認証でなにができるのですか?|セキュリティの素朴な疑問を解く]
http://ascii.jp/elem/000/000/436/436614/
[よくわかる認証と認可 | Developers.IO]
https://dev.classmethod.jp/security/authentication-and-authorization/[【プログラマ英語】それ認証って意味じゃないですよ(厳密には) - Qiita]
https://qiita.com/tienlen/items/9e1b58dd173472f071c0[plataformatec/devise]
https://github.com/plataformatec/devise[Railsのログイン認証gemのDeviseのインストール方法 - Rails Webook]
http://ruby-rails.hatenadiary.com/entry/20140801/1406907000
#02:ログイン時だけ投稿できる掲示板を作ろう
ここでは、前回のレッスンで作成したユーザー認証機能を利用して、1行掲示板へのアクセスを制御します。誰でも記事を表示できて、登録したユーザーだけが新しい記事を投稿・編集できるようにしましょう。1行掲示板を作成する
$ cd bbs_users
$ rails g scaffold article user_id:integer content:string
$ rails db:migrate掲示板の初期データを投入する
db/seeds.rbArticle.create(user_id: 1, content: 'hello world')
Article.create(user_id: 1, content: 'hello paiza')
Article.create(user_id: 2, content: '世界の皆さん、こんにちは')データベースに反映するには、次のコマンドを実行する
$ rails db:seed
ログイン時に、特定のアクションだけ実行できるようにする
articles_controller.rbbefore_action :authenticate_user!, only: [:new, :create, :edit, :update, :destroy]
before_action :set_article, only: [:show, :edit, :update, :destroy]
演習課題「ユーザーログインに初期ユーザーを登録する・作成する」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。ここに、以下の初期投稿を登録してください。この時、seed.rbファイルで一括登録します。
- user_id: 1, content: '吾輩は猫である'
- user_id: 1, content: '名前はまだない'
- user_id: 2, content: 'どこで生まれたか'
- user_id: 3, content: 'とんと見当がつかぬ'
- user_id: 1, content: 'なんでも'
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
/home/ubuntu/myblog/db/seed.rbファイルに登録するデータを記述する。Talk.create(user_id: 1, content: '吾輩は猫である')
Talk.create(user_id: 1, content: '名前はまだない')
Talk.create(user_id: 2, content: 'どこで生まれたか')
Talk.create(user_id: 3, content: 'とんと見当がつかぬ')
Talk.create(user_id: 1, content: 'なんでも')
演習課題「ログインしているときだけ投稿・編集・削除」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成してあります。この環境で、ログインしているユーザーだけが編集・削除できるように、talk_controllers.rbファイルを設定してください。編集・削除に対応するのは、以下のアクションです。
- edit
- update
- destroy
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
/home/ubuntu/myblog/app/controllers/talks_controller.rbに次の内容を記述するclass TalksController < ApplicationController before_action :authenticate_user!, only: [:edit, :update, :destroy] before_action :set_talk, only: [:show, :edit, :update, :destroy] # GET /talks # GET /talks.json def index @talks = Talk.all end # GET /talks/1 # GET /talks/1.json def show end # GET /talks/new def new @talk = Talk.new end # GET /talks/1/edit def edit end # POST /talks # POST /talks.json def create @talk = Talk.new(talk_params) respond_to do |format| if @talk.save format.html { redirect_to @talk, notice: 'Talk was successfully created.' } format.json { render :show, status: :created, location: @talk } else format.html { render :new } format.json { render json: @talk.errors, status: :unprocessable_entity } end end end # PATCH/PUT /talks/1 # PATCH/PUT /talks/1.json def update respond_to do |format| if @talk.update(talk_params) format.html { redirect_to @talk, notice: 'Talk was successfully updated.' } format.json { render :show, status: :ok, location: @talk } else format.html { render :edit } format.json { render json: @talk.errors, status: :unprocessable_entity } end end end # DELETE /talks/1 # DELETE /talks/1.json def destroy @talk.destroy respond_to do |format| format.html { redirect_to talks_url, notice: 'Talk was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_talk @talk = Talk.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def talk_params params.require(:talk).permit(:user_id, :content) end end
#03:1行掲示板にEmailアドレスを表示しよう
ここでは、ユーザーの情報を掲示板に利用します。ここでは、投稿一覧にユーザーのメールアドレスを表示しましょう。ArticlesモデルとUserモデルを関連付ける
model/article.rbclass Article < ApplicationRecord
belongs_to :user
end投稿者のメールアドレスを表示する
iews/articles/index.html.erb
User Content Welcomeページから、掲示板にリンクする
index.html.erbWelcome#index
Find me in app/views/welcome/index.html.erb
<%= link_to "articles", articles_path %>
ナビゲーションを共通で表示する
app/views/layouts/application.html.erb
<% if user_signed_in? %>
Logged in as <%= current_user.email %>.
<%= link_to "Settings", edit_user_registration_path %> |
<%= link_to "Logout", destroy_user_session_path, method: :delete %>
<% else %>
<%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link' %> |
<%= link_to "Login", new_user_session_path, :class => 'navbar-link' %>
<% end %>
<%= notice %>
<%= alert %>
<%= yield %>
演習課題「掲示板とユーザーを関連付ける」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成してあります。掲示板の投稿一覧では、Userモデルのメールアドレスを表示するようになっていますが、エラーになってしまいます。データベースの関連付けを設定して、メールアドレスが表示されるようにしてください。
模範解答1
/home/ubuntu/myblog/app/models/talk.rbに次の内容を記述するclass Talk < ApplicationRecord
belongs_to :user
end
演習課題「投稿一覧にメールアドレスを表示する」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成し、talkと関連付けてあります。この掲示板の投稿一覧に、Userモデルのメールアドレスを表示するように設定してください。
模範解答1
/home/ubuntu/myblog/app/views/talks/index.html.erbに次の内容を記述するTalks
User Content
<%= link_to 'New Talk', new_talk_path %>
#04:Userモデルにnameカラムを追加しよう
ここでは、記事を投稿したユーザーの名前を表示するため、deviseのUserモデルに名前のカラムを追加します。それに合わせて、ユーザーの登録フォームを変更しましょう。Userモデルにカラムを追加
$ rails g migration AddNameToUser name:string
$ rails db:migrateコンソールで確認
rails console
User.allサインアップ画面に「name」カラムを追加
app/views/devise/registrations/new.html.erb
ユーザー情報の変更画面に「name」カラムを追加
app/views/devise/registrations/edit.html.erb
参考になるWebページ
- [devise にusername カラムを追加し、usernameを登録できるようにする。 - Qiita]
https://qiita.com/yasuno0327/items/ff17ddb6a4167fc6b471
[初めてのdevise ② -- カラムを追加してみる -- ~ やってみようカスマイズ! ~ - Qiita]
https://qiita.com/uloruson/items/40154b4be19d1ac900f3[Railsのログイン認証gemのDeviseのカスタマイズ方法 - Rails Webook]
http://ruby-rails.hatenadiary.com/entry/20140804/1407168000
演習課題「Userモデルにnameカラムを追加する」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成して、talkと関連付けてあります。このUserモデルに、nameカラムを追加してください。
模範解答1
次のコマンドを順にターミナルで実行する
cd myblog
rails g migration AddNameToUser name:string
rails db:migrate
演習課題「サインアップ画面と変更画面に、nameカラムを追加する」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成し、talkと関連付けてあります。このUserモデルにnameカラムを追加したので、次のフォームに、ラベルとテキストフィールドを追加してください。
- edit.html.erb
- new.html.erb
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
/home/ubuntu/myblog/app/views/devise/registrations/edit.html.erbに次の内容を記述する<h2>Edit <%= resource_name.to_s.humanize %></h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> <%= devise_error_messages! %> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> <div>Currently waiting confirmation for: <%= resource.unconfirmed_email %></div> <% end %> <div class="field"> <%= f.label :password %> <i>(leave blank if you don't want to change it)</i><br /> <%= f.password_field :password, autocomplete: "off" %> <% if @minimum_password_length %> <br /> <em><%= @minimum_password_length %> characters minimum</em> <% end %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "off" %> </div> <div class="field"> <%= f.label :current_password %> <i>(we need your current password to confirm your changes)</i><br /> <%= f.password_field :current_password, autocomplete: "off" %> </div> <div class="actions"> <%= f.submit "Update" %> </div> <% end %> <h3>Cancel my account</h3> <p>Unhappy? <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %></p> <%= link_to "Back", :back %>
#05:Userモデルのユーザー名を保存しよう
ここでは、Userモデルにある名前のカラムをデータベースに保存できるようにします。コントローラで、nameカラムを保存する
app/controllers/application_controller.rbbefore_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
devise_parameter_sanitizer.permit(:account_update, keys: [:name])
end
演習課題「nameカラムを保存できるようにする」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成し、talkと関連付けてあります。このUserモデルに、nameカラムを追加したので、保存できるようにコードを修正してください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
/home/ubuntu/myblog/app/controllers/application_controller.rbに次の内容を記述するclass ApplicationController < ActionController::Base
protect_from_forgery with: :exceptionbefore_action :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
devise_parameter_sanitizer.permit(:account_update, keys: [:name])
end
end
#06:掲示板にユーザー名を表示しよう
ここでは、1行掲示板のナビゲーションと投稿一覧に、Userモデルのnameカラムを表示しましょう。ナビゲーションのログイン情報に、ユーザー名を表示
app/views/layouts/application.html.erb<% if user_signed_in? %>
Logged in as <%= current_user.name %>.
<%= link_to "Settings", edit_user_registration_path, :class => "navbar-link" %> |
<%= link_to "Logout", destroy_user_session_path, method: :delete, :class => "navbar-link" %>
<% else %>
<%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link' %> |
<%= link_to "Login", new_user_session_path, :class => 'navbar-link' %>
<% end %>投稿一覧に、nameカラムを表示する
views/articles/index.erb.html
Name Content 投稿の詳細画面に、nameカラムを表示する
views/articles/show.erb.htmlUser:
演習課題「投稿一覧と詳細画面に、Userモデルのnameカラムを表示する」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成し、talkと関連付けてあります。このUserモデルに、nameカラムを追加したので、投稿一覧と詳細画面で、user_idの代わりにUserモデルのnameカラムを表示してください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
/home/ubuntu/myblog/app/views/talks/index.html.erbに次の内容を記述するTalks
User Content
<%= link_to 'New Talk', new_talk_path %>
模範解答2
/home/ubuntu/myblog/app/views/talks/show.html.erbに次の内容を記述するUser:
Content:
<%= link_to 'Edit', edit_talk_path(@talk) %> |
<%= link_to 'Back', talks_path %>
#07:ログインユーザー名で投稿を保存しよう
ここでは、1行掲示板の投稿を、ログインしたユーザーの名前で保存できるようにします。すでに、ログインした時だけ投稿できるようになっているので、現在のログインユーザーで投稿できるようにしましょう。新規投稿フォームを修正して、user_idを削除する
app/views/articles/_form.html.erb<%= form_with(model: article, local: true) do |form| %>
<% if article.errors.any? %>
<%= pluralize(article.errors.count, "error") %> prohibited this article from being saved:
<ul> <% article.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div><% end %>
<%= form.label :content %>
<%= form.text_field :content, id: :article_content %>
<%= form.submit %>
<% end %>createメソッドを修正する
app/controllers/articles_controller.rbPOST /articles
POST /articles.json
def create
@article = Article.new(article_params)
@article.user_id = current_user.id
演習課題「投稿一覧と詳細画面に、Userモデルのnameカラムを表示する」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成し、talkと関連付けてあります。talks_controllerを修正し、ユーザーがログインしている時だけ、
新規投稿のユーザー名をログインユーザー名にしてください。なお、ログインしているかどうかは「user_signed_in?」で判別できます。採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
/home/ubuntu/myblog/app/controllers/talks_controller.rbに次の内容を記述するclass TalksController < ApplicationController before_action :authenticate_user!, only: [:edit, :update, :destroy] before_action :set_talk, only: [:show, :edit, :update, :destroy] # GET /talks # GET /talks.json def index @talks = Talk.all end # GET /talks/1 # GET /talks/1.json def show end # GET /talks/new def new @talk = Talk.new end # GET /talks/1/edit def edit end # POST /talks # POST /talks.json def create @talk = Talk.new(talk_params) if user_signed_in? @talk.user_id = current_user.id end respond_to do |format| if @talk.save format.html { redirect_to @talk, notice: 'Talk was successfully created.' } format.json { render :show, status: :created, location: @talk } else format.html { render :new } format.json { render json: @talk.errors, status: :unprocessable_entity } end end end # PATCH/PUT /talks/1 # PATCH/PUT /talks/1.json def update respond_to do |format| if @talk.update(talk_params) format.html { redirect_to @talk, notice: 'Talk was successfully updated.' } format.json { render :show, status: :ok, location: @talk } else format.html { render :edit } format.json { render json: @talk.errors, status: :unprocessable_entity } end end end # DELETE /talks/1 # DELETE /talks/1.json def destroy @talk.destroy respond_to do |format| format.html { redirect_to talks_url, notice: 'Talk was successfully destroyed.' } format.json { head :no_content } end end private # Use callbacks to share common setup or constraints between actions. def set_talk @talk = Talk.find(params[:id]) end # Never trust parameters from the scary internet, only allow the white list through. def talk_params params.require(:talk).permit(:user_id, :content) end end
#08:自分の記事だけ編集・削除 その1
ここでは、投稿したユーザーだけが自分の記事を編集・削除できるようにしましょう。そのために、contollerで、user_idが一致した時だけ、アクションを実行させます。updateアクションを修正する
```app/controllers/articles_controller.rbdef update
if @article.user_id == current_user.id
respond_to do |format|
if @article.update(article_params)
format.html { redirect_to @article, notice: 'Article was successfully updated.' }
format.json { render :show, status: :ok, location: @article }
else
format.html { render :edit }
format.json { render json: @article.errors, status: :unprocessable_entity }
end
end
else
redirect_to @article, notice: "You don't have permission."
end
end
```destroyアクションを修正する
```app/controllers/articles_controller.rbdef destroy
if @article.user_id == current_user.id
@article.destroy
msg = "Article was successfully destroyed."
else
msg = "You don't have permission."
end
respond_to do |format|
format.html { redirect_to articles_url, notice: msg }
format.json { head :no_content }
end
end
```
#09:自分の記事だけ編集・削除 その2
ここでは先ほどの続きとして、使わないアクションを呼び出すリンクを非表示にします。updateアクションを修正する
```app/controllers/articles_controller.rbdef update
if @article.user_id == current_user.id
respond_to do |format|
if @article.update(article_params)
format.html { redirect_to @article, notice: 'Article was successfully updated.' }
format.json { render :show, status: :ok, location: @article }
else
format.html { render :edit }
format.json { render json: @article.errors, status: :unprocessable_entity }
end
end
else
redirect_to @article, notice: "You don't have permission."
end
end
```destroyアクションを修正する
```app/controllers/articles_controller.rbdef destroy
if @article.user_id == current_user.id
@article.destroy
msg = "Article was successfully destroyed."
else
msg = "You don't have permission."
end
respond_to do |format|
format.html { redirect_to articles_url, notice: msg }
format.json { head :no_content }
end
end
```投稿一覧を修正する
```app/views/articles/index.html.erb<% if user_signed_in? && article.user_id == current_user.id %>
<%= link_to 'Edit', edit_article_path(article) %>
<%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %>
<% end %>
```詳細画面を修正する
app/views/articles/show.html.erb<% if user_signed_in? && @article.user_id == current_user.id %> <%= link_to 'Edit', edit_article_path(@article) %> | <% end %> <%= link_to 'Back', articles_path %>
演習課題「ユーザーがログインしている時だけ、編集・削除を可能にする」
右の環境には、「myblog」プロジェクトに「talk」という1行掲示板が作成されています(user_idとcontentというカラムを持っています)。また、ユーザー認証構築用にdeviseを導入して、「User」というモデルを作成し、talkと関連付けてあります。この掲示板で、ユーザーがログインしている時だけ、投稿一覧と詳細画面に、編集と削除のリンクを表示してください。なお、ログインしているかどうかは「user_signed_in?」で判別できます。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
/home/ubuntu/myblog/app/views/talks/index.html.erbに次の内容を記述する<p id="notice"><%= notice %></p> <h1>Talks</h1> <table> <thead> <tr> <th>User</th> <th>Content</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @talks.each do |talk| %> <tr> <td><%= talk.user.name %></td> <td><%= talk.content %></td> <td><%= link_to 'Show', talk %></td> <% if user_signed_in? && talk.user_id == current_user.id %> <td><%= link_to 'Edit', edit_talk_path(talk) %></td> <td><%= link_to 'Destroy', talk, method: :delete, data: { confirm: 'Are you sure?' } %></td> <% end %> </tr> <% end %> </tbody> </table> <br> <%= link_to 'New Talk', new_talk_path %>模範解答2
/home/ubuntu/myblog/app/views/talks/show.html.erbに次の内容を記述する<p id="notice"><%= notice %></p> <p> <strong>User:</strong> <%= @talk.user.name %> </p> <p> <strong>Content:</strong> <%= @talk.content %> </p> <% if user_signed_in? && @talk.user_id == current_user.id %> <%= link_to 'Edit', edit_talk_path(@talk) %> <% end %> <%= link_to 'Back', talks_path %>
お疲れ様でした!!!!
- 投稿日:2020-04-09T20:09:08+09:00
Rails入門4: deviseでユーザー認証してみよう
Rails入門4: deviseでユーザー認証してみよう
#01:ユーザー認証機能を理解しよう
このレッスンでは、Railsのユーザー認証用ライブラリdeviseを使って、Webアプリケーションで必要になるユーザー認証の基本を学習します。
まず初めに、ユーザー認証の概要と、deviseの役割を理解しましょう。ユーザー認証とは
相手を確認してアクセスを許可する仕組みを「ユーザー認証」と呼びます。ユーザー認証
- 認証:Authentication 本人かどうか確認する
- 許可:Authorization 認証の結果をあたえ、利用許可deviseの主な機能
deviseは、ユーザー認証の仕組みをRailsに提供するライブラリです。
- サインアップ:ユーザー情報と暗号化したパスワードをデータベースに保存
- メールによるユーザー確認
- ログイン:メールとパスワードによる認証
- クッキーによるセッション管理
- ユーザー追跡:ログイン回数、日時、IPアドレスなど
- パスワードリセット
- ユーザーのロック
- OmniAuth対応:TwitterやFacebookなどによるSNS認証
参考になるWebページ
- [ASCII.jp:ユーザー認証でなにができるのですか?|セキュリティの素朴な疑問を解く]
http://ascii.jp/elem/000/000/436/436614/
[よくわかる認証と認可 | Developers.IO]
https://dev.classmethod.jp/security/authentication-and-authorization/[【プログラマ英語】それ認証って意味じゃないですよ(厳密には) - Qiita]
https://qiita.com/tienlen/items/9e1b58dd173472f071c0[plataformatec/devise]
https://github.com/plataformatec/devise[Railsのログイン認証gemのDeviseのインストール方法 - Rails Webook]
http://ruby-rails.hatenadiary.com/entry/20140801/1406907000
#02:ログインフォームの動作確認
ここでは、deviseで作ったログインフォームを実際に使ってみましょう。deviseでは、ユーザー認証のために多くの機能を提供します。ログイン情報
Email: kirishima@paiza.jp
パスワード: passworddeviseの主な機能
deviseは、ユーザー認証の仕組みをRailsに提供するライブラリです。
- サインアップ:ユーザー情報と暗号化したパスワードをデータベースに保存
- メールによるユーザー確認
- ログイン:メールとパスワードによる認証
- クッキーによるセッション管理
- ユーザー追跡:ログイン回数、日時、IPアドレスなど
- パスワードリセット
- ユーザーのロック
- OmniAuth対応:TwitterやFacebookなどによるSNS認証
#03:deviseを導入する
ここでは、Ruby on Railsに、deviseを導入します。そして、いくつかの基本設定で、deviseを使えるようにします。プロジェクトと静的ページの作成
$ rails new bbs_users
$ cd bbs_users
$ rails g controller welcome index動画0:50でエラーが出る場合
rails g controller welcome index でエラーが出る場合、bbs_users/db/Gemfileの12行目でバージョンを指定するgem 'sqlite3', '~> 1.3.6'
Railsを始めてsqlite3まわりのエラーで躓いている人たちへ
https://qiita.com/Kta-M/items/254a1ba141827a989cb7deviseライブラリを追加してインストールする
Gemfilegem 'devise'
$ bundle install
$ rails g devise:install
手動設定1.デフォルトURLを追加する
config/environments/development.rbconfig.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_mailer.raise_delivery_errors = false
config.action_mailer.perform_caching = false手動設定2.root_urlを指定する
config/routes.rbroot 'welcome#index'
手動設定3.フラッシュメッセージの表示場所を作る
app/views/layouts/application.html.erb
<%= notice %>
<%= alert %>
<%= yield %>
手動設定4.ユーザー認証用のviewを生成する
$ rails g devise:views
deviseのViewの対応
- ログイン: app/views/devise/sessions/new.html.erb
- サインアップ: app/views/devise/registrations/new.html.erb
- ユーザ情報変更: app/views/devise/registrations/edit.html.erb
- パスワード変更: app/views/devise/passwords/edit.html.erb
- メール認証: app/views/devise/confirmations/new.html.erb
- パスワードリセット: app/views/devise/passwords/new.html.erb- アカウントアンロック: app/views/devise/unlocks/new.html.erb
演習課題「deviseをインストールする」
右の環境には、「myblog」プロジェクトに「Welcome#index」という静的ページが作成してあります。
すでに、deviseのgemが導入してあります。ここで、deviseのインストールを実行してください。なお、手動設定は全て不要です。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
次のコマンドをターミナルで実行するrails g devise:install
#04:ユーザー認証用のUserモデルを作成
ここでは、ユーザー認証のためにUserモデルを作成します。そして、できあがったログイン画面を呼び出してみましょう。Userモデルを作成する
$ rails g devise User
$ rails db:migrateDeviseに初期ユーザを一括登録
db/seeds.rbUser.create(email: 'admin@paiza.jp', password: 'password')
User.create(email: 'kirishima@paiza.jp', password: 'password')
User.create(email: 'neko@paiza.jp', password: 'password')$ cd bbs_users
$ rails db:seedユーザー認証
サインアップ
https://xxxx.paiza-app:3000/users/sign_upログイン
https://xxxx.paiza-app:3000/users/sign_in
演習課題「管理者ログインを作成する」
右の環境には、「myblog」プロジェクトに「Welcome#index」という静的ページが作成してあります。また、ユーザー認証構築用にdeviseが導入済みです。ここに、deviseを使って「Admin」というモデルを作成してください。
また、マイグレーションも実行してください。採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
次のコマンドを順にターミナルで実行する
rails g devise Admin
rails db:migrate
#05:アクセスを制限する
ここでは、deviseで作成したユーザー認証機能にログアウト機能を追加します。また、ログインしている時だけ、Welcomeページを表示できるようにアクセス制御します。ログインナビゲーションを追加
app/views/welcome/index.html.erb<% if user_signed_in? %>
Logged in as <%= current_user.email %>.
<%= link_to "Settings", edit_user_registration_path %> |
<%= link_to "Logout", destroy_user_session_path, method: :delete %>
<% end %>この時、ヘルパーメソッドの「user」の部分は、モデル名の「User」に合わせて記述しています。
ログインページに強制移動
app/controllers/welcome_controller.rbclass WelcomeController < ApplicationController
before_action :authenticate_user!
def index
end
end参考になるWebページ
- [ログイン認証も簡単!Railsでのdeviseの使い方 | TechAcademyマガジン]
https://techacademy.jp/magazine/7336
- [STEP21:Rails5にdeviseでログイン機能を実装しよう! #Rails #Ruby | TickleCode]
http://ticklecode.com/devise/
- [Devise に初期ユーザを追加 - Ruby and Rails]
http://rubyandrails.hatenablog.com/entry/devise-user-create
- [RailsのDBの初期データ(rake db:seed用)をyamlで美しく管理する方法 - Qiita]
https://qiita.com/yukimura1227/items/ff04eb6a771ffe1ab0b8
- [【Rails入門】seedの使い方まとめ | 侍エンジニア塾ブログ | プログラミング入門者向け学習情報サイト]
https://www.sejuku.net/blog/28395
演習課題「管理者ログインに初期ユーザーを登録する作成する」
右の環境には、「myblog」プロジェクトに「Welcome#index」という静的ページが作成してあります。
また、ユーザー認証構築用にdeviseを導入して、「Admin」というモデルを作成してあります。ここに、以下の初期ユーザーを登録してください。この時、seed.rbファイルで一括登録します。
- email: 'admin@paiza.jp', password: 'password'
模範解答1
/home/ubuntu/myblog/db/seed.rbファイルに登録するデータを記述する。
Admin.create(email: 'admin@paiza.jp', password: 'password')模範解答2
次のコマンドを順にターミナルで実行する
rails db:seed
演習課題「間違い探し:管理者ログインのナビゲーションを作成する」
右の環境には、「myblog」プロジェクトに「Welcome#index」という静的ページが作成してあります。
また、ユーザー認証構築用にdeviseを導入して、「Admin」というモデルを作成してあります。ここに、全てのページにナビゲーションを表示するようコードを記述してありますが、エラーになってしまいます。
間違いを修正して、正常に表示されるようにしてください。
模範解答1
/home/ubuntu/myblog/app/views/layouts/application.html.erbに記述する。
ヘルパーメソッドは、すべてモデル名のAdminに合わせる。<!DOCTYPE html>
Myblog
<%= csrf_meta_tags %><%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %><% if admin_signed_in? %>
演習課題「ログインしていないと強制移動」
右の環境には、「myblog」プロジェクトに「Welcome#index」という静的ページが作成してあります。また、ユーザー認証構築用にdeviseを導入して、「Admin」というモデルを作成してあります。この時、ログインしていないとログインページに強制移動するよう、Welcome#indexページを設定してください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
/home/ubuntu/myblog/app/controllers/welcome_controller.rbに、
before_actionを記述する。
ヘルパーメソッドは、モデル名の「Admin」に合わせて記述する。class WelcomeController < ApplicationController
before_action :authenticate_admin!
def index
end
end
#06:セッションとパスワードを理解する
ここでは、ログイン状態を保持するセッションとユーザーを認証するパスワードについて学習します。deviseを使うと、セッション管理機能も簡単に組み込むことができます。セッションとは
ログインしてから、ログアウトするまでの一連のアクセスを「セッション」と呼びます。Webサイトへの基本的なアクセスでは、アプリケーション側でそれぞれのアクセスは独立しています。そのために、同じ人が同じサイトに複数回アクセスしても、それぞれのアクセスを区別できません。これでは、アクセスするたびにログインし直す必要があります。
そこで、セッションという仕組みが使われています。セッションは、ログインすると開始して、そのアクセスを区別する「セッション情報」を記録します。そして、ログアウトしたり、ブラウザを閉じて一定時間が経ったりすると終了します。この「セッション情報」おかげで、セッションが有効な間、Webアプリケーションに同じ人がアクセスしていると判断できるようになります。
セッションを確認する手順
Google Chromeの場合1.「設定」メニューを呼び出す
2.「詳細設定」-「コンテンツの設定」-「Coookies」-「すべてのクッキーとサイトデータ」
3.Webアプリケーションのドメイン名を検索する暗号化されたパスワードの確認手順
$ rails console
user = User.find(2)
user.email
user.encrypted_password参考になるWebページ
- [セッション (session)とは|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典]
http://wa3.i-3-i.info/word1791.html
[今さら聞けないセッションとCookie、ログイン・ログアウト(Rails編) - Qiita]
https://qiita.com/SpicyCoffee/items/de9de9a5427adf81817a[Rails4での基本的なセッションの使い方 - Rails Webook]
http://ruby-rails.hatenadiary.com/entry/20141130/1417334030[Rails セキュリティガイド | Rails ガイド]
https://railsguides.jp/security.html[第8章 ログイン、ログアウト | Rails チュートリアル]
https://railstutorial.jp/chapters/log_in_log_out?version=4.2#cha-log_in_log_out
演習課題「セッションidを削除する」
右の環境には、「myblog」プロジェクトに「Welcome#index」という静的ページが作成してあります。また、ユーザー認証構築用にdeviseを導入して、「Admin」というモデルを作成してあります。そして、1個のブラウザが立ち上がって、ログイン状態になっています。このアプリのセッションidを削除して、ブラウザをログアウト状態にしてください。
模範解答1
ブラウザの設定メニューからクッキーを削除する
ブラウザの設定ページで「_myblog_session」のクッキーを削除する。
- 投稿日:2020-04-09T20:07:57+09:00
Rails入門3: Railsサービスを機能アップしてみよう
Rails入門3: Railsサービスを機能アップしてみよう
#01:RailsにBootstrapを導入しよう
このレッスンでは、お勧めのお店を投稿できる「ランチマップ」アプリのデザインを整えて、スマートフォン対応と日本語表示を実現します。まず初めに、HTMLテンプレートのBootstrapを導入します。
RailsのWebページにアクセスするには
- paizaの場合: New Broweserタブに表示されたアドレスに「:3000」を追加する
例 https://xxx.paiza-app.cloud:3000
- 作成した掲示板にアクセスする:https://xxx.paiza-app.cloud:3000/categories
- 自分のPCでrailsを起動する場合: ブラウザで「http://localhost:3000」にアクセスする
※ xxxには、コンテナ名が入ります
RailsのWebサーバーを停止するには
「CTRL」キー(コントロールキー)を押しながら「C」のキーを押しますRailsにBootstrapを導入する
Gemfileを修正gem 'bootstrap-sass', '~> 3.3.6'
gem 'sass-rails', '~> 5.0'コマンドを実行
$ bundle installscssを修正
app/assets/stylesheets/にある「application.css」のファイル名をapp/assets/stylesheets/application.css.scssに変更するapp/assets/stylesheets/application.css.scssを修正
@import 'bootstrap-sprockets';
@import 'bootstrap';/* universal */
body {
padding: 60px 15px 0;
}jsファイルの修正
app/assets/javascripts/application.js//= require bootstrap-sprockets
コンテナを割り当て
app/views/layouts/application.html.erbを修正
<%= yield %>
演習課題「コンテナを追加する」
右の環境には、Railsで、おすすめのネコ情報を投稿する「catmap」というプロジェクトに、「cat」という掲示板と「feed」という掲示板が作られており、Bootstrapが導入してあります。
エディタを使って、app/views/layouts/application.html.erbを修正して、Bodyタグにcontainerクラスを追加してください。採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
class='container'を要素にしたdivタグを配置します。
<%= yield %>
#02:Bootstrapでレスポンシブデザインにしよう
ここでは、BootstrapでRailsアプリをレスポンシブデザインにします。
そして、テーブルやボタン・フォームにBootstrapのスタイルを適用します。お店リストのテーブルを修正
app/views/shops/index.html.erb・・・
リンクにボタンを追加
app/views/shops/index.html.erb
<%= link_to 'New Shop', new_shop_path, class: 'btn btn-primary' %> | <%= link_to 'Show category list', categories_path %>
フォームを修正1
app/views/shops/_form.html.erb<%= form_for(shop, html: {class: 'form-horizontal', role: 'form'}) do |f| %>
フォームを修正2
フォームの部品
app/views/shops/_form.html.erbフォームを修正3
登録ボタン
app/views/shops/_form.html.erbグリッドの横幅の比率
Bootstrapでは、HTML要素の"class"属性に"col-sm-X"を適用すると、ページの横幅を12としたときに、その要素が占める横幅をXとすることができます。
例えば、"col-sm-3"が適用された要素と"col-sm-9"が適用された要素を並べたならば、これらの横幅の比率は、3:9になります。このようなレイアウト方法をグリッドレイアウトと呼びます。
演習課題「テーブルへのクラスの適用」
右の環境には、Railsで、おすすめのネコ情報を投稿する「catmap」というプロジェクトに、「cat」という掲示板と「feed」という掲示板が作ってあり、Bootstrapが導入してあります。エディタを使って、app/views/cats/index.html.erbを修正して、猫リストのテーブルにBootstrapのデザインを割り当てて、1行おきに色を付け、マウスポインタを重ねたら色が変わるようにしてください。
割り当てるのは、次のクラスです。
- table
- table-striped
- table-hover
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
divタグに、class='table table-striped table-hover'を記述します。
...
演習課題「ボタンにクラスを適用する」
右の環境には、Railsで、おすすめのネコ情報を投稿する「catmap」というプロジェクトに、「cat」という掲示板と「breed」という掲示板が作られており、Bootstrapが導入されています。エディタを使って、app/views/cats/index.html.erbを修正して、
「New Cat」ボタンに、'btn btn-primary'スタイルを割り当ててください。
模範解答1
class='btn btn-primary'を要素にしたdivタグを配置します。
<%= link_to 'New Cat', new_cat_path, class: 'btn btn-primary' %>
演習課題「グリッドの幅を設定する」
右の環境には、Railsで、おすすめのネコ情報を投稿する「catmap」というプロジェクトに、「cat」という掲示板と「feed」という掲示板が作られてあり、Bootstrapが導入してあります。エディタを使って、app/views/cats/_form.html.erbを修正して、グリッドの割合を1:11にしてください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
最初は、2:10になっています。<%= form_for(cat, html: {class: 'form-horizontal', role: 'form'}) do |f| %>
<% if cat.errors.any? %>
<%= pluralize(cat.errors.count, "error") %> prohibited this cat from being saved:
<ul> <% cat.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div><% end %>
#03:スマートフォン向けの画面にしよう vol.1
ここでは、Railsアプリをスマートフォン向けの画面に設定します。そのために、ナビゲーションバーを設置し、お店リストの操作を整理します。ナビゲーションバーを追加する
app/views/layouts/application.html.erbナビゲーションバーにリンクを追加する
app/views/layouts/application.html.erb<%= link_to 'Lunch Map', root_path, class: 'navbar-brand' %>
app/views/layouts/application.html.erb
- <%= link_to 'Shop', shops_path %>
- <%= link_to 'Category', categories_path %>
リストの行クリックで、詳細ページを表示
<%= link_to shop.category.name, shop, class: 'widelink' %>
app/views/shops/index.html.erb
<%= link_to shop.name, shop, class: 'widelink' %>
<%= link_to shop.address, shop, class: 'widelink' %>app/assets/stylesheets/application.css.scss
/* table row for link */
a.widelink {
display: block;
width: 100%;
height: 100%;
text-decoration: none;
}一覧から各リストのボタンを削除
app/views/shops/index.html.erb
Category
Name
Address
演習課題「ナビゲーションバーのリンクの追加」
右の環境には、Railsで「catmap」というプロジェクトに、「cat」という掲示板と「feed」という掲示板が作られており、Bootstrapのナビゲーションバーが導入されています。エディタを使って、app/views/layouts/application.html.erbを修正して、ナビゲーションバーの「Cat」と「Feed」という項目に、各掲示板へのリンクを貼ってください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
link_to でリンクする
#04:スマートフォン向けの画面にしよう vol.2
このチャプターでは、引き続き、Railsアプリをスマートフォン向けの画面に変えたいと思います。今度は、詳細ページと登録ページのレイアウトを整理しましょう。地図を画面に収める
app/views/shops/show.html.erb<%= content_tag(:iframe,
'map',
src:'https://www.google.com/maps/embed/v1/place?key=AIzaSyCJBgcuCowQa5-V8owXaUCHhUNBN8bfMfU&q=' + @shop.address,
width: '100%',
height: 320,
frameborder: 0) %>詳細ページにボタンを設置
app/views/shops/show.html.erb<%= link_to 'Delete', @shop,
method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' %>
<%= link_to 'Edit', edit_shop_path(@shop), class: 'btn btn-primary' %>
<%= link_to 'Back', shops_path, class: 'btn btn-default' %>登録ページに戻るボタンを追加
app/views/shops/_form.html.erb新規と更新ページのボタンを削除
app/views/shops/edit.html.erbEditing Shop
<%= render 'form', shop: @shop %>
app/views/shops/new.html.erb
New Shop
<%= render 'form', shop: @shop %>
演習課題「Googleマップの横幅の調整」
右の環境には、Railsで「catmap」というプロジェクトに、「cat」という掲示板と「feed」という掲示板が作ってあり、Googleマップが導入されています。エディタを使って、app/views/cats/show.html.erbを修正して、ウィンドウの幅に合わせて、Googleマップの横幅が変わるようにください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
width: '100%'を指定する<%= content_tag(:iframe,
'map',
src:'https://www.google.com/maps/embed/v1/place?key=AIzaSyCJBgcuCowQa5-V8owXaUCHhUNBN8bfMfU&q=' + @cat.address,
width: '100%',
height: 320,
frameborder: 0) %>
#05:Railsの日本語化を試そう
ここでは、Railsアプリのボタンやメッセージを日本語で表示させます。Ruby on Railsは、このような多言語対応のため国際化機能を装備しています。URLのオプションに合わせて切り換えるようコントローラーを修正
app/controllers/application_controller.rbclass ApplicationController < ActionController::Base
before_action :set_locale
protect_from_forgery with: :exceptiondef set_locale
I18n.locale = params[:locale] || I18n.default_locale
enddef default_url_options(options = {})
{ locale: I18n.locale }.merge options
end
enden.ymlを確認
/config/locale/en.ymlen:
hello: "Hello world"ja.yml をコピーして修正
$ cd ~/lunchmap
$ cp -a config/locales/en.yml config/locales/ja.yml/config/locale/ja.yml
ja:
hello: "世界の皆さん、こんにちは"ウェルカムページに翻訳用キーワードを埋め込む
app/views/welcome/index.html.erbLunch Map
Tasty meal on your place!!
演習課題「「ハロー パイザ」と表示」
右の環境には、Railsで「catmap」というプロジェクトに、start/index.html.erbというページが設置されています。また、国際化機能として、URLに日本語ロケール(?locale=ja)を指定すると、日本語表記に切り替わるようになっています。ja.ymlには「paiza: "ハロー パイザ"」と登録してあります。エディタを使って、app/views/start/index.html.erbを修正して、pタグの位置に「ハロー パイザ」と表示してください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
pタグに、<%= t('paiza') %>を記述する
#06:Railsアプリを日本語表記に切り替えよう
ここでは、引き続き、Railsアプリのボタンやメッセージを日本語で表示させます。今回は、テーブルとフォームの表記を日本語にします。特に、Railsが自動的に割り当てた表記の変更方法を取り上げます。en.ymlに英単語を登録する
/config/locale/en.ymlen:
hello: 'Hello world'
shops: 'Shops'
category: 'Category'
name: 'Name'
address: 'Address'ja.ymlに日本語を登録する
/config/locale/ja.yamlja:
hello: '世界の皆さん、こんにちは'
shops: 'お店リスト'
category: 'カテゴリ'
name: '店名'
address: '住所'お店リストに翻訳用キーワードを埋め込む
app/views/shops/index.html.erb<%= t('category') %>
<%= t('name') %>
<%= t('address') %> フォーム用のため、ja.ymlにデータ名を追記する
/config/locale/ja.yamlja:
hello: '世界の皆さん、こんにちは'
shops: 'お店リスト'
category: 'カテゴリ'
name: '店名'
address: '住所'activerecord:
models:
shop: お店attributes: shop: category_id: カテゴリ name: 店名 address: 住所参考になるWebページ
- Railsで日本語化対応にする方法 - Qiita
http://qiita.com/kusu_tweet/items/b534c808ac1ee0382f05
Railsの多言語化対応 i18nのやり方を整理してみた!【国際化/英語化】 - 酒と泪とRubyとRailsと
http://morizyun.github.io/blog/i18n-english-rails-ruby-many-languages/rails-i18n/rails/locale/ja.yml
https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.ymlRails標準のi18n機能による日本語化まとめ | ユニコーンリサーチ
https://unicorn.limited/jp/item/373Rails国際化(I18n) API | Rails ガイド
https://railsguides.jp/i18n.html
- 投稿日:2020-04-09T20:06:20+09:00
Rails覚書2: 実用的なRailsサービスを作ろう
Rails入門2: 実用的なRailsサービスを作ろう
#01:プロジェクトとウェルカムページを作ろう
このレッスンでは、お勧めのお店を投稿できる「ランチマップ」アプリをRuby on Railsで作ってみましょう。まず初めに、プロジェクトとウェルカムページを作ります。今回使ったLinuxコマンド
現在のディレクトリ(フォルダ)の位置を確認する。
$ pwdファイルやディレクトリの情報を表示する。
$ lslunchmapディレクトリに移動する。
$ cd lunchmap今回使ったRailsコマンド
lunchmapプロジェクトを作成する。
$ rails new lunchmapRails用のWebサーバーを起動する
$ rails s -b 0.0.0.0ウェルカムページを生成する
rails generate controller welcome indexウェルカムページを修正する
app/views/welcome/index.html.erbLunch Map
Tasty meal on your place!
config/routes.rbに追加
config/routes.rbRails.application.routes.draw do
get 'welcome/index'root 'welcome#index'
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
endRailsのWebページにアクセスするには
- paizaの場合: New Broweserタブに表示されたアドレスに「:3000」を追加する
例 https://xxx.paiza-app.cloud:3000
- 作成した掲示板にアクセスする:https://xxx.paiza-app.cloud:3000/categories
- 自分のPCでrailsを起動する場合: ブラウザで「http://localhost:3000」にアクセスする
※ xxxには、コンテナ名が入ります
RailsのWebサーバーを停止するには
「CTRL」キー(コントロールキー)を押しながら「C」のキーを押します。
演習課題「Railsプロジェクトを作成する」
右のターミナルで、現在のディレクトリに、おすすめのネコ情報を投稿する「catmap」というRailsプロジェクトを作成してください。$ rails new catmap
演習課題「Railsサーバーを起動する」
右のターミナルでは、「catmap」というRailsプロジェクトが作成されています。catmapディレクトリに移動して、RailsのWebサーバーを起動してください。
$ cd ~/catmap
$ rails server -b 0.0.0.0
演習課題「ページを追加する」
右の環境には、Railsで「catmap」というプロジェクトが作られています。
ターミナルを使って、このプロジェクトに「https://xxx.paiza-app.cloud:3000/start/index」というWebページを作ってください。rails serverを起動した上で、採点して、すべてのジャッジに正解すれば、演習課題クリアです!
$ cd ~/catmap
$ rails generate controller start index
#02:カテゴリリストとお店リストを作ろう
ここでは、ランチマップのカテゴリリストとお店リストを作成します。そして、このお店リストに、カテゴリ名を表示するよう設定します。今回使ったLinuxコマンド
ファイルやディレクトリの情報を表示する。
$ lslunchmapディレクトリに移動する。
$ cd lunchmap今回使ったRailsコマンド
カテゴリリストを自動生成する$ rails generate scaffold category name:string
$ rails db:migrateお店リストを自動生成する
$ rails generate scaffold shop category_id:integer name:string address:string
$ rails db:migrateRailsのWebページにアクセスするには
- paizaの場合: New Broweserタブに表示されたアドレスに「:3000」を追加する
例 https://xxx.paiza-app.cloud:3000
- 作成したカテゴリリストにアクセスする:https://xxx.paiza-app.cloud:3000/categories
- 作成したお店リストにアクセスする:https://xxx.paiza-app.cloud:3000/shops
- 自分のPCでrailsを起動する場合: ブラウザで「http://localhost:3000」にアクセスする
※ xxxには、コンテナ名が入ります
RailsのWebサーバーを停止するには
「CTRL」キー(コントロールキー)を押しながら「C」のキーを押します。
演習課題1「掲示板を作成しよう」
右の環境には、Railsで「catmap」というプロジェクトが作られています。
ターミナルを使って、このプロジェクトに「feed」という掲示板を作ってください。
この掲示板には、nameという文字列カラムを1つだけ用意します。rails serverを起動した上で、採点して、すべてのジャッジに正解すれば、演習課題クリアです!
$ cd ~/catmap
$ rails generate scaffold feed name:string$ rails db:migrate
演習課題2「掲示板をさらに追加しよう」
右の環境には、Railsで「catmap」というプロジェクトが作られています。
ターミナルを使って、このプロジェクトに「cat」という掲示板を作ってください。
この掲示板には、以下の3つのカラムを用意します。
- feed_id : integer
- name : string
- address : string
rails serverを起動した上で、採点して、すべてのジャッジに正解すれば、演習課題クリアです!
$ cd ~/catmap
$ rails generate scaffold cat feed_id:integer name:string address:string$ rails db:migrate
#03:カテゴリリストとお店リストを関連付けよう
ここでは、ランチマップのカテゴリリストとお店リストを関連付けて、このお店リストに、カテゴリ名を表示するよう設定します。相互にリンクする
自動生成した掲示板の間で相互にリンクを貼るには、次のように記述します。app/views/shops/index.html.erb<%= link_to 'New Shop', new_shop_path %> <%= link_to 'Show Categories', categories_path %>app/views/categories/index.html.erb<%= link_to 'New Category', new_category_path %> <%= link_to 'Show Shops', shops_path %>登録時に、カテゴリを選択できるようにする
登録・修正フォームで、カテゴリを選択できるようにするには、次のように記述します。app/views/shops/_form.html.erb
カテゴリを表示
お店リストの一覧表示と詳細表示にカテゴリ名を表示するには、次のように記述します。app/view/shops/index.html.erb<td><%= shop.category.name %></td>app/view/shops/show.html.erb<%= @shop.category.name %>
演習課題「ネコの種別を選択できるようにしよう」
右の環境には、Railsで「catmap」というプロジェクトに、「cat」という掲示板と「feed」という掲示板が作られています。また、この2つの掲示板は、すでに関連付けてあります。エディタを使って、app/views/cats/_form.html.erbを修正して、catの登録/修正フォームで、catのfeedを選択できるようにしてください。
rails serverを起動した上で、採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
ラベルとセレクトボックスを記述する
```
<%= f.label :feed_id %>
<%= f.select :feed_id, Feed.all.map{|o| [o.name, o.id]} %>
```
#04:検索機能を追加しよう
ここでは、ランチマップのためにお店の名前の検索機能を作成します。そのために、検索フォームを追加します。Viewに検索フォームを追加
```/app/views/shops/index.html.erb<%= form_tag('/shops', method: 'get') do %>
<%= label_tag(:name_key, 'Search name:') %>
<%= text_field_tag(:name_key) %>
<%= submit_tag('Search') %> <%= link_to 'Clear', shops_path %>
<% end %>
```Controllerにindexメソッドを修正
/app/controllers/shops_controller.rbdef index
if params[:name_key]
@shops = Shop.where('name LIKE ?', "%#{params[:name_key]}%")
else
@shops = Shop.all
end
endウェルカムページからリンクする
app/views/welcome/index.html.erbLunch Map
Tasty meal on your place!!
演習課題「検索機能を作成しよう」
右の環境には、Railsで「catmap」というプロジェクトに、「cat」という掲示板と「feed」という掲示板が作られています。また、この2つの掲示板は、すでに関連付けてあります。エディタを使って、app/views/cats/index.html.erbを修正して、ネコ一覧(cats)で猫の名前を検索できるようにしてください。
検索するためのコードは、すでにapp/controllers/cats_controller.rbに記述してあります。
rails serverを起動した上で、採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
ラベルとテキストフィールド、登録ボタンを記述する<%= form_tag('/cats', method: 'get') do %>
<%= label_tag(:name_key, 'Search name:') %>
<%= text_field_tag(:name_key) %>
<%= submit_tag('Search') %> <%= link_to 'Clear', cats_path %>
<% end %>
#05:Googleマップを組み込む
ここでは、ランチマップに地図を追加します。そのために、このRailsアプリケーションにGoogleマップを組み込みます。APIとは
APIとは、Application Programming Interfaceの略で、プログラムから別のプログラムの機能を呼び出すために用意された命令や関数のこと。Google MAP API
- Google Maps API | Google Developers
https://developers.google.com/maps/
Google Maps JavaScript API スタートガイド | Google Developers
https://developers.google.com/maps/documentation/javascript/tutorialGoogle Maps Embed APIの使い方まとめ!カスタム地図を埋め込もう
https://syncer.jp/google-maps-embed-api-matomeGoogle Maps Embed API
https://developers.google.com/maps/documentation/embed/guideAPIキーの取得手順
今回は以下のpaizaラーニング用のAPIキーを使ってください。
AIzaSyCJBgcuCowQa5-V8owXaUCHhUNBN8bfMfU自分でウェブサービスを公開する場合には、以下の手順を参考に取得してください。
- Google Developers Consoleにアクセスする
Google Developers Console
https://console.developers.google.com/
- プロジェクトを作成を選択
- Google APIが表示されたら、Google Maps APIから「Google Maps Embed API」を選択
- 「有効にする」をクリック
- 「認証情報を作成」をクリックして、「必要な認証情報」ボタンをクリック
- 表示されたAPIキーを記録する
※特定のWebサービスだけから利用できるよう、「API利用制限」を設定することをお勧めします。
※この手順や利用範囲はGoogle側で変更される場合があります。ビューに、マップエリアを追加
app/views/shops/show.html.erb<%= content_tag(:iframe, 'map', src:'https://www.google.com/maps/embed/v1/place?key=AIzaSyCJBgcuCowQa5-V8owXaUCHhUNBN8bfMfU&q=' + @shop.address, width: 800, height: 400, frameborder: 0) %>
演習課題「地図を表示しよう」
右の環境には、Railsで「catmap」というプロジェクトに、「cat」という掲示板と「feed」という掲示板が作られています。また、この2つの掲示板は、すでに関連付けてあります。エディタを使って、app/views/cats/show.html.erbを修正して、ネコの居場所(address)をGoogleマップで表示できるようにしてください。
なお、Googleマップに必要なライブラリや設定は、すでに記述してあります。
rails serverを起動した上で、採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
content_tagで、Googleマップを表示するエリアを記述する
<%= content_tag(:iframe, 'map', src:'https://www.google.com/maps/embed/v1/place?key=AIzaSyCJBgcuCowQa5-V8owXaUCHhUNBN8bfMfU&q=' + @cat.address, width: 800, height: 400, frameborder: 0) %>
- 投稿日:2020-04-09T16:08:58+09:00
[Rails]テンプレートエンジン(haml.slim)の導入方法と書き方
パフォーマンス比較グラフ
erb(比較用)
~.html.erb<h1>Users</h1> <table> <thead> <tr> <th>Name</th> <th>Email</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @users.each do |user| %> <tr> <td><%= user.name %></td> <td><%= user.email %></td> <td><%= link_to 'Show', user %></td> <td><%= link_to 'Edit', edit_user_path(user) %></td> <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> </table> <br> <%= link_to 'New User', new_user_path %>haml
1.導入方法
Gemfilegem 'haml-rails' #hamlファイルを使えるようにするターミナル$ bundleターミナル$ rails haml:erb2hamlターミナル<!-- 下記の様に問われるので、既存のerbファイルを削除してもいいなら「y + Enter」、駄目なら 「n + Enter」 --> Would you like to delete the original .erb files? (This is not recommended unless you are under version control.) (y/n)
既存の
erb
ファイルをhaml
ファイルに変換2.書き方
~.html.haml%h1 Users %table %thead %tr %th Name %th Email %th %tbody - @users.each do |user| %tr %td = user.name %td = user.email %td = link_to 'Show', user %td = link_to 'Edit', edit_user_path(user) %td = link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %br = link_to 'New User', new_user_pathslim
1.導入方法
Gemfilegem 'slim-rails' #slimファイルを使えるようにする gem 'html2slim' #既存のerbファイルをslimファイルに変換出来る様にするターミナル$ bundleターミナル$ bundle exec erb2slim app/views app/views -d既存の
erb
ファイルをslim
ファイルに変換2.書き方
~.html.slimh1 Users table thead tr th Name th Email th tbody - @users.each do |user| tr td = user.name td = user.email td = link_to 'Show', user td = link_to 'Edit', edit_user_path(user) td = link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } br = link_to 'New User', new_user_path
- 投稿日:2020-04-09T15:40:33+09:00
Docker-composeを使ってRailsの環境構築をする
今回はDocker-composeを使ってRailsの環境構築をする方法をまとめました!
Dockerは既に導入しているということを前提とします。
今回は特に説明等を書かずに環境構築する方法だけをまとめています。1.ファイル構成
・Dockerfile
・docker-compose.yml
・Gemfile
・Gemfile.lockまた各ファイルの中身は
Dockerfile.FROM ruby:2.5.1 RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - && apt-get update && apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/* RUN mkdir /app_name ENV APP_ROOT /app_name WORKDIR $APP_ROOT ADD ./Gemfile $APP_ROOT/Gemfile ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock RUN bundle install ADD . $APP_ROOTdocker-compose.ymlversion: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root ports: - "3306:3306" web: environment: - SPROCKETS_CACHE=/cache build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/app_name ports: - "3000:3000" links: - dbGemfile.source 'https://rubygems.org' gem 'rails', '5.2.1'Gemfile.lockは空で大丈夫です
2.Railsのプロジェクトを作成する
docker-compose run web rails new . --force --database=mysql --skip-bundle
docker-composeコマンドを使用し、rails newを実行します。
3.DBの設定をする
作成したらconfigというファイルがあるのでその中のdatabese.ymlを変更していきます
config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: db4.Docker-composeを起動します
# 1.コンテナをビルドする $ docker-compose build # 2.コンテナの作成&起動 $ docker-compose up # 2.コンテナの作成&起動(バックグラウンドでの起動) $ docker-compose up -d #3.DBの作成をする $ docker-compose run web rails db:create #4.DBをmigrateする $ docker-compose run web rails db:migrate5.localhostに接続
- 投稿日:2020-04-09T15:31:01+09:00
Rails覚書1: Ruby on Railsを理解しよう
Rails覚書1: Ruby on Railsを理解しよう
Railsの覚書、詳しくはコチラを参照:https://paiza.jp/works/rails/primer/
Webアプリケーションフレームワークである、Ruby on Railsについて学習してみたいと思います。
Railsを使って、基本的な機能を持ったWeb掲示板とWebページを作ることで、本格的なWebアプリケーション開発の第1歩を体験しましょう。
#01:Ruby on Railsとは
Ruby on Railsとは
プログラミング言語Ruby上で動作するWebアプリケーションフレームワーク。
Webアプリケーションフレームワークとは、Webアプリを開発するために便利な部品やツールをひとまとめにしたものです。
実践的なWeb開発では、このようなフレームワークを使うことで、効率よくWebアプリケーションを作っていくことができます。Rails使用に必要な最低限の知識
- Ruby
- HTML/CSS
- PHP+MySQL
参考チュートリアル
Rails をはじめよう | Rails ガイド
https://railsguides.jp/getting_started.htmlRuby on Rails チュートリアル:実例を使って Rails を学ぼう
https://railstutorial.jp/
02:Railsを使う準備をしよう
今回使ったrailsコマンド
Railsのバージョンを確認する。
rails -v
bbsプロジェクトを作成する。
rails new bbs
Rails用のWebサーバーを起動する
rails server -b 0.0.0.0
RailsのWebページにアクセスするには
paizaの場合: New Broweserタブに表示されたアドレスに「:3000」を追加する
例 https://xxx.paiza-app.cloud:3000自分のPCでrailsを起動する場合: ブラウザで「http://localhost:3000」にアクセスする
RailsのWebサーバーを停止するには
「CTRL」キー(コントロールキー)を押しながら「C」のキーを押します。
03:3分で掲示板を作ってみよう
今回使ったrailsコマンド
Rails用のWebサーバーを起動する
rails server -b 0.0.0.0
掲示板を自動生成する
rails generate scaffold article content:string
rails db:migrate
04:Railsで、Webページを追加しよう
Welcomeページを作成する
$ rails generate controller welcome index
サーバーを起動する(ショートカット)
$ rails s -b 0.0.0.0
トップページに設定する
config/routes.rbに記述する
Rails.application.routes.draw do get 'welcome/index' resources :articles root 'welcome#index' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end
05:RailsのWebページを修正してみよう
ここでは、Railsコマンドで自動生成したWebページを修正します。
Webページの修正方法
RailsのWebページの表示は、「app/views」のerbファイルに記述します。
ウェルカムページのindex.html
app/views/welcome/index.html.erb<h1>Hello BBS</h1>Rubyのコードを埋め込む
app/views/welcome/index.html.erb <h1>Hello BBS</h1> <p><%= Date.today %></p>リンクを貼る
app/views/welcome/index.html.erb <h1>Hello BBS</h1> <p><%= Date.today %></p> <%= link_to 'Show list', articles_path %><%= ... %>
テンプレート中でRubyコードを実行します。対応するコントローラのインスタンスコンテキストで実行されるので、グローバル変数のほか、コントローラのインスタンス変数、インスタンス関数なども利用できます。
演習課題1:「h1タグの見出しの変更」
右の環境には、Railsで「myblog」というプロジェクトに「start#index」というWebページが作られています。
このWebページのh1タグの見出しを、「Welcome to my diary」に変更してください。rails serverを起動した上で、採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
~/myblog/app/views/start/index.html.erbを次のように編集する
Welcome to my diary
Find me in app/views/start/index.html.erb
演習課題2「現在の日付を表示」
右の環境には、Railsで「myblog」というプロジェクトに「start#index」というWebページが作られています。
このWebページに、現在の日付を追加してください。rails serverを起動した上で、採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答2
~/myblog/app/views/start/index.html.erbを次のように編集するWelcome to my diary
演習課題3「リンクの追加」
右の環境では、Railsで「myblog」というプロジェクトに、「diary」掲示板と「start/index」というWebページが作られています。
この「start/index」に、「diary」掲示板へのリンクを追加してください。「diary」掲示板へのリンクのパスは、diaries_pathで取得できます。
rails serverを起動した上で、プログラムを実行して、正しく出力されれば演習課題クリアです!
模範解答3
~/myblog/app/views/start/index.html.erbを次のように編集する
Welcome to my diary
<%= link_to 'Show list', diaries_path %>
06:Rails掲示板の構成を調べよう
ここでは、Ruby on Rails(ルビー オン レイルズ)で作成した掲示板の構成を調べます。
MVCアーキテクチャ
- Model:アプリで扱うデータを保持し、操作する。
- View:受け取ったデータを表示する。
- Controller:ユーザーからのリクエストを処理し、モデル・ビューを呼び出して結果を返す。参考:
- RailsにおけるMVC(モデル/ビュー/コントローラ) - Ruby on Rails入門
http://www.rubylife.jp/rails/ini/index7.html
- MVC、本当にわかってますか? - Qiita http://qiita.com/tshinsay/items/5b1724baf32b8b5113c2
07:Rails掲示板を改良しよう
ここでは、Ruby on Railsで作成した掲示板に投稿者名を保存する機能をを追加してみたいと思います。
カラムの追加方法
データベースのarticlesテーブルに、nameカラムを追加する$ rails generate migration AddNameToArticle name:string
$ rails db:migrateviewファイルを変更する
index.html.erb<table> <thead> <tr> <th>Content</th> <th>Name</th> <th colspan="3"></th> </tr> </thead> <tbody> <% @articles.each do |article| %> <tr> <td><%= article.content %></td> <td><%= article.name %></td> <td><%= link_to 'Show', article %></td> <td><%= link_to 'Edit', edit_article_path(article) %></td> <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %> </tbody> </table>show.html.erb<p> <strong>Name:</strong> <%= @article.name %> </p>_form.html.erb<div class="field"> <%= f.label :name %> <%= f.text_field :name %> </div>コントローラーを修正する
article_controller.rbdef article_params params.require(:article).permit(:content, :name) end
演習課題1「nameカラムの追加」
右の環境には、Railsで「myblog」というプロジェクトに、「diary」掲示板が作られています。
ターミナルを使って、ここに「name」カラムを追加してください。採点して、すべてのジャッジに正解すれば、演習課題クリアです!
範解答1
次のコマンドを順にターミナルで実行する$ cd ~/myblog
$ rails generate migration AddNameToDiary name:string
$ rails db:migrate
Rails入門1-2: RailsのDBの動作を理解しよう
01:Railsの設計方針を知ろう
このチャプターでは、Rails の設計方針について学習すると共に、データベースとの連携の概要を理解します。
MVCアーキテクチャ
- Model:アプリで扱うデータを保持し、操作する。
- View:受け取ったデータを表示する。
- Controller:ユーザーからのリクエストを処理して、モデル・ビューを呼び出し、結果を返す。
02:Rails コンソールで動作を確認しよう
ここでは、Rails の動作確認に欠かせない Rails コンソールの基本的な使い方を学習します。
Rails コンソールを使うと、Rails アプリの環境を有効にした状態で、Ruby コードをひとつずつ実行することができます。Railsコンソールの起動
cd bbs
rails console次のように、短縮形も利用できる。
rails c
railsコンソール終了
exitデータ数を表示する
Article.count
特定のデータを表示する
Article.find(1)
特定のデータを変数に代入して、カラムを取り出す
article = Article.find(1)
article.contentカラムを更新する
article = Article.find(1)
article.content = "hello world"
article.save
演習課題「Railsコンソールで、データベースの値を変更する」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
Railsコンソールを使って、掲示板の最初の記事(id = 1)のcontentカラムの値を次の文字列に変更してください。sunny day
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
次のコマンドをターミナルで実行する$ cd myblog
$ rails c
$ diary = Diary.find(1)
$ diary.content = 'sunny day'
$ diary.save
#03:ActiveRecord を理解しよう
ここでは、Rails のデータベースを操作する ActiveRecord について学習します。ActiveRecord は、データベースのテーブルを Ruby のオブジェクトに割り当てる機能です。ActiveRecordとは
ActiveRecordは、データベースのテーブルをRubyのオブジェクトに割り当てる機能です。ActiveRecordを使うことで、SQLを書かなくても、Rubyの作法でデータベースを操作することができるんですよ。すべてのデータを表示する
articles = Article.all
articles.each {|article| p article}
#04:データベースのマイグレーションを理解しよう
ここでは Rails のマイグレーションについて学習します。
マイグレーションは Rails で自動生成した設定内容をデータベースに反映させます。マイグレーションとは
マイグレーションとは、簡単に言うと、データベースのテーブルを作成・変更するための仕組みです。Railsは、マイグレーションファイルという設定ファイルを自動作成して、それをmigrationコマンドで、データベースに反映させます。
カラムを追加するマイグレーションファイルを生成する
rails generate migration AddCategoryToArticle category:string
生成されたマイグレーションファイルは、db/migrateにあります。
データベース設定を反映する
rails db:migrate
データベース設定を取り消す
rails db:rollback
演習課題1「マイグレーションでカラムを追加」
右の環境では、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
「diaries」テーブルに「degree」という文字列のカラムを追加してください。手順は次の通りです。
- カラムを追加するマイグレーションファイルを生成し、
- 生成したファイルの設定をデータベースに反映させてください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
次のコマンドをターミナルで実行する
$ cd myblog
$ rails generate migration AddDegreeToDiary degree:string$ rails db:migrate
演習課題2「ロールバックでカラムを削除」
右の環境では、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
「diaries」テーブルの「degree」というカラムを削除してください。ただし、「degree」というカラムを追加するマイグレーションファイル「作成日時_add_degree_to_diary.rb」がすでに存在します。
マイグレーションのロールバックによって、このファイルの設定を取り消すことで、「degree」カラムを削除してください。採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答2
次のコマンドをターミナルで実行する
$ cd myblog$ rails db:rollback
演習課題3「マイグレーションファイルのカラム名を変更する」
右の環境では、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
ここに、「degree」というカラムを追加するマイグレーションファイルが作られています。
カラム名を変更するため、このマイグレーションファイルでカラム名を下記のように変更してから、
マイグレーションを実行してください。
degree -> temperature
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答3
myblog/db/migrate/XXXX_add_degree_to_diary ファイルを修正するclass AddDegreeToDiary < ActiveRecord::Migration[5.0]
def change
add_column :diaries, :temperature, :string
end
end
#05:ビューにカラムを追加しよう
ここでは、モデルのデータをビューに表示する時、ActiveRecord のメソッドをどのように記述するか学習します。
そのために、モデルに追加したカラムを ビューに表示させてみましょう。Railsコンソールで、新しい記事を投稿する
article = Article.new
article.feeling = "(^o^)"
article.saveerbとは
erbは、embedded Rubyの略。erbを使うと、htmlファイルを表示するときに、埋め込んでおいたRubyのコードが実行される。erbの書き方
- <% %> : Rubyのコードを実行する
- <%= %> : Rubyのコードを実行し、その結果をhtmlの該当位置に出力する<% @articles.each do |article| %> <tr> <td><%= article.content %></td> <td><%= article.name %></td> <td><%= article.feeling %></td> <td><%= link_to 'Show', article %></td> <td><%= link_to 'Edit', edit_article_path(article) %></td> <td><%= link_to 'Destroy', article, method: :delete, data: { confirm: 'Are you sure?' } %></td> </tr> <% end %>
演習課題「Railsコンソールで、記事を投稿する」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
Railsコンソールを使って、この掲示板に、次の内容を持つ新しい記事を作成してください。It was fine weather today
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
次のコマンドをターミナルで実行する
$ cd myblog
$ rails c
$ diary = Diary.new
$ diary.content = 'It was fine weather today'
$ diary.save
演習課題「Viewにカラムを追加する」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
この一覧ページ(index.html.erb)に、次の順序で「temperature」カラムを追加してください。
「temperature」カラムは、すでにデータベースに追加してあります。content, temperature
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
index.html.erb に temperature を表示するよう修正する<p id="notice"><%= notice %></p> <h1>Diaries</h1> <table> <thead> <tr> <th>Content</th> <th colspan="3"></th> </tr> </thead>
Rails入門1-3: Railsのデータの流れを理解しよう
#01:Webアプリのデータの流れを理解しよう
このレッスンでは、Ruby on Rails のアプリが動作するとき、どのようにデータを処理していくか学習します。先ほどのレッスンから引き続き、簡単な 1 行掲示板を題材にして、Rails の動作をさらに理解しましょう。Webフォームのデータ送信方式
- GETメソッド
URLに含めるhttp://example.net?id=3&content=hello
- POSTメソッド リクエストメッセージに含める
article[name]=paiza
article[content]=hello+worldRailsサーバーのログを上下にスクロールするには
ターミナルタブでは、マウスのホイールで、表示内容を上下にスクロールされることができます。
Railsサーバーのログなどが、画面の上部に消えてしまった場合に役立ちます。なお、ホイールでは操作できない場合、下記の操作が有効です。
Mac:CTRLキー + Altキー + 上下矢印キー
Windows: CTRLキー + 上下矢印キー
#02:Railsのルーター機能を理解しよう
ここでは、Rails アプリへのアクセスを振り分けるルーター機能について学習します。Rails アプリでは、機能ごとに URL が割り当てられており、ルーターは、そのアクセスをどのコントローラーのどのメソッドに振り分けるか制御します。Routersの設定内容を確認する
rails routes
ルーターの振り分け先を設定する
routes.rbで設定します。config/routes.rb
Rails.application.routes.draw do
get 'welcome/index'resources :articles
root 'welcome#index'
end参考になるWebサイト
- Rails のルーティング | Rails ガイド
https://railsguides.jp/routing.html
ルーティング(routes) - - Railsドキュメント
http://railsdoc.com/routesRailsのルーティングを極める(前編)
https://techracho.bpsinc.jp/baba/2014_02_17/15665Railsのルーティングを極める (後編)
https://techracho.bpsinc.jp/baba/2014_03_03/15619
演習課題「インデックスページのルーター設定」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
このroutes.rbファイルを修正して、アドレスに何も付けずにアクセスした時、
1行掲示板の一覧ページを表示するようにしてください。採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
myblog/config/routes.rb ファイルを修正する
Rails.application.routes.draw do
resources :diaries
get 'start/index'
root 'diaries#index'# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end
#03:ビューのテンプレートをさらに理解しよう
ここでは、Rails アプリの表示内容を制御するビューの書き方について、さらに理解を深めます。そのために、詳細ページを例にとり、コントローラーとビューの連携について学習します。また、ビューを作る時に便利なヘルパーメソッドについても取り上げます。ヘルパーメソッドとは
ヘルパーメソッドは、ビューを作るときに利用できる専用コマンドです。
ヘルパーメソッドは、このように不等号とパーセント記号の中に記述します。<%= link_to 'Edit', edit_article_path(@article) %> |
<%= link_to 'Back', articles_path %>link_toヘルパーメソッドについて
htmlのタグに相当するヘルパーメソッドで、Railsアプリのページ間のリンクなどをシンプルに記述できる。<%= link_to 'text', path %>
上記のlink_toヘルパーメソッドは、次のhtmlに変換される。
link_toのパスには、rails routesコマンドで最初に表示される名前付きパスが利用できる。
演習課題「ビューにカラムを追加する」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
この詳細ページ(show.html.erb)に、次の順序で「weather」カラムを追加してください。
「weather」カラムは、すでにデータベースに追加してあります。content, weather
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
myblog/views/diaries/show.html.erb ファイルを修正する。
<%= notice %>
Content:
Weather:
<%= link_to 'Edit', edit_diary_path(@diary) %> |
<%= link_to 'Back', diaries_path %>
演習課題「リンク先を変更する」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
この詳細ページ(show.html.erb)で、記事一覧に戻る「Back」リンクを、Welcomeページへのリンクに変更してください。
Welcomeページは、すでにRailsアプリに追加してあります。採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
myblog/views/diaries/show.html.erb ファイルを修正し、link_to の 'Back' を welcome_index_path にする。
<%= notice %>
Content:
Weather:
<%= link_to 'Edit', edit_diary_path(@diary) %> |
<%= link_to 'Back', welcome_index_path %>
#04:投稿フォームの動作を理解しよう
ここでは、Rails アプリの Web フォームの動作について、さらに理解を深めます。そのために、投稿フォームにカラムを追加すると共に、form_for メソッドの使い方について学習します。部分テンプレートとは
複数のビューの共通要素を記述するテンプレート。
呼び出すには、次のようにrenderメソッドを利用する。<%= render 'form', article: @article %>
この場合、「_form.html.erb」が部分テンプレートのファイル名になる。
また、article変数で、@articleのオブジェクトを利用できる。フォームを作成するヘルパーメソッド
- form_for:投稿フォームのように、Modelの新規作成・更新に使用する
- form_tag:検索フォームのように、Modelを更新しない場合に使用するform_forメソッド
対象となるモデルのカラムをf変数で取り出して、フォームの構成要素を指定する。<%= form_for(@article) do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :content %>
<%= f.text_field :content %>
<%= f.submit %>
<% end %>
演習課題「フォームにカラムを追加する」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
この投稿フォームページに、次の順序で「weather」カラムを追加してください。
「weather」カラムは、すでにデータベースに追加してあります。content, weather
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
myblog/app/views/diaries/_form.html.erb ファイルを修正し、weather カラムの内容を表示させる。<%= form_for(diary) do |f| %>
<% if diary.errors.any? %>
<%= pluralize(diary.errors.count, "error") %> prohibited this diary from being saved:
<ul> <% diary.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div><% end %>
#05:controllerがデータを書き込む流れを理解しよう
ここでは、投稿フォームのデータを Controller でデータベースに書き込む流れについて学習します。そのために、Web フォームから受信した Parameters の処理について理解すると共に、データベースに書き込む際の安全性を高める strong parameter について説明します。ストロングパラメータとは
データベースに安全にアクセスするために、データベースに書き込みできるカラムをリストアップしておく。
controllerのarticle_paramsメソッドに記述する。articles_controller.rb(一部)
Never trust parameters from the scary internet, only allow the white list through.
def article_params
params.require(:article).permit(:content, :name, :feeling)
end
演習課題「コントローラにカラムを追加する」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
この掲示板に「weather」カラムを追加しましたが、フォームを投稿ボタンをクリックしても、データベースに書き込まれません。
コントローラーを修正して、「weather」カラムのデータが反映されるようにしてください。採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
myblog/app/controllers/diaries_controller.rb を修正し、ストロングパラメータ部分に weather を追加する。
Never trust parameters from the scary internet, only allow the white list through.
def diary_params
params.require(:diary).permit(:content, :weather)end
#06:検索フォームを追加してみよう
ここでは、1 行掲示板に検索フォームを追加します。そのために、検索フォームから送信した検索キーワードを、サーバー側で受け取って、一覧ページを絞り込み表示してみましょう。ビューに検索フォームを追加する
index.html.erb(一部)<%= form_tag('/articles', method: 'get') do %>
<%= label_tag(:name_key, 'Search name:') %>
<%= text_field_tag(:name_key) %>
<%= submit_tag('Search') %> <%= link_to 'Clear', articles_path %>
<% end %>
コントローラーにindexメソッドに検索コードを追加する
articles_controller.rb(一部)GET /articles
GET /articles.json
def index
if params[:name_key]
@articles = Article.where('name LIKE ?', "%#{params[:name_key]}%")
else
@articles = Article.all
end
end
演習課題「間違い探し」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
この掲示板の一覧ページに「weather」カラムの検索フォームを追加しましたが、「search」ボタンをクリックしても、検索ができません。どうやら、index.html.erbに間違いがあるようです。viewを修正して、「weather」カラムを検索できるようにしてください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
myblog/app/views/diaries/index.html.erb ファイルを修正し、form_tag メソッドを使用して検索フォームを作成する。
<%= form_tag('/diaries', method: 'get') do %>
<%= label_tag(:weather_key, 'Search weather:') %>
<%= text_field_tag(:weather_key) %>
<%= submit_tag('Search') %> <%= link_to 'Clear', diaries_path %><% end %>
演習課題「間違い探し」
右の環境には、「myblog」プロジェクトに「Diary」という1行掲示板が作成されています。
この掲示板の一覧ページに「weather」カラムの検索フォームを追加しましたが、部分一致検索ができません。どうやら、diaries_controller.rbに間違いがあるようです。コントローラーを修正して、「weather」カラムを検索できるようにしてください。
採点して、すべてのジャッジに正解すれば、演習課題クリアです!
模範解答1
myblog/app/controllers/diaries_controller.rb を修正し、部分一致検索を可能にする。GET /diaries
GET /diaries.json
def index
if params[:weather_key]
@diaries = Diary.where('weather LIKE ?', "%#{params[:weather_key]}%")
else
@diaries = Diary.all
endend
- 投稿日:2020-04-09T15:07:52+09:00
配列のメソッドまとめ
覚えてないので自分用のカンペ。
配列に要素を加えるメソッド
a.unshift(item)
- 配列aの先頭に新しい要素をつけ加えるメソッド
a = [1,2,3,4,5] a.unshift(0) p a => [0,1,2,3,4,5] # 出力結果a << item もしくは a.push(item)
- << とpushは同じ働きのメソッドで、配列aの末尾に新しい要素itemを付け加える。
a = [1,2,3,4,5] a << 6 # もしくはa.push(6) p a => [1,2,3,4,5,6]a.concat(b) もしくは a + b
- 配列(a)に別の配列(b)を連結する。
- concatは破壊的メソッド。(レシーバにあたるオブジェクトの値そのものを変える。もしb=aとして別の変数にオブジェクトを入れてても、aとbは同一オブジェクトを示すため、aに破壊的メソッドを使えばbの値も変更される)
- +は元の配列をそのままにして新しい配列を作る。
a = [1,2,3,4,5] a.concat([8,9]) p a => [1,2,3,4,5,8,9]a[n] = item もしくは a[n..m] = item もしくは a[n, len] = item
- 配列aの指定された部分の要素をitemに置き換える。
a = [1,2,3,4,5,6,7,8] a[2..4] = 0 p a => [1,2,0,6,7,8] a[1,3] = 9 p a => [1,9,7,8]配列から要素を取り除くメソッド
a.compact もしくは a.compact!
- 配列aの中から要素がnilのものを取り除く。
- compactは新しい配列を作る。compact!メソッドはnilを取り除いたあとのaを返すが、何も取り除けなければnilを返す。
a = [1,nil,3,nil,nil] a.compact! p a => [1,3]a.delete(x)
- 配列aから要素xを取り除く。
a = [1,2,3,2,1] a.delete(2) p a => [1,3,1]a.delete_at(n)
- 配列a[n]の要素を取り除く。
a = [1,2,3,4,5] a.delete_at(2) p a => [1,2,4,5]a.delete_if{|item| ・・・} もしくは a.reject{|item| ・・・} もしくは a.reject!{|item| } ・・・}
- 配列aの各要素itemについて、ブロックを実行した結果が真だった場合、aからitemを取り除く。
- delete_ifとreject!は破壊的なメソッド。
a = [1,2,3,4,5] a.delete_if(|i| i>3) p a => [1,2,3]a.slice!(n) もしくは a.slice!(n..m) もしくは a.slice!(n,len)
- 配列aから指定され部分を取り除き、取り除いた値を返す。
- slice!は破壊的なメソッド
a = [1,2,3,4,5] p a.slice!(1,2) => [2,3] p a => [1,4,5]a.uniq もしくは a.uniq!
- 配列のaの重複する要素を削除。uniq!は破壊的なメソッド。
a = [1,2,3,4,3,2,1] a.uniq! p a => [1,2,3,4]a.shift
- 配列aの先頭要素を取り除き、取り除いた値を返す。
a = [1,2,3,4,5] a.shift => 1 p a => [2,3,4,5]a.pop
- 配列aの末尾要素を取り除き、取り除いた値を返す。
a= [1,2,3,4,5] a.pop => 5 p a => [1,2,3,4]配列の要素を置き換えるメソッド
これも!がつくものは破壊的なメソッド、!がつかないものは別の配列を作って返すメソッド。
a.collect{|item| ・・・} もしくは a.collect!{|item| } ・・・} もしくは a.map{|item|} ・・・} もしくは a.map!{|item| ・・・}
- 配列aの各要素itemにブロックを適用し、その結果を集めて新しい配列を作る。
- 要素数は変わらないが、配列の各要素はブロック中の処理によって前と異なるものになる。
a = [1,2,3,4,5] a.collect! {|item| item * 2} p a => [2,4,6,8,10]a.fill(value) もしくは a.fill(value,begin) もしくは a.fill(value,begin,len) もしくは a.fill(value,n..m)
- 配列aの要素をvalueに置き換える。引数が1つの場合は、aの要素すべてをvalueにする。
- 引数が2つの場合はbeginから配列の末尾まで、引数が3つの場合はbeginからlen個までをvalueにする。
- また2つ目の引数に「n..m」と範囲を指定している場合はその範囲をvalueにする。
p [1,2,3,4,5].fill(0) => [0,0,0,0,0] p [1,2,3,4,5].fill(0,2) => [1,2,0,0,0] p [1,2,3,4,5].fill(0,2,2) => [1,2,0,0,5] p [1,2,3,4,5].fill(0,2..3) => [1,2,0,0,5]a.flatten もしくは a.flatten!
- 配列を平坦化する。(「平坦化」というのは、配列の中に配列が入れ子になっているような場合に、その入れ子を展開して、1つの大きな配列にする操作のこと。)
a = [1,2,3,4,5] a.reverse! p a => [5,4,3,2,1]a.reverse もしくは a.reverse!
- 配列aの要素を逆順に並べ替える。
a = [1,2,3,4,5] a.reverse! p a => [5,4,3,2,1]a.sort もしくは a.sort! もしくは a.sort{|i,j| ・・・} もしくは a.sort!{|i,j| ・・・}
- 配列aの各要素を並べ替える。並べ替え方は、ブロックで指定。
- ブロックを指定しない場合は<=>演算子を使って比較する。
a = [2,4,3,5,1] a.sort! p a => [1,2,3,4,5] # 文字列同士を<=>演算子で比較した場合は文字コードの値で大小が決まる。(アルファベットの場合は大文字の次に小文字) array = ["Ruby", "Perl", "PHP", "Python"] sorted = array.sort p sorted => ["PHP", "Perl", "Python", "Ruby"] # 文字列長い順にソート array = ["Ruby", "Perl", "PHP", "Python"] sorted = array.sort{ |a,b| b.length <=> a.length } p sorted => ["Python", "Perl", "Ruby", "PHP"]a.sort_by{|i| ・・・}
- 配列aの要素を並べ替える。
- 並べ替え方はすべての要素についてブロックを評価した結果をソートした順に行われる。
a = [2,4,3,5,1] p a.sort_by{|i| -i} => [5,4,3,2,1]
- 投稿日:2020-04-09T14:44:39+09:00
【Ruby】正規表現の概要
はじめに
学習中の備忘録です。
概要
使用頻度の高いパターンについてまとめ
- 正規表現を実現する2つのメソッド
- 文字列の一部分を置換する
- 文字列が制約を満たしているか調べる
- 文字列の一部分を抽出する
- 正規表現のパターン表記一覧
前提
Ruby 2.5.1
ターミナルにてirbコマンドで処理結果を確認正規表現を実現する2つのメソッド
subメソッド
subメソッドは、文字列の指定した部分を別の文字列に置き換えるためのメソッドです。第一引数に置き換えたい文字列を指定し、第2引数に変換後の文字列を指定します。 また、操作したい文字列は/で囲みます。
ターミナルirb(main):001:0> str = "りんごを食べる" => "りんごを食べる" irb(main):002:0> str.sub(/りんご/,"みかん") => "みかんを食べる"matchメソッド
matchメソッドは引数に指定した文字列がレシーバの文字列に含まれているか否かをチェックするためのメソッドです。含まれている場合は、指定した文字列がMatchDataオブジェクトの返り値で得られます。また、含まれていない場合は、返り値としてnilが得られます。
※MatchDataオブジェクト
マッチした文字列等はMatchDataオブジェクトで返されます。MatchDataオブジェクトから文字列等を取り出す際は、以下の様に配列からデータを取り出す時と同様の形で取り出すことができます。ターミナルirb(main):001:0> str = "Hello, World" => "Hello, World" irb(main):002:0> md = str.match(/Hello/) => #<MatchData "Hello"> irb(main):003:0> md[0] => "Hello"文字列の一部分を置換する
電話番号からハイフンを取り除く
特定の文字を取り除く場合は、「特定の文字を空文字に置換する」と考えます。置換するメソッドはsubメソッドでした。以下のコードがハイフンを取り除くための処理です。しかしながら、subメソッドでは最初のハイフンしか置換されません。そこでgsubメソッドを用いることにします。
ターミナルirb(main):001:0> tel = '090-1234-5678' => "090-1234-5678" irb(main):002:0> tel.sub(/-/,'') => "0901234-5678" # 最初のハイフンしか置換されない irb(main):003:0> tel.gsub(/-/,'') => "09012345678"※グローバルマッチのg
subの前にgが追加された、gsubメソッドが登場しました。このgが意味するのは、グローバルマッチと呼ばれ、文字列内で指定した文字が複数含まれている場合、その全てを置換するという意味になります。gsubではなくsubを使用した場合、初めの1つだけ置換されます。文字列が制約を満たしているか調べる
パスワードに英数字8文字以上という制約を設定する
今回はパスワードに「Hoge1234」という大文字小文字を区別した英字と数字を使用することを想定します。
matchメソッドを使用して以下のように記述します。ターミナルirb(main):001:0> pass = 'Hoge1234' => "Hoge1234" irb(main):002:0> pass.match(/[a-z\d]{8,}/i) => #<MatchData "Hoge1234">
- [a-z]: 角括弧で囲まれた文字のいずれか 1 個にマッチ
- \d: 数字にマッチ
- {n, m}: 直前の文字が少なくとも n 回、多くても m 回出現するものにマッチ
- i: 大文字・小文字を区別しない検索
[a-z] : 角括弧で囲まれた文字のいずれか 1 個にマッチ
](角括弧)を使用することで角括弧で囲まれた文字のいずれか1つがマッチするかをチェックしています。また、-(ハイフン)を使用することで範囲を設定することができます。[a-z]はアルファベットのa からzまでのいずれかにマッチという意味になります。
(例)a~cの英字を抽出
ターミナルirb(main):001:0> 'dog'.match(/[a-c]/) => nil「dog」という単語にはa ~ cのどの英字も含まれていないのでマッチしません。
\d : 数字にマッチ
このdは数字を表します。数字を表すdのような文字を特殊文字と呼びます。特殊文字を使用する場合は直前に\を記述するというルールが存在します。
今回は、\dは角括弧の内部にあるので、[a-z\d]は「英数字のいずれか1つにマッチ」という意味になります。(例)数字のみ抽出する
ターミナルirb(main):001:0> 'I have 3 pens'.match(/\d/) => #<MatchData "3">{n, m} : 直前の文字が少なくとも n 回、多くても m 回出現するものにマッチ
波括弧を使用することで文字数の制約を追加することができます。{8, }は、直前の文字が少なくとも8回出現するものにマッチという意味になります。今回、波括弧の直前は[a-z\d]でした。つまり、英数字のいずれか1つが少なくとも8回出現するものにマッチという意味になります。
(例)少なくとも4回、多くても6回出現するものにマッチ
ターミナルirb(main):001:0> '12345678'.match(/\d{4,6}/) => #<MatchData "123456"> irb(main):002:0> '123'.match(/\d{4,6}/) => nili : 大文字・小文字を区別しない検索
最後にiオプションを加えることで大文字・小文字を区別せずに検索します。iオプションを付けない場合ですと、[a-z]と小文字で記述しているので大文字にマッチしなくなってしまいます。
(例)大文字・小文字の区別
ターミナルirb(main):003:0> 'Cat'.match(/cat/) => nil irb(main):004:0> 'Cat'.match(/cat/i) => #<MatchData "Cat">実践的な使用方法
パターンにマッチした場合はマッチした要素の配列が返されます。またマッチしなかった場合はnullが返ってくるので、その性質を使用してif文で処理を分けます。
ターミナルpass = 'Hoge1234' if pass.match(/[a-z\d]{8,}/i) //パスワード設定の処理 else puts 'パスワードの形式が間違っています。' end文字列の一部分を抽出する
メールアドレスからドメインの部分のみ抽出する
今回は、「hoge@abcd.com」というアドレスから「@abcd.com」の部分のみを取得したいと思います。
今回もmatchメソッドを使用することで抽出することができます。ターミナルrb(main):001:0> mail = 'hoge@abcd.com' => "hoge@abcd.com" irb(main):002:0> mail.match(/@.+/) => #<MatchData "@abcd.com">
- . : どの1 文字にもマッチ
- + : 直前の文字の 1 回以上の繰り返しにマッチ
. どの1文字にもマッチ
ハイフンやピリオドなど含めた全ての英数字において、どの1文字にもマッチします。
ターミナルirb(main):001:0> 'hoge'.match(/./) => #<MatchData "h">+ 直前の文字の 1 回以上の繰り返しにマッチ
直前の文字が1回以上の繰り返しにマッチします。
ターミナルirb(main):001:0> 'aaabb'.match(/a+/) => #<MatchData "aaa">これらを踏まえますと、.+は何かしらの文字が1回以上繰り返すものにマッチします。先頭に@を付けることで「@から始まり、何かしらの文字が1回以上繰り返すものにマッチ」という意味になります。
こうすることでメールアドレスからドメイン部分のみを抽出することができます。正規表現のパターン表記一覧
パターン 意味 [a-z] 角括弧で囲まれた文字のいずれか 1 個にマッチ \d 数字にマッチ {n, m} 直前の文字が少なくとも n 回、多くても m 回出現するものにマッチ . どの1 文字にもマッチ + 直前の文字の 1 回以上の繰り返しにマッチ まとめ
正規表現について新たな学習があれば適宜追記するかもです。
- 投稿日:2020-04-09T11:46:45+09:00
【Rails】Ajaxを用いた非同期フォロー機能の実装
目標
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina前提
ログイン機能を実装済み。
ログイン機能 ➡︎ https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d
フォロー機能を実装
1.モデル
ターミナル$ rails g model Relationship follower_id:integer followed_id:integerターミナル$ rails db:migrateschema.rbActiveRecord::Schema.define(version: 2020_04_05_115005) do create_table "relationships", force: :cascade do |t| t.integer "follower_id" t.integer "followed_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" t.string "last_sign_in_ip" t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["email"], name: "index_users_on_email", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true end enduser.rbclass User < ApplicationRecord devise :database_authenticatable, :registerable, :recoverable, :rememberable, :trackable,:validatable has_many :books, dependent: :destroy has_many :follower, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :followed, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :following_user, through: :follower, source: :followed has_many :follower_user, through: :followed, source: :follower #ユーザーをフォローする def follow(user_id) follower.create(followed_id: user_id) end #ユーザーをアンフォローする def unfollow(user_id) follower.find_by(followed_id: user_id).destroy end #フォローしているかを確認する def following?(user) following_user.include?(user) end end
![]()
has_many :follower, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
➡︎ Relationshipモデルのfollower_idにuser_idを格納
![]()
has_many :followed, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
➡︎ Relationshipモデルのfollower_idにuser_idを格納
![]()
has_many :following_user, through: :follower, source: :followed
➡︎ 自分がフォローしているユーザー
![]()
has_many :follower_user, through: :followed, source: :follower
➡︎ 自分をフォローしているユーザーrelationship.rbclass Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" end2.コントローラー
ターミナル$ rails g controller relationshipsrelationships_controller.rbclass RelationshipsController < ApplicationController def follow current_user.follow(params[:id]) redirect_to root_path end def unfollow current_user.unfollow(params[:id]) redirect_to root_path end endusers_controller.rbclass UsersController < ApplicationController def index @users = User.all end #自分がフォローしているユーザー一覧 def following @user = User.find(params[:user_id]) end #自分をフォローしているユーザー一覧 def follower @user = User.find(params[:user_id]) end end3.ルーティング
routes.rbRails.application.routes.draw do root 'homes#top' devise_for :users resources :users post 'follow/:id' => 'relationships#follow', as: 'follow' post 'unfollow/:id' => 'relationships#unfollow', as: 'unfollow' get 'users/following/:user_id' => 'users#following', as:'users_following' get 'users/follower/:user_id' => 'users#follower', as:'users_follower' end4.ビュー
users/index.html.erb<table class="table"> <thead> <tr> <th>Image</th> <th>Name</th> <th></th> <th></th> <th></th> <th></th> </tr> </thead> <tbody> <% @users.each do |user| %> <tr> <td><%= attachment_image_tag(user, :profile_image, :fill, 50, 50, fallback: "no-image-mini.jpg") %></td> <td><%= user.name%></td> <td><%= link_to "Show", user %></td> <td><%= link_to "フォロー数:#{user.follower.count}", users_following_path(user) %></td> <td><%= link_to "フォロワー数:#{user.followed.count}", users_follower_path(user) %></td> <td> <% unless user == current_user %> <% if current_user.following?(user) %> <%= link_to 'フォロー解除', unfollow_path(user.id), method: :POST,class:"btn btn-info", remote: true %> <% else %> <%= link_to 'フォローする', follow_path(user.id), method: :POST,class:"btn btn-default" %> <% end %> <% end %> </td> </tr> <% end %> </tbody> </table>users/following.html.erb<h3>フォロー中</h3> <table class="table"> <thead> <tr> <th>Image</th> <th>Name</th> <th>Introduction</th> <th></th> <th></th> </tr> </thead> <tbody> <% @user.following_user.each do |user| %> <tr> <td><%= attachment_image_tag user, :profile_image, :size =>'30x30', class: "img-circle pull-left profile-thumb", fallback: "no_image.jpg" %></td> <td><%= user.name %></td> <td><%= user.introduction %></td> <td><%= link_to "Show", user %></td> <td> <% if current_user.following?(user) %> <% unless user == current_user %> <%= link_to 'フォロー解除', unfollow_path(user.id), method: :POST,class:"btn btn-info", remote: true %> <% else %> <%= link_to 'フォローする', follow_path(user.id), method: :POST,class:"btn btn-default" %> <% end %> <% end %> </td> </tr> <% end %> </tbody> </table>users/follower.html.erb<table class="table"> <thead> <tr> <th>Image</th> <th>Name</th> <th>Introduction</th> <th></th> <th></th> </tr> </thead> <tbody> <% @user.follower_user.each do |user| %> <tr> <td><%= attachment_image_tag user, :profile_image, :size =>'30x30', class: "img-circle pull-left profile-thumb", fallback: "no_image.jpg" %></td> <td><%= user.name %></td> <td><%= user.introduction %></td> <td><%= link_to "Show", user %></td> <td> <% unless user == current_user %> <% if current_user.following?(user) %> <%= link_to 'フォロー解除', unfollow_path(user.id), method: :POST,class:"btn btn-info", remote: true %> <% else %> <%= link_to 'フォローする', follow_path(user.id), method: :POST,class:"btn btn-default" %> <% end %> <% end %> </td> </tr> <% end %> </tbody> </table>非同期機能を実装
1.jQueryを導入
Gemfilegem 'jquery-rails'ターミナル$ bundleapplication.js//= require rails-ujs //= require activestorage //= require turbolinks //= require jquery //= require_tree .2.各ビューのフォローボタンを編集
users/~.html.erb<td class="followbutton_<%= user.id %>"> <!-- 各ボタンにidをつける --> <% unless user == current_user %> <%= render "followbutton", user: user %> <!-- ボタン部分をパーシャル化 --> <% end %> </td>users/_followbutton.html.erb<!-- link_toの引数に「remote: true」を追記 --> <% if current_user.following?(user) %> <%= link_to 'フォロー解除', unfollow_path(user.id), method: :POST,class:"btn btn-info", remote: true %> <% else %> <%= link_to 'フォローする', follow_path(user.id), method: :POST,class:"btn btn-default", remote: true %> <% end %>2.JavaScriptファイルを作成
users/follow.js.erb$("#followbutton_<%= @user.id %>").html("<%= j(render 'users/followbutton', user: @user) %>");users/unfollow.js.erb$("#followbutton_<%= @user.id %>").html("<%= j(render 'users/followbutton', user: @user) %>");
$("#followbutton_<%= @user.id %>")
➡︎ 「2」で付けたクラスを指定
.html("<%= j(render 'users/followbutton', user: @user) %>");
➡︎ フォローボタンのパーシャルをrenderしている参考サイト
- 投稿日:2020-04-09T10:08:43+09:00
オブジェクト指向設計実践ガイド 第2章 「単一責任のクラスを設計する」
「オブジェクト指向設計実践ガイド」
第2章 「単一責任のクラスを設計する」 のまとめです
心に残ったことば
設計とは、アプリケーションの可変性を維持するために技巧を凝らすことであり、完璧を目指すための行為ではない
今回は「単一責任の原則」についてです
単一責任の原則(Single Responsibility Principle)とは?
- クラス、メソッドはそれぞれ一つの責任(役割)に徹すること
- メソッドの単一責任が大事!!
- 一つの責任に徹することで、
- ✅変更の副作用が少なくなる
- ✅変更を取り込みやすくなる
- ✅再利用しやすくなる
- 単一責任のクラス、メソッドは変更に強いアプリケーションの大前提
クラスに属するものを決める
- 最も目立つのは「クラス」だが、「メッセージ(メソッド)」こそがオブジェクト指向設計の核
- メソッドをグループに分けクラスにまとめる
- プロジェクト初期の知識量では、正しくグループ分けなんて到底できない
- 変更がかんたんなようにしておくことが大事
「変更がかんたん」とは?
- 変更による副作用がない
- 要件の変更が小さければ、コード変更量も小さい
- 既存のコードをかんたんに再利用できる
- コードの追加がかんたん
- 追加するコードはそれ自体変更が容易なもの
「変更がかんたん」とは、TRUEなコードのこと
- Transparent(見通しが良い)
- 変更がもたらす影響が明白
- Reasonable(合理的)
- 変更にかかるコストが利益にふさわしい
- Usable(利用性が高い)
- 予期していなかった環境でも再利用できる
- Exemplary(模範的)
- コードに変更を加える人が、品質を自然と保つ
- TRUEなコードは、将来的なニーズを満たすために変更しやすい
- TRUEなコードを書くために、明確に定義された単一の責任を持つよう徹底する
なぜ単一責任が重要なのか
- 変更がかんたんなアプリケーションは、再利用がかんたんなクラスで構成されている
- 再利用がかんたんなクラスは、周りとの絡み合いが少ない疎結合なクラスのこと
- 2つ以上の責任(役割)を持つクラスは、クラス「内部」に絡みついてしまい、かんたんに再利用できない
- 目的の用途と関係ない理由で変更され、アプリケーションを予期せず壊す可能性が高まってしまう
- メソッドも同じ
- 2つ以上の責任(役割)を持つメソッドは変更に弱い
アプリケーション例:自転車とギア(1/2)
- チェーンリングとコグの歯数を受け取り、ギア比を計算する
- ギアに関するデータ、振る舞いしか無いのでこれは単一責任のクラス
class Gear attr_reader :chainring, :cog def initialize(chainring, cog) @chainring = chainring @cog = cog end def ratio chainring / cog.to_f end end
アプリケーション例:自転車とギア(2/2)
- ギアインチを計算できるようにする
- ギアがタイヤのことを知る必要が出た
- 単一責任のクラスではなくなった
class Gear attr_reader :chainring, :cog, :rim, :tire def initialize(chainring, cog, rim, tire) @chainring = chainring @cog = cog @rim = rim @tire = tire # <- ギアがタイヤまで扱うっておかしくない??? end def ratio chainring / cog.to_f end def gear_inches ratio * (rim + (tire * 2)) end end
クラスが単一責任かどうか見極める
- クラスの持つメソッドを質問に言い換えてみる
- 「Gearさん、あなたの比を教えてください」
- ?♂️ギアのことを聞いているのでOK
- 「Gearさん、あなたのタイヤはなんですか?」
- ?♂️ギアにタイヤのこと聞くのはおかしい
- 1文でクラスを説明してみる
- 「それと」が含まれていたら、2つ以上の責任を負っている
- 「または」が含まれていたら、2つ以上の責任があり、さらにそれらがあまり関係ない
単一責任のクラスじゃないことが分かったら・・
- 即座に設計変更すべきとは限らない
- Gearクラスは「見通しが良い」、「合理的」な状態
- Gearへ依存するものがないので問題ない
- 将来どんな要求がくるか分からない
- 多くの情報が揃うまでできるだけ決定を遅らせるべき
- そのために、変更を歓迎するコードを日頃から書いておくことが大事
- 「いますぐ改善」と「あとで改善」のトレードオフを理解してコストを最小にすることが大事
- 「早すぎる最適化」に気を付ける
変更を歓迎するコード(1/3)
- データではなく、振る舞いに依存する
- attr_readerでインスタンス変数を隠蔽する
- 外部から直接インスタンス変数にアクセスしない
- 利用する側からしたらデータでもオブジェクトでもどっちでもよい
- データ構造を隠蔽する
- HashやArrayのデータに対するインデックスアクセスを色んなところでやらない
- Structを作ってインデックスアクセスは一箇所に集約する
- 他ではそのStructのフィールドを利用する
変更を歓迎するコード(2/3)
- メソッドを単一責任にする
- 繰り返し処理と繰り返し実行される処理を別のメソッドにする
- すべてのメソッドが単一の責任を持つようにすることで、クラスの責任を明確にする効果がある
- メソッド内のコードにコメントが必要なら、そのコードをメソッドに抽出する
- そのメソッド名がコメントの目的を果たす
変更を歓迎するコード(3/3)
- クラスの余計な責任を隔離する
- GearからWheelを追い出しながら、Wheelに関する決定を遅らせる
- これ面白いなあと思った
class Gear attr_reader :chainring, :cog, :wheel def initialize(chainring, cog, rim, tire) @chainring = chainring @cog = cog @wheel = Wheel.new(rim, tire) end def ratio chainring / cog.to_f end def gear_inches ratio * wheel.diameter end Wheel = Struct.new(:rim, :tire) do def diameter rim + (tire * 2) end end end
まとめ
- 変更が簡単なアプリケーションは、単一責任のクラス、メソッドから構成される
- クラス、メソッドはそれぞれ一つの役割に徹するべき
- 複数の役割を持つと変更・再利用しづらくなる
- 多くの情報が揃うまで待ち、できるだけ決定を遅らせること
- そのために、変更に強い単一責任のクラス、メソッドを日頃から意識して書くことが大事
- 特にメソッドの単一責任が大事!!!
まずは、メソッドの単一責任からはじめましょう!!!
おしまい
- 投稿日:2020-04-09T07:40:23+09:00
第13章〜マイクロポストを作る〜
ユーザーのマイクロポスト
今回ユーザーが短いメッセージを投稿できるような機能を実装する。
マイクロソフトモデルの作成
このMicropostモデルはデータ検証とUserモデルの関連付けを含んでいる
マイクロポストの内容を保存するcontent属性と特定のユーザーとマイクロポストを関連付けするuser_id属性の2つだけの属性だけをもつ。
Micropostはstring型ではなくtext型を使用する。
140文字制限をつけるので表示できる文字数などは差がないがtext型のフォームのほうがより自然な投稿フォームを実現できる。
Userモデルとの最大の違いはreferences型を利用している点である。
これを利用すると自動的にインデックスと外部キー参照付きのuser_idカラムが追加され、UserとMicropostを関連付けする下準備をしてくれる。add_index:micropost,[use_id,:create_at]このコードはあるユーザーを検索したいときにUsersテーブルのnameカラムにインデックスを張ってないと、プログラムは、Userテーブルのnameカラムを上から順にみて、そのユーザーのデータを取得します。もし、これが1万人もしくはそれ以上の大量のデータを含むカラムだった場合に時間がとてもかかってしまう。
Usersテーブルのnameカラムにindexを張ることで、アルファベット順にnameを並べ替え検索しやすいようにしてくれます。また、user_idとcreated_atの両方を1つの配列に含めている点にも注目です。こうすることでActive Recordは、両方のキーを同時に扱う複合キーインデックス (Multiple Key Index) を作成します。バリデーションの作成
まずuser_idに対するバリデーションを作成。
次に、マイクロポストのcontent属性に対するバリデーションを追加。マイクロポストのuser_idに対する検証 green app/models/micropost.rb class Micropost < ActiveRecord::Base belongs_to :user validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } endUserとMicropostの関連付け
Micropostモデルにコード化 belongs_to :userMicropostとそのUserはbelongs_to(一対一)の関係性がある
Userモデルにコード化 has_many:micropostUserとMicropostはhas_many(一対多)に関係性がある
@user.microposts.builduserに紐付いた新しいMicropostオブジェクトを返す
Micropostを改良する
UserとMicropostの関連付けを改良する。
ユーザーのマイクロポストを特定の順序で取得できるようにする
マイクロポストをユーザーに依存させて、ユーザーが削除されたらマイクロポストも自動的に削除されるようにしていくデフォルトスコープ
user.micropostsメソッドはデフォルトでは読み出しの順序に対して何も保証しないが、 ブログやTwitterの慣習に従って、作成時間の逆順、つまり最も新しいマイクロポストを最初に表示するようにしてみましょう4。これを実装するためには、default scopeというテクニックを使う。
default_scopeでマイクロポストを順序付ける green app/models/micropost.rb class Micropost < ApplicationRecord belongs_to :user default_scope -> { order(created_at: :desc) } validates :user_id, presence: true validates :content, presence: true, length: { maximum: 140 } endDependent: destroy
今度はマイクロポストに第二の要素を追加する。サイト管理者はユーザーを破棄する権限を持つ。ユーザーが破棄された場合、ユーザーのマイクロポストも同様に破棄されるべきです。
class User < ApplicationRecord has_many :microposts, dependent: :destroy endMicropostの操作
Micropostsリソースへのインターフェイスは、主にプロフィールページとHomeページのコントローラを経由して実行されるので、Micropostsコントローラにはnewやeditのようなアクションは不要ということになる。
Rails.application.routes.draw do resources :microposts, only: [:create, :destroy] endマイクロポストのアクセス制御
logged_in_userメソッドを使って、ログインを要求する。
Usersコントローラ内にこのメソッドがあったので、beforeフィルターで指定していたが、このメソッドはMicropostsコントローラでも必要です。そこで、各コントローラが継承するApplicationコントローラにこのメソッドを移し、まとめる。logged_in_userメソッドをApplicationコントローラに移す red app/controllers/application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper private # ユーザーのログインを確認する def logged_in_user unless logged_in? store_location flash[:danger] = "Please log in." redirect_to login_url end end endこのコードによって、Micropostsコントローラからもlogged_in_userメソッドを呼び出せるようになる。
Micropostを作成
まずはcreateアクションを作成
Micropostsコントローラのcreateアクション app/controllers/microposts_controller.rb class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] def create @micropost = current_user.microposts.build(micropost_params) if @micropost.save flash[:success] = "Micropost created!" redirect_to root_url else render 'static_pages/home' end end def destroy end private def micropost_params params.require(:micropost).permit(:content) end endStrong Parametersを使用
ストロングパラメータは、Web上から受けつけたパラメータが、本当に安全なデータかどうかを検証した上で、取得するための仕組み。Rails4から実装されている。具体的にどうやって防ぐか
メソッドにあらかじめ登録・更新を許可するカラム名を指定(ホワイトリスト形式)する。そうすると、万が一、未許可のカラムデータが送られてきても、データの登録前に未許可であることを検出し、登録対象として無視することができる。フィードの原型
マイクロポスト投稿フォームが動くようになったが、今の段階では投稿した内容をすぐに見ることができない。Homeページにまだマイクロポストを表示する部分が実装されていないからである。
すべてのユーザーがフィードを持つので、feedメソッドはUserモデルで作るのが自然。フィードの原型では、まずは現在ログインしているユーザーのマイクロポストをすべて取得する。マイクロポストの削除
マイクロポストリソースにポストを削除する機能を追加します。これはユーザー削除と同様に"delete" リンクで実現します。ユーザーの削除は管理者ユーザーのみが行えるように制限されていたのに対し、今回は自分が投稿したマイクロポストに対してのみ削除リンクが動作するように設定する。
Micropostsコントローラのdestroyアクション
class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :correct_user, only: :destroy . . . def destroy @micropost.destroy flash[:success] = "Micropost deleted" redirect_to request.referrer || root_url end private def micropost_params params.require(:micropost).permit(:content) end def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end endrequest.referrer || root_url
ここではrequest.referrerというメソッドを使っています12。このメソッドはフレンドリーフォワーディングのrequest.urlと似ていて、一つ前のURLを返す (今回の場合、Homeページになります)このため、マイクロポストがHomeページから削除された場合でもプロフィールページから削除された場合でも、request.referrerを使うことでDELETEリクエストが発行されたページに戻すことができるので、非常に便利です。ちなみに、元に戻すURLが見つからなかった場合でも (例えばテストではnilが返ってくることもあります)、||演算子でroot_urlをデフォルトに設定しているため心配ない。
- 投稿日:2020-04-09T07:32:14+09:00
map 派? collect 派?
最近下記の記事を読んで。
私、map 派でした。
そうか、関数型は結構合ってるのかもしれん。純粋関数型言語は使ったことなかったけど。なんとなく融通効かなさそう、とか、理想が高過ぎて実用的ではなさそう、みたいな先入観で捉えてましたが。しかし、私の理想とするプログラムの組み方は関数型だったのか。
仕組みと定義でソフトウェアを組み上げていくという考え方は 100% 同意。分かりづらいかもしれないが、ランダムという要素を無くしたパチンコ台みたいなもので、玉を転がしたらあるルールに従って然るべき所に自動的に転がっていくという。バグを一番少なくできるやり方だと思う。異常系も含めて想定外を無くす的な。オートマトンとかもそう。「やりたいことを宣言的に書いておけば、あとはコンピューターが頑張ってくれる」という発想は大事。
なので、初心者向けの講座で良くあるような「順に A やって、次に B やって」というプログラムの作り方はよろしくないと思うのです。
そうは言っても純粋な関数型(と何か言えるほどの見識もなく)、というのだと実際問題としてプログラム作れないんじゃなかろうか、という先入観があった。というか、今もある。その辺は原理主義者ではなく現実主義者なのかなー、と思う。
個人的には仕組みで作っていくやり方は、「フレームワークとライブラリとオブジェクト指向で組み上げていくイメージ」を持ってました。
単に見た目で Lisp を避けてただけかもしれないけど。(かっこ)ばっかりなので。今やってる Kinx も、やたら見た目にこだわってるし。
ということで、map 派。そして reduce 派。Kinx での例。
Kinxvar a = 100.times(); var r = a.map(&(e) => e * 2).reduce(&(r, e) => r + e); System.println(r);結果。
9900あ、Array の説明してないな。近々書きます。こういう map とか reduce とか、Array 用の特殊メソッドもいくつか定義してあるので、使えると幅が広がる。
ただし、Array と Object は本質的に同じなので、Array の特殊メソッドはオブジェクトにも適用されるので注意。逆に Object という名の特殊オブジェクトは無いので、それもご注意。
- 投稿日:2020-04-09T02:20:53+09:00
【Rails】繰り返し処理でメニューを簡単に作る方法
はじめに
下の記事「【爆速実装】夢と魔法のeach文【Rails-haml】」の記事を見て爆速でメニューが作れることを知りました。
https://qiita.com/enjoy_omame/items/c9f14f9cbb9bd2408a18
すげぇ。なんじゃこりゃ!で、この記事なんですが、あくまで"簡単に作る方法"を解説してくれています。遷移先などの実装には触れていません。
きっと「ここからは自分で考えて実用的にしましょうね」という意図があるのだろう。と、言っているんだと思います。僕には聞こえました。そんな副音声が!
なので、自分なりに考えて実用的な実装をしてみたので今回記事にしてみます。実装した内容
こんな感じのメニューを繰り返し処理で実装します。
今回は、usersコントローラ内のshowアクション(ここで繰り返し処理の元になるネタをしこむ)を介して、show.html.haml(ここで仕込んだネタを繰り返し処理して)表示するといった感じに処理が走ってます。
「【爆速実装】夢と魔法のeach文【Rails-haml】」の記事では、上記実装で言う、「プロフィール編集」「クレジットカード登録」「ログアウト」とかの文字列を繰り返し処理を使って実装する方法を紹介しています。そのため、リンク先の設定とかについては触れていません。
なので、クリックした時の遷移先についても繰り返しで実装できるように挑戦しました!実装結果
コントローラー
users_controller.rb(参考にした記事を自分なりに応用してみた)def show @user = User.find(params[:id]) @contents = [{name:"プロフィール編集",path: "/profiles/edit",pattern: "GET"},{name: "クレジットカード登録", path:"#", pattern: "GET"},{name: "ログアウト", path: "/users/sign_out", pattern: "delete"}] end※クレジットカードの導線はまだ決めてないので、遷移先を"#"で仮置きしてます。
ビュー
show.rb(部分テンプレートで切り出してます)- @contents.each do |content| .MyPageSideMenuList .MyPageSideMenuList__Value = link_to content[:path],method: content[:pattern] do = content[:name] .MyPageSideMenuList__Disclosure = icon('fas','chevron-right')解説(と、いうか実装に到るまでの脳内経緯)
「参考にした記事では、表示したい文字列をeach文を使って繰り返し処理で表示しているぞ。」
↓
「ん?それなら、each文を使って繰り返し処理をする時にURLとかも一緒にインスタンス変数に入れられれば良い感じに遷移先も実装できるんじゃない?」
↓
「そんなことできるのだろうか・・・。ん?まてよ、データベースから複数のレコードを取得している時って似たような事やってない?」
↓
「あ、じゃあ配列の中身をハッシュにして、その中に文字列とURLをキーとして設定すればいいんじゃない?」
↓
「ぉ?と言うことは、URLを設定するんだしHTTPメソッドも指定しないとルーティングエラー起こすぞこりゃ。methodを入れるキーも必要だ!」
(ごにょごにょえーと・・・端的に言いますと
配列の中に3つのキーを含んだハッシュを入れてみました。
ハッシュの内容はこんな感じ
キー(シンボル) 入れるバリュー name: リストに表示したい文字列 path: 遷移させたいURL pattern: HTTPメソッド user_controller(代入内容の比較).rb#before @contents = ["プロフィール編集","クレジットカード登録","ログアウト"] #After(ハッシュを配列に入れてる) @contents = [{name: "プロフィール編集",path: "/profiles/edit",pattern: "GET"},{name: "クレジットカード登録", path: "#", pattern: "GET"},{name: "ログアウト", path: "/users/sign_out", pattern: "delete"}]つまり、
name:
path:
pattern:
で1セットのハッシュにしてあげて、この塊をeach文で繰り返す。って感じです。もうちょっと掘り下げて解説
show.html.haml-#~省略(部分テンプレートで切り出してます) - @contents.each do |content| .MyPageSideMenuList .MyPageSideMenuList__Value = link_to content[:path],method: content[:pattern] do = content[:name] .MyPageSideMenuList__Disclosure = icon('fas','chevron-right')5行目の
=link_to
に注目です。もし、繰り返しを使わずに、ログアウトの処理を記述する場合は、以下のような書き方ができます。
(※devise gem を利用している前提でのお話となります。)sample.html.haml-# URLをベタ書きした場合 = link_to "/users/sign_out", method: :delete do = "ログアウト"今回は @contents をcontentに代入してeach文で繰り返し処理をしていますよね?
そのため、繰り返し処理の最中、contentさんは以下のように扱われます。#1週目のcontentの中身 → {name:"プロフィール編集",path: "#",pattern: "GET"} #2週目のcontentの中身 → {name: "クレジットカード登録", path:"#", pattern: "GET"} #3週目のcontentの中身 → {name: "ログアウト", path: "/users/sign_out", pattern: "delete"}ではでは、この知識を前提として、繰り返し処理の3週目がどうなるかというと・・・。
show.html.haml-#~省略(部分テンプレートで切り出してます) - @contents.each do |content| .MyPageSideMenuList .MyPageSideMenuList__Value -#実際に記載されている下の2行が... = link_to content[:path],method: content[:pattern] do = content[:name] -#繰り返し処理の結果、下の2行の内容に書き換わる = link_to "/users/sign_out",method: :delete do = ログアウト .MyPageSideMenuList__Disclosure = icon('fas','chevron-right')なんとなくイメージ掴めますかね?
最後は、上の結果を先ほどちょっと記述した
sample.html.haml
(URLをベタ書きした時)と比較してみましょう!sample.html.haml-# URLをベタ書きした場合 = link_to "/users/sign_out", method: :delete do = "ログアウト"どうでしょうか?同じ内容になってませんか?
これで、繰り返し処理で遷移先も実装が可能となりました!まとめ
今回やったことをざっとまとめると・・・。
- コントローラーの該当アクションで繰り返したいネタを配列で準備する
- Hashの中身を{name: " ~~ ", path: " ~~ ", method: " ~~ "}を1セットとして配列に格納する
- ヘルパーメソッド の
=link_to
の引数の箇所にhashのキーを置き換えてあげるこれだけを意識すれば、あら不思議!遷移できるメニューリストが作れちゃう!!
(補足)pathとmethodについては、ターミナルでrails routesをしてあげて、飛ばしたい遷移先がなんなのかを調べてあげれば問題なしです!最後に
他にも実装方法を色々試しましたが、:pathにPrefixの値を記述した場合はどうもうまくいきませんでした。
理由としては、ダブルクォーテーションもしくはシングルクォーテーションで囲われた状態になってしまうからだと思います。sample.html.haml-#prefixの正しい指定方法 = link_to destroy_user_session_path, method: :delete do -#@contentsの中に入れたハッシュの :path でprefixを指定した場合 = link_to "destroy_user_session_path", method: :delete do -# →Prefixで指定しているのに""で囲われてしまうからprefixとして扱われない解消方法やもっと良い実装方法などご存知の方いらっしゃいましたら、コメント頂けますと幸いです!
駄文にお付き合いくださりありがとうございました!
- 投稿日:2020-04-09T00:05:23+09:00
Railsチュートリアル9章攻略の第一歩を踏み出してみた。【セッション永続化編】
はじめに
9章の解説は私にとってあまりに複雑だったので
少しだけわかりやすく理解できるように、9章の中でもセッションの永続化に関する内容を簡単にまとめてみました。説明と言うより、箇条書き形式にしてまとめております。
こんな人に読んで欲しい
Railsチュートリアルを1〜8章までをそれなりに理解し、尚且つ9章を一応は終えたが、それでも理解度は低めだと感じている方。
内容
セッション永続化機能の実装。
簡単に言うと、ブラウザを閉じてもログインを保持することができる機能です。
見たらわかるめちゃくちゃ便利な機能って事で絶対に物にしたいです。実装手順
前置きが長くなりましたが、進めていきます。
ログイン保持機能を実装する手順を簡単にまとめると以下のようになります。1.永続セッション作成の準備
2.永続セッション作成
3.上記を利用して、ログインユーザーのログインを保持する
4.永続セッションからログアウトできるようにする
5.上記機能実装により発生するバグを修正するでは、より詳しくまとめていきます。
1.永続セッション作成の準備
手順1の、永続セッション作成の準備についてです。
ログインを保持するには永続セッションが必要ですが、さらにその永続セッションを作成するための準備が必要になるので、手順1ではその準備を行っていきます。
永続セッション作成準備でやること
・ユーザーを記憶するための、記憶トークンなる物を作成する。
・その記憶トークンをハッシュ化し、データベースに保存させる機能を実装する。
1-1.トークンの作成。
❶Userモデル内に、クラスメソッドとして生成。
・Userモデル内のdigestメソッドが、ユーザーオブジェクトを必要としないため。
❷トークンを生成するメソッドを定義。
・new_tokenとする。
❸トークンは、長いランダムな文字列にすること。
・SecureRandomモジュールにある、urlsage_base64メソッドを使用。
・22文字のランダムな文字列が生成される。
❹このトークンを使って、記憶トークンを生成する。1-2.記憶トークンをユーザーと関連付け、記憶ダイジェストに保存されるようにする。
❶記憶ダイジェスト属性をUserモデルに追加する。
・カラム名はremember_digest、データ型は文字列とする。
・コマンドでDBへその変更を反映。
・このデータにユーザーが触れることはないので、インデックスは不要。
・記憶トークンは、このカラムに保存されることになる。
❷記憶トークン属性を追加する。
・DBには保存しないような属性を追加する。
・attr_accessorを使い、仮想の属性とする。
・Userモデル内に記述する。
・属性名はremember_tokenとする。
・この記憶トークンの値が、記憶ダイジェストカラムに追加されることになる。
❸ユーザーを記憶し、記憶ダイジェストに保存するメソッドを定義する。
・Userモデル内に作成。
・メソッド名をrememberとする。
・1-1で生成したランダムな文字列のトークン(new_token)を、remember_token属性として取得する処理の記述。
→remember_token属性にはselfを付け、属性として正しく認識させる。
→付けないとローカル変数として定義される。
・記憶トークンを、記憶ダイジェストに保存する処理の記述。
→update_attributeを使う。
→remember_tokenで取得した値をハッシュ化して、記憶ダイジェストに保存する。
→パスワード実装の際に使用した、digestメソッドによりハッシュ化する。
・このメソッドは、バリデーションを素通りさせる。
・これにより、ユーザーと関連付けられた記憶トークンが、記憶ダイジェストに保存されるようになった。
→例えば、user.rememberのようにして、このメソッドを使ってユーザーを呼び出すと、呼び出したユーザーのremember_digestカラムに、ランダムな文字列で生成された記憶トークンが保存されるようになる。models/user.rb#1-1 def User.new_token #Userを付することでクラスメソッドであることを明示的に示している。 SecureRandom.urlsafe_base64 end1-2$ rails g migration add_remember_digest_to_users remember_digest:string $ rails db:migratemodels/user.rb#1-2 attr_accessor :remember_token #仮想の記憶トークン属性を作成。models/user.rb#1-2 #記憶トークンをハッシュ化し、呼び出したユーザーの記憶ダイジェストに保存する。 def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end1-3.準備完了
・以上で、ユーザーと一緒にrememberメソッドを呼び出すと、そのユーザーの記憶ダイジェストにランダムな文字列が保存されるようになりました。
2.永続セッション作成
続きまして、手順2の永続セッションの作成です。
手順1で作成したメソッドなどは、ここで使用していきます。
永続セッション作成でやること
・ユーザーIDを暗号化する
・暗号化したユーザーIDと記憶トークンを、ブラウザの永続cookiesに保存する。
・以上の機能を実装するために、cookiesメソッドを使用する。
2-1.記憶トークンをcookiesに保存する。
❶cookiesメソッドを使用することで保存できる。
・記憶トークンをcookieに保存する。
→cookies.permanent[:remember_token] = remember_token
→cookiesメソッドはsessionメソッド同様、ハッシュで扱う。
→permanentメソッドを使用すれば、cookiesの有効期限が20年後に設定される。
→permanentメソッドにより永続化完了。2-2.ユーザーIDを暗号化してcookiesに保存する。
❶cookiesメソッドでcookieにユーザーを保存する。
・cookies[:user_id] = user.id
・ただこのままでは、IDが生のテキストのまま保存されているので、奪い取られる可能性がある。
・署名付きcookieを使い、ユーザーIDを暗号化する。
→cookies.signed[:user_id] = user.id
→sigedメソッド付与により暗号化される。
・さらに、cookieも永続化してあげる。
→cookies.permanent.signed[:user_id] = user.id
→permanentとsignedはメソッドチェーンで繋ぐ。
・cookiesを設定したことで、cookiesからユーザーを取得できるようになった。
→例えば、User.find_by(id: cookies.signed[:user_id])
→ちなみに、cookies.signed[:user_id]では自動でユーザーIDの暗号化が解除される。2-3.cookiesに保存された記憶トークンが、ユーザーの記憶ダイジェストと一致することを確認する。
❶この2つの一致を確認することにより、後々ログインを可能とする。
→パスワードで例えると、passwordとpassword_digestの比較と同じ。
❶bcryptを使って確認。
・secure_passwordのソースコードを確認してみる。
→BCrypt::Password.new(password_digest) == unencrypted_passwordというコードを参考にする。
→bcryptでハッシュ化されたパスワードを比較する際にこのコードが使われているので、これを利用してやるという動機がある。
→BCrypt::Password.new(remember_digest) == remember_tokenとして、当てはめてみる。
→しかしこれだと、暗号化されたパスワードとトークンを直接比較している。
→bcryptでは記憶ダイジェストは復号化されないので、直接比較はできない。
・bcrypt gemのソースコードの方を詳しく見てみる。
→すると、==の部分がis_password?という論理値メソッドで再定義されている。
→なので、BCrypt::Password.new(remember_digest).is_password?(remember_token)として比較してあげることにする。
・これにより、記憶トークンと記憶ダイジェストを比較してあげることにする。2-4.2-3より、記憶トークンと記憶ダイジェストを比較するためのメソッドの定義をする。
❶Userモデルの中に作成。
❷メソッド名はauthenticated?とする。
❸渡された記憶トークンが、記憶ダイジェストと一致したらtrueを返す処理を記述する。
❹ここでの記憶トークンとは、cookiesに保存されている物で、仮想の属性であるアクセサとは別物。
※このメソッドは、手順3の3-2で使用。#2-1.記憶トークンをcookiesに保存する。 cookies.permanent[:remember_token] = user.remember_token#2-2.ユーザーIDを暗号化してcookiesに保存する cookies.permanent.signed[:user_id] = user.idmodels/user.rb#2-4.記憶トークンと記憶ダイジェストを比較するためのメソッドの定義をする def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end2-5.これにて、永続セッションの作成が完了しました。
3.ログインしたユーザーのログインを保持する
続きまして、手順3に移ります。
手順2で作成した永続セッションの動作を利用して、ログインを保持する機能を実装していきます。3-1.ログイン時にユーザーを保持させる。
❶ログインを保持するためのメソッドを定義する。
・メソッド名をrememberとし、ユーザーを引数に取るヘルパーメソッドを定義。
→Sessionsヘルパー内に記述。
→記憶トークンを生成し、DBの記憶ダイジェストに保存させる処理を記述。
→これに関しては、Usersモデルで定義したrememberメソッドを呼び出せばいい。
→暗号化したユーザーIDを、永続cookiesに保存する処理を記述。
→ユーザーの記憶トークンを、永続cookiesに保存する処理を記述。
・そして、アクション内でこのメソッドを呼び出すと…
→渡されたユーザーの記憶トークンが記憶ダイジェストにが保存される。
→暗号化したユーザーIDと記憶トークンが、永続cookiesに保存される。
→その結果、ログインが保持されることになる。
❷最後に、Sessionsコントローラ内でrememberヘルパーメソッドを呼び出す。
・ログインの保持は、ログインした時に実行されるのでlog_inメソッドとセットで定義してあげる。
❸これでログインしたユーザーは、ブラウザに正しく記憶されるようになったので、ログイン保持機能の実装は完了した。3-2.問題点
しかしながら現状では、このログイン保持機能には、問題点が1つ存在します。それは、Sessionsヘルパー内で定義されているcurrent_user内にあり、このメソッド内でsessionメソッドによる一時セッションしか扱っていないため、ログイン保持機能が正常に動作しない。【解決策】
❶永続セッションを動作させる場合には、ユーザーの取得方法に関して条件がある。
・session[:user_id]が存在する場合は、一時セッションからユーザーを取得する必要がある。
→sessionにユーザーIDが存在するかどうかの条件式を記述。
・それ以外の場合は、cookies[:user_id]からユーザーを取得する必要がある。
→cookiesに、暗号化されたユーザーIDが存在するかどうかの条件式を記述。
→存在する場合、cookiesに保存されたIDと一致するIDを持つユーザーを取得する処理を記述。
→「上記のユーザーが存在し」、かつ、「そのユーザーの記憶トークンと記憶ダイジェストが一致する(2-4で定義したメソッドを使用)」かどうかの条件式を記述。
→どちらの条件も満たすならば、そのユーザーでログインする処理を記述。
→ログイン中のユーザーをインスタンス変数で返す処理を記述。
・リファクタリング
→ローカル変数を用いて、同じ記述をしている部分を修正。
→重複している記述を、条件式内でローカル変数として一気に定義してあげる。
→ユーザーIDのセッションが存在する場合=if(user_id = session[:user_id])とする。
→ユーザーIDのクッキーが存在する場合=if(user_id = cookies.signed[:user_id])とする。
→ローカル変数user_idを利用する。
❷これにより、ログインしたユーザーが正しく記憶されるようになった。helpers/sessions_helper.rb#3-1の❶.ログイン時にユーザーを保持させるメソッド。 def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token endcontrollers/sessions_controller.rbdef create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user remember user #3-1の❷.ログイン時に、ログインを保持する。 redirect_to user else flash.now[:danger] = "失敗" render 'new' end endhelpers/sessions_helper.rb#3-2.記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) #sessionを持つユーザーの存在。 @current_user ||= User.find_by(id: user_id ) elsif (user_id = cookies.signed[:user_id]) #暗号化IDを持つユーザーの存在。 user = User.find_by(id: user_id ) #ユーザーの取得 if user && user.authenticated?(cookies[:remember_token]) #ユーザーの存在と、記憶トークンと記憶ダイジェストが一致するかどうか(2-4で定義)。 log_in user @current_user = user end end end
4.永続セッションからログアウトできるようにする
4-1.完全にログアウトできるように、ユーザーを忘れるためのメソッドを定義する必要がある。
・Userモデルに追加。
・メソッド名をforgetとして定義する。
・DBの記憶ダイジェストをnilで更新する処理を記述をする。
→update_attributeを使用。
・ユーザーのログイン情報を破棄する処理を記述する。
・永続セッションを終了させる準備が完了。4-2.永続的セッションを破棄するメソッドを記述。
・Sessionsヘルパー内に追加。
・メソッド名をforgetとする。
・渡されたユーザーのログイン情報を破棄する処理を記述。
・ブラウザに保存されている、ユーザーIDのクッキーを削除する処理を記述。
・ブラウザに保存されている、記憶トークンのクッキーを削除する処理を記述。4-3.現在のユーザーのログアウトする。
・log_outヘルパーメソッド内で、永続セッションを破棄するメソッドを呼び出す。
→ログイン中のユーザーを破棄するので、引数はcurrent_user。4-4.全てのテストスイートがGREENになる。
models/user.rb#4-1.ユーザーのログイン情報を破棄する。 def forget update_attribute(:remember_digest, nil) endhelpers/sessions_helper.rb#4-2.ユーザーの永続的セッションを破棄する。 def forget(user) user.forget #4-1のメソッドを呼び出してる。 cookies.delete(:user_id) cookies.delete(:rememeber_token) endhelpers/sessions_helper.rb#4-3.現在のユーザーをログアウトする。 def log_out forget(current_user) #4-2のメソッドを呼び出している。 session.delete(:user_id) @current_user = nil end
5.上記機能実装により発生するバグを修正する
まず、上記機能実装によるバグが2つ存在するので、その詳細から挙げていく。
【1つ目】:複数のタブを開きながらログインしているユーザーが、一方のタブでログアウトをして、もう一つのタブで再度ログアウトしようとするとエラーになる。
【原因】:ログアウトリンクをクリックすると、current_userがnilになり、log_outメソッド内のforget(current_user)が失敗してしまうため。
【解決策】:ユーザーが、ログイン中の場合にのみログアウトさせる。【2つ目】:複数のブラウザ(FirefoxやChromeなど)でログインしているユーザーが、一方のブラウザ(Firefoxとする)ではログアウトして、
一方(Chromeとする)のブラウザではログアウトはせずに、ブラウザを終了させてから再度同じブラウザで同じページへ行くと、問題が発生する。
【具体例】
・Firefoxでログアウトした時。
→user.forgetメソッドによってremember_digestがnilになる。
→この時同じタイミングで、log_outメソッドによりユーザーIDが削除され、current_userメソッド内の2つの条件文がfalseになる。
→結果、current_userメソッドの戻り値は期待通りnilになる。
→ここまでは正常に動作している。
・問題は、Chromeをログアウトせずに閉じた時。
→current_userメソッド内のsession[:user_id]はnilになる(セッションの有効期限切れによる)。
→しかし、cookiesはブラウザに残り続けている。
→そのためChromeを再起動して、アプリにアクセスすると、DBからそのユーザーを見つけることができてしまうため、セキュリティ上の問題が存在する。
→クッキーが存在するせいで、current_userメソッド内のuser && user.authenticated?(cookies[:remember_token])が評価される。
→結果的に、記憶ダイジェストがnilなので、user.authenticated?(cookies[:remember_token])の部分でエラーが発生する。
【原因】
・Firefoxログアウト時に、remember_digestが削除されるにもかかわらず、Chromeアクセス時、BCrypt::Password.new(remember_didest).is_password?(remember_token)が実行されてしまうため。
→要するにremember_digestがnilとなるため、bcrypt内で例外が発生してしまう。
【解決策】
・remember_digestが存在しない場合は例外ではなく、falseを返す処理を、authenticated?メソッドに追加する。5-1.まず、上記2つのエラーをキャッチするテストを記述していく。
【1つ目の問題についてのテスト】
❶2番目のウィンドウでログアウトをクリックするユーザーを検証する。
・integration/users_login_test.rb内のログアウトテストに追加で記述。
・1個目のログアウトの後に、もう一つログアウトテストを追加する。
→delete logout_path
・current_userがいないので、2度目の呼び出しはエラーが発生するため、テストスイートはRED。
❷テストをパスさせるために機能を追加していく。
・ユーザーがログイン中の場合のみ、ログアウトできるようにする機能を記述する。
→Sessionsコントローラ内に処理を記述。
→logged_in?メソッド(ログインしているユーザーがいればtrueを返すメソッド)が、trueの場合にのみ、log_outを呼び出すようにする。
→destroyアクション内に処理を記述。
→「 log_out if logged_in? 」
・テストスイートがパス。GREEN。【2つ目の問題についてのテスト】
❸ダイジェストが存在しない場合のauthenticated?をテストする。
・Userモデルで直接テストする。
・記憶ダイジェストを持たないユーザーを用意する。
・authenticated?メソッドを呼び出す。(渡されたトークンが、ダイジェストと一致したらtrueを返すメソッド)
・assert_not @user.authenticated?('')
→結果がfalseならパス。
→記憶トークンが使われる前に記憶ダイジェストでエラーになるので、記憶トークン部分の値は何でもいい。
・BCrypt::Password.new(nil)でエラーが発生するため、テストスイートはRED。❹テストをパスさせる。
・authenticated?メソッドで、記憶ダイジェストがnilの場合はfalseを返すようにする処理を記述。
→return false if remember_digest.nil?
→returnキーワードによって、記憶ダイジェストがnilの場合、即座にメソッドを終了させる。
→処理を途中で終わらせるのに用いられるテクニック。
・テストスイートGREEN。integration/users_login_test.rbtest "login with valid information followed by logout" do get login_path post login_path, params: { session: { email: @user.email, password: 'password' }} assert is_logged_in? assert_redirected_to @user follow_redirect! assert_template 'users/show' assert_select "a[href=?]", login_path, count: 0 assert_select "a[href=?]", logout_path assert_select "a[href=?]", user_path(@user) delete logout_path assert_not is_logged_in? assert_redirected_to root_url delete logout_path #5-1の❶.2度目のログアウトを検証(RED)。 follow_redirect! assert_select "a[href=?]", login_path assert_select "a[href=?]", logout_path, count: 0 assert_select "a[href=?]", user_path(@user), count: 0 endcontrollers/sessions_controller.rbdef destroy log_out if logged_in? #5-1の❷.ログインしているユーザーがいる場合のみログアウトさせる。 redirect_to root_url endtest/models/user_test.rb#5-1の❸.authenticated?メソッドの結果がfalseならテストをパスする。 test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?('') endmodels/user.rbdef authenticated?(remember_token) return false if remember_digest.nil? #5-1の❹.記憶ダイジェストがnilの場合はfalseを返す。 BCrypt::Password.new(remember_digest).is_password?(remember_token) end
最後に
以上で、セッションの永続化機能の実装が完了しました。
あとは、この機能をユーザーが選択可能にする[Remember me]チェックボックス機能を実装すれば、9章の内容が完了します。
チェックボックス編に関しては、別でまた記事を投稿しようと思います。参考
- 投稿日:2020-04-09T00:05:23+09:00
Railsチュートリアル9章攻略の第一歩を踏み出す。【セッション永続化編】
はじめに
9章の解説は私にとってあまりに複雑だったので
ほんの少しでもわかりやすく理解できるよう、9章の中でもセッションの永続化に関する内容を簡単にまとめてみました。対象者
Railsチュートリアルにてログイン機能を実装しており、尚且つ9章を終えたが理解度が足りていないと感じている方。
あと私自身。内容
セッション永続化機能の実装。
簡単に言うと、ブラウザを閉じてもログインを保持することができる機能です。
見たらわかるめちゃくちゃ便利な機能って事で絶対に物にしたいです。ほぼ箇条書きでまとめてあります。
キリの良いポイントまで一旦まとめてから、コードの記述を行っています。実装手順
前置きが長くなりましたが、進めていきます。
ログイン保持機能を実装する手順を簡単にまとめると以下のようになります。1.永続セッション作成の準備
2.永続セッション作成
3.上記を利用して、ログインユーザーのログインを保持する
4.永続セッションからログアウトできるようにする
5.上記機能実装により発生するバグを修正する
では、より詳しくまとめていきます。1.永続セッション作成の準備
手順1の、永続セッション作成の準備についてです。
ログインを保持するには永続セッションが必要ですが、さらにその永続セッションを作成するための準備が必要になるので、手順1ではその準備を行っていきます。
永続セッション作成準備でやること
●ユーザーを記憶するための、記憶トークンなる物を作成する。
●その記憶トークンをハッシュ化し、データベースに保存させる機能を実装する。
1-1.トークンの作成。
●Userモデル内に、クラスメソッドとして生成。
➡︎Userモデル内のdigestメソッドが、ユーザーオブジェクトを必要としないため。
●トークンを生成するメソッドを定義。
➡︎new_tokenとする。
●トークンは、長いランダムな文字列にすること。
➡︎SecureRandomモジュールにある、urlsage_base64メソッドを使用。
➡︎22文字のランダムな文字列が生成される。
●このトークンを使って、記憶トークンを生成する。1-2.記憶トークンをユーザーと関連付け、記憶ダイジェストに保存されるようにする。
●記憶ダイジェスト属性をUserモデルに追加する。
➡︎カラム名はremember_digest、データ型は文字列とする。
➡︎コマンドでDBへその変更を反映。
➡︎このデータにユーザーが触れることはないので、インデックスは不要。
➡︎記憶トークンは、このカラムに保存されることになる。
●記憶トークン属性を追加する。
➡︎DBには保存しないような属性を追加する。
➡︎attr_accessorを使い、仮想の属性とする。
➡︎Userモデル内に記述する。
➡︎属性名はremember_tokenとする。
➡︎この記憶トークンの値が、記憶ダイジェストカラムに追加されることになる。
●ユーザーを記憶し、記憶ダイジェストに保存するメソッドを定義する。
➡︎Userモデル内に作成。
➡︎メソッド名をrememberとする。
➡︎1-1で生成したランダムな文字列のトークン(new_token)を、remember_token属性として取得する処理の記述。
→remember_token属性にはselfを付け、属性として正しく認識させる。
→付けないとローカル変数として定義される。
➡︎記憶トークンを、記憶ダイジェストに保存する処理の記述。
→update_attributeを使う。
→remember_tokenで取得した値をハッシュ化して、記憶ダイジェストに保存する。
→パスワード実装の際に使用した、digestメソッドによりハッシュ化する。
➡︎このメソッドは、バリデーションを素通りさせる。
➡︎これにより、ユーザーと関連付けられた記憶トークンが、記憶ダイジェストに保存されるようになった。
→例えば、user.rememberのようにして、このメソッドを使ってユーザーを呼び出すと、呼び出したユーザーのremember_digestカラムに、ランダムな文字列で生成された記憶トークンが保存されるようになる。1-3.準備完了
●ユーザーをレシーバにrememberメソッドを呼び出すと、そのユーザーの記憶ダイジェストにランダムな文字列が保存されるようになりました。
●以上の機能を利用して、永続セッションを作成していきます。
models/user.rb#1-1 def User.new_token #Userを付することでクラスメソッドであることを明示的に示している。 SecureRandom.urlsafe_base64 end1-2$ rails g migration add_remember_digest_to_users remember_digest:string $ rails db:migratemodels/user.rb#1-2 attr_accessor :remember_token #仮想の記憶トークン属性を作成。models/user.rb#1-2 #記憶トークンをハッシュ化し、呼び出したユーザーの記憶ダイジェストに保存する。 def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end
2.永続セッション作成
次に、手順2の永続セッションの作成です。
手順1で作成したメソッドなどは、ここで使用していきます。
永続セッション作成でやること
・ユーザーIDを暗号化する
・暗号化したユーザーIDと記憶トークンを、ブラウザの永続cookiesに保存する。
・以上の機能を実装するために、cookiesメソッドを使用する。
2-1.記憶トークンをcookiesに保存する。
●cookiesメソッドを使用することで保存できる。
➡︎記憶トークンをcookieに保存する。
→cookies.permanent[:remember_token] = remember_token
→cookiesメソッドはsessionメソッド同様、ハッシュで扱う。
→permanentメソッドを使用すれば、cookiesの有効期限が20年後に設定される。
→permanentメソッドにより永続化完了。2-2.ユーザーIDを暗号化してcookiesに保存する。
●cookiesメソッドでcookieにユーザーを保存する。
➡︎cookies[:user_id] = user.id
➡︎ただこのままでは、IDが生のテキストのまま保存されているので、奪い取られる可能性がある。
➡︎署名付きcookieを使い、ユーザーIDを暗号化する。
→cookies.signed[:user_id] = user.id
→sigedメソッド付与により暗号化される。
➡︎さらに、cookieも永続化してあげる。
→cookies.permanent.signed[:user_id] = user.id
→permanentとsignedはメソッドチェーンで繋ぐ。
➡︎cookiesを設定したことで、cookiesからユーザーを取得できるようになった。
→例えば、User.find_by(id: cookies.signed[:user_id])
→ちなみに、cookies.signed[:user_id]では自動でユーザーIDの暗号化が解除される。2-3.cookiesに保存された記憶トークンが、ユーザーの記憶ダイジェストと一致することを確認する。
●この2つの一致を確認することにより、後々ログインを可能とする。
➡︎パスワードで例えると、passwordとpassword_digestの比較と同じ。
●bcryptを使って確認。
➡︎secure_passwordのソースコードを確認してみる。
→BCrypt::Password.new(password_digest) == unencrypted_passwordというコードを参考にする。
→bcryptでハッシュ化されたパスワードを比較する際にこのコードが使われているので、これを利用してやるという動機がある。
→BCrypt::Password.new(remember_digest) == remember_tokenとして、当てはめてみる。
→しかしこれだと、暗号化されたパスワードとトークンを直接比較している。
→bcryptでは記憶ダイジェストは復号化されないので、直接比較はできない。
➡︎bcrypt gemのソースコードの方を詳しく見てみる。
→すると、==の部分がis_password?という論理値メソッドで再定義されている。
→なので、BCrypt::Password.new(remember_digest).is_password?(remember_token)として比較してあげることにする。
➡︎これにより、記憶トークンと記憶ダイジェストを比較してあげることにする。2-4.記憶トークンと記憶ダイジェストを比較するためのメソッドの定義をする。(2-3より)
●Userモデルの中に作成。
●メソッド名はauthenticated?とする。
●渡された記憶トークンが、記憶ダイジェストと一致したらtrueを返す処理を記述する。
●ここでの記憶トークンとは、cookiesに保存されている物で、仮想の属性であるアクセサとは別物。
※このメソッドは、手順3の3-2で使用。2-5.永続セッションの作成が完了しました。
#2-1.記憶トークンをcookiesに保存する。 cookies.permanent[:remember_token] = user.remember_token#2-2.ユーザーIDを暗号化してcookiesに保存する cookies.permanent.signed[:user_id] = user.idmodels/user.rb#2-4.記憶トークンと記憶ダイジェストを比較するためのメソッドの定義をする def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end
3.ログインしたユーザーのログインを保持する
続きまして、手順3に移ります。
手順2で作成した永続セッションの動作を利用して、ログインを保持する機能を実装していきます。3-1.ログイン時にユーザーを保持させる。
●ログインを保持するためのメソッドを定義する。
➡︎メソッド名をrememberとし、ユーザーを引数に取るヘルパーメソッドを定義。
→Sessionsヘルパー内に記述。
→記憶トークンを生成し、DBの記憶ダイジェストに保存させる処理を記述。
→これに関しては、Usersモデルで定義したrememberメソッドを呼び出せばいい。
→暗号化したユーザーIDを、永続cookiesに保存する処理を記述。
→ユーザーの記憶トークンを、永続cookiesに保存する処理を記述。
➡︎そして、アクション内でこのメソッドを呼び出すと…
→渡されたユーザーの記憶トークンが記憶ダイジェストにが保存される。
→暗号化したユーザーIDと記憶トークンが、永続cookiesに保存される。
→その結果、ログインが保持されることになる。
●最後に、Sessionsコントローラ内でrememberヘルパーメソッドを呼び出す。
・ログインの保持は、ログインした時に実行されるのでlog_inメソッドとセットで定義してあげる。
●これでログインしたユーザーは、ブラウザに正しく記憶されるようになったので、ログイン保持機能の実装は完了した。3-2.問題点
●しかしながら現状では、このログイン保持機能には、問題点が1つ存在。
●それは、Sessionsヘルパー内で定義されているcurrent_user内にあり、このメソッド内でsessionメソッドによる一時セッションしか扱っていないため、ログイン保持機能が正常に動作しないこと。3-3.解決策
●永続セッションを動作させる場合には、ユーザーの取得方法に関して条件がある。
➡︎session[:user_id]が存在する場合は、一時セッションからユーザーを取得する必要がある。
→sessionにユーザーIDが存在するかどうかの条件式を記述。
➡︎それ以外の場合は、cookies[:user_id]からユーザーを取得する必要がある。
→cookiesに、暗号化されたユーザーIDが存在するかどうかの条件式を記述。
→存在する場合、cookiesに保存されたIDと一致するIDを持つユーザーを取得する処理を記述。
→「上記のユーザーが存在し」、かつ、「そのユーザーの記憶トークンと記憶ダイジェストが一致する(2-4で定義したメソッドを使用)」かどうかの条件式を記述。
→どちらの条件も満たすならば、そのユーザーでログインする処理を記述。
→ログイン中のユーザーをインスタンス変数で返す処理を記述。
➡︎リファクタリング
→ローカル変数を用いて、同じ記述をしている部分を修正。
→重複している記述を、条件式内でローカル変数として一気に定義してあげる。
→ユーザーIDのセッションが存在する場合=if(user_id = session[:user_id])とする。
→ユーザーIDのクッキーが存在する場合=if(user_id = cookies.signed[:user_id])とする。
→ローカル変数user_idを利用する。
●これにより、ログインしたユーザーが正しく記憶されるようになった。
helpers/sessions_helper.rb#3-1.ログイン時にユーザーを保持させるメソッド。 def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token endcontrollers/sessions_controller.rbdef create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user remember user #3-1.ログイン時に、ログインを保持する。 redirect_to user else flash.now[:danger] = "失敗" render 'new' end endhelpers/sessions_helper.rb#3-2.記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) #sessionを持つユーザーの存在。 @current_user ||= User.find_by(id: user_id ) elsif (user_id = cookies.signed[:user_id]) #暗号化IDを持つユーザーの存在。 user = User.find_by(id: user_id ) #ユーザーの取得 if user && user.authenticated?(cookies[:remember_token]) #ユーザーの存在と、記憶トークンと記憶ダイジェストが一致するかどうか(2-4で定義)。 log_in user @current_user = user end end end
4.永続セッションからログアウトできるようにする
4-1.完全にログアウトできるように、ユーザーを忘れるためのメソッドを定義する必要がある。
●Userモデルに追加。
●メソッド名をforgetとして定義する。
●DBの記憶ダイジェストをnilで更新する処理を記述をする。
➡︎update_attributeを使用。
●ユーザーのログイン情報を破棄する処理を記述する。
●永続セッションを終了させる準備が完了。4-2.永続的セッションを破棄するメソッドを記述。
●Sessionsヘルパー内に追加。
●メソッド名をforgetとする。
●渡されたユーザーのログイン情報を破棄する処理を記述。
●ブラウザに保存されている、ユーザーIDのクッキーを削除する処理を記述。
●ブラウザに保存されている、記憶トークンのクッキーを削除する処理を記述。4-3.現在のユーザーのログアウトする。
●log_outヘルパーメソッド内で、永続セッションを破棄するメソッドを呼び出す。
➡︎ログイン中のユーザーを破棄するので、引数はcurrent_user。4-4.全てのテストスイートがGREENになる。
models/user.rb#4-1.ユーザーのログイン情報を破棄する。 def forget update_attribute(:remember_digest, nil) endhelpers/sessions_helper.rb#4-2.ユーザーの永続的セッションを破棄する。 def forget(user) user.forget #4-1のメソッドを呼び出してる。 cookies.delete(:user_id) cookies.delete(:rememeber_token) endhelpers/sessions_helper.rb#4-3.現在のユーザーをログアウトする。 def log_out forget(current_user) #4-2のメソッドを呼び出している。 session.delete(:user_id) @current_user = nil end
5.上記機能実装により発生するバグを修正する
まず、上記機能実装によるバグが2つ存在するので、その詳細から挙げていく。
【1つ目】:複数のタブを開きながらログインしているユーザーが、一方のタブでログアウトをして、もう一つのタブで再度ログアウトしようとするとエラーになる。
【原因】:ログアウトリンクをクリックすると、current_userがnilになり、log_outメソッド内のforget(current_user)が失敗してしまうため。
【解決策】:ユーザーが、ログイン中の場合にのみログアウトさせる。
【2つ目】:複数のブラウザ(FirefoxやChromeなど)でログインしているユーザーが、一方のブラウザ(Firefoxとする)ではログアウトして、一方(Chromeとする)のブラウザではログアウトはせずに、ブラウザを終了させてから再度同じブラウザで同じページへ行くと、問題が発生する。
【具体的なエラーの挙動】
●Firefoxでログアウトした時。
➡︎user.forgetメソッドによってremember_digestがnilになる。
➡︎この時同じタイミングで、log_outメソッドによりユーザーIDが削除され、current_userメソッド内の2つの条件文がfalseになる。
➡︎結果、current_userメソッドの戻り値は期待通りnilになる。
➡︎ここまでは正常に動作している。
●問題は、Chromeをログアウトせずに閉じた時。
➡︎current_userメソッド内のsession[:user_id]はnilになる(セッションの有効期限切れによる)。
➡︎しかし、cookiesはブラウザに残り続けている。
➡︎そのためChromeを再起動して、アプリにアクセスすると、DBからそのユーザーを見つけることができてしまうため、セキュリティ上の問題が存在する。
➡︎クッキーが存在するせいで、current_userメソッド内のuser && user.authenticated?(cookies[:remember_token])が評価される。
➡︎結果的に、記憶ダイジェストがnilなので、user.authenticated?(cookies[:remember_token])の部分でエラーが発生する。【原因】
●Firefoxログアウト時に、remember_digestが削除されるにもかかわらず、Chromeアクセス時、BCrypt::Password.new(remember_didest).is_password?(remember_token)が実行されてしまうため。
➡︎要するにremember_digestがnilとなるため、bcrypt内で例外が発生してしまう。【解決策】
●remember_digestが存在しない場合は例外ではなく、falseを返す処理を、authenticated?メソッドに追加する。
5-1.まず、上記2つのエラーをキャッチするテストを記述していく。
【1つ目の問題についてのテスト】
●2番目のウィンドウでログアウトをクリックするユーザーを検証する。
➡︎integration/users_login_test.rb内のログアウトテストに追加で記述。
➡︎1個目のログアウトの後に、もう一つログアウトテストを追加する。
→delete logout_path
➡︎current_userがいないので、2度目の呼び出しはエラーが発生するため、テストスイートはRED。
●テストをパスさせるために機能を追加していく。
➡︎ユーザーがログイン中の場合のみ、ログアウトできるようにする機能を記述する。
→Sessionsコントローラ内に処理を記述。
→logged_in?メソッド(ログインしているユーザーがいればtrueを返すメソッド)が、trueの場合にのみ、log_outを呼び出すようにする。
→destroyアクション内に処理を記述。
→「 log_out if logged_in? 」
➡︎テストスイートがパス。GREEN。【2つ目の問題についてのテスト】
●ダイジェストが存在しない場合のauthenticated?をテストする。
➡︎Userモデルで直接テストする。
➡︎記憶ダイジェストを持たないユーザーを用意する。
➡︎authenticated?メソッドを呼び出す。(渡されたトークンが、ダイジェストと一致したらtrueを返すメソッド)
➡︎assert_not @user.authenticated?('')
→結果がfalseならパス。
→記憶トークンが使われる前に記憶ダイジェストでエラーになるので、記憶トークン部分の値は何でもいい。
➡︎BCrypt::Password.new(nil)でエラーが発生するため、テストスイートはRED。●テストをパスさせる。
➡︎authenticated?メソッドで、記憶ダイジェストがnilの場合はfalseを返すようにする処理を記述。
→return false if remember_digest.nil?
→returnキーワードによって、記憶ダイジェストがnilの場合、即座にメソッドを終了させる。
→処理を途中で終わらせるのに用いられるテクニック。
➡︎テストスイートGREEN。
integration/users_login_test.rbtest "login with valid information followed by logout" do get login_path post login_path, params: { session: { email: @user.email, password: 'password' }} assert is_logged_in? assert_redirected_to @user follow_redirect! assert_template 'users/show' assert_select "a[href=?]", login_path, count: 0 assert_select "a[href=?]", logout_path assert_select "a[href=?]", user_path(@user) delete logout_path assert_not is_logged_in? assert_redirected_to root_url delete logout_path #5-1.2度目のログアウトを検証(RED)。 follow_redirect! assert_select "a[href=?]", login_path assert_select "a[href=?]", logout_path, count: 0 assert_select "a[href=?]", user_path(@user), count: 0 endcontrollers/sessions_controller.rbdef destroy log_out if logged_in? #5-1.ログインしているユーザーがいる場合のみログアウトさせる。 redirect_to root_url endtest/models/user_test.rb#5-1.authenticated?メソッドの結果がfalseならテストをパスする。 test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?('') endmodels/user.rbdef authenticated?(remember_token) return false if remember_digest.nil? #5-1.記憶ダイジェストがnilの場合はfalseを返す。 BCrypt::Password.new(remember_digest).is_password?(remember_token) end
最後に
以上で、セッションの永続化機能の実装が完了しました。
あとは、この機能をユーザーが選択可能にする[Remember me]チェックボックス機能を実装すれば、9章の内容が完了します。
チェックボックス編に関しては、別でまた記事を投稿しようと思います。参考