- 投稿日:2019-12-03T23:02:54+09:00
B - ROT N
過去問やっていきます
AtCoder Beginner Contest 146
Aは流石にやらなくていいかな問題
https://atcoder.jp/contests/abc146/tasks/abc146_b
1回目
回答
N = gets.to_i S = gets.chomp alf = ['A','B','C','D','E','F','G', 'H','I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V','W','X','Y','Z'] result = '' for c in S.chars x = alf.index(c) x = ( x + N ) % 26 result += alf[x] end puts result結果
2回目
回答
N = gets.to_i S = gets.chomp N.times do |i| S = S.tr('A-Z', 'B-ZA') end puts S結果
感想
流石にAからZ書かなくてもいい方法あるよな〜と思ったけど、書き方が分からなかった。。。
メモリ使用量9割くらい減ったな。。。追記
コメントを頂いて、もう一回やりました!ありがとうございます!
あと alphabet の頭をとったつもりが alf となっていたのめっちゃ恥ずかしい
(alfabet はポーランド語らしい。へえ〜)回答
N = gets.to_i S = gets.chomp a = [*"A".."Z"] puts S.tr(a.to_s, a.rotate(N).to_s)追記2
to_s
では配列の文字を繋げられないとのご指摘を頂きました。
今回はたまたま上手くいきましたが、
本来の想定では以下のコードの挙動になる予定でしたので、
追記しておきます!N = gets.to_i S = gets.chomp a = [*"A".."Z"] puts S.tr(a.join, a.rotate(N).join)結果
- 投稿日:2019-12-03T22:34:57+09:00
railsアプリではクラスインスタンス変数の注意する
クラスインスタンス変数の注意
右辺の実行結果をキャッシュするために以下の手法が使われる事があると思います。
1回目のメソッド実行時に実行結果をインスタンス変数@foo
でキャッシュしておいて、2回目のメソッド実行時にはインスタンス変数の中身を返しています。Class Foo def @foo ||= bar end endインスタンスメソッドの場合、インスタンス変数はFooクラスのオブジェクト毎に保持されます。
railsアプリ内のコードでこれをクラスメソッド内で使用する場合は注意が必要です。Class Foo self.def @foo ||= bar end endクラスメソッド内でインスタンス変数はClassクラスのオブジェクト毎に保持され、
Classクラスのオブジェクトはrailsアプリケーション起動時から同じものを使い続けます。
その為、DB問合せ結果をキャッシュしてしまったりすると、アプリケーション起動後DBに初めて問い合わせした値を保持し続けてしまう為、その後別のリクエストでDBを更新しても、反映されない問題が発生します。
- 投稿日:2019-12-03T22:28:01+09:00
rubyワンライナーでwebサーバーを立てるTips
mockサーバとかサッと作成したい時に使うTipsです。
rubyコマンドを使ってワンライナーでwebサーバーを起動します。手順
1. レスポンスボディのファイルを用意します。
response.rb"Hello"
2.
response.rb
ファイルが配置されているパスでruby -run -e httpd ./response.rb -p 3333
実行します。$ ls -la response.rb $ ruby -run -e httpd ./response.rb -p 3333-> これでwebサーバーが起動します。
3. 確認してみる
curlで確認してみます。
ブラウザでもlocalhost:3333
にアクサスしたらresponse.rb
の中身が返ってきます。$ curl localhost:3333 "hello" ::1 - - [03/Dec/2019:20:18:36 JST] "GET / HTTP/1.1" 200 8 - -> /
- 投稿日:2019-12-03T20:59:28+09:00
#Rails OR #Ruby + ActiveSupport の String#in_time_zone で 2月29日 30日 31日 が3月にずれるのだが
Rubyとか言語使用に関わる、深い事情ありの話かと思った。
Date.new を利用した方が良いかな。
require 'active_support/core_ext' '2019-2-29'.in_time_zone('Tokyo').to_s # => "2019-03-01 00:00:00 +0900" '2019-2-30'.in_time_zone('Tokyo').to_s # => "2019-03-02 00:00:00 +0900" '2019-2-31'.in_time_zone('Tokyo').to_s # => "2019-03-03 00:00:00 +0900" '2019-2-32'.in_time_zone('Tokyo').to_s # ArgumentError: argument out of range Date.new(2019, 02, 28).in_time_zone('Tokyo') # => Thu, 28 Feb 2019 00:00:00 JST +09:00 Date.new(2019, 02, 29) # ArgumentError: invalid dateOriginal by Github issue
- 投稿日:2019-12-03T18:43:02+09:00
さくらVPSでCentOS7 11.RailsプロジェクトをGitで共同開発
はじめに
自由にテスト出来るLinuxのサーバーがほしくて、さくらVPSで構築してみました。
順次手順をアップしていく予定です。前回インストールしたRuby On Railsを共同開発できるようにGitで管理したいと思います。
目次
- 申し込み
- CentOS7インストール
- SSH接続
- Apache・PHPインストール
- MariaDBインストール
- FTP接続
- sftp接続
- phpMyAdminインストール
- 環境のバックアップ
- Ruby On Railsインストール
- RailsプロジェクトをGitで共同開発
11.RailsプロジェクトをGitで共同開発
前回、Ruby On Railsインストール時に作成したテスト用プロジェクトHelloWorkdを共同開発できるようにGitで管理したいと思います。
共同開発用のマシンは、ローカルのWindows10のマシンにします。
構成は、こんな感じです。
共有リポジトリは、GitHub等を使う方法もありますが、今回はプロジェクトの有るさくらVPSサーバー内に作成します。Gitでは、開発マシンのソースを直接本番ソースにアップするのではなく、一度共有リポジトリにアップしたものを本番ソースから取りに行く形になります。
本番ソースも開発と同様ローカルポジトリですので、開発側がアップした変更は手動で本番ソースに取り込む必要があります。
これだと不便ですので、共有リポジトリが変更を受け取った時自動的に本番ソースが取りに行くように設定します。さくらVPSにGitインストール
インストール
$ sudo yum install git-allインストール後の設定
ユーザ名とメールアドレスを設定します
$ git config --global user.name "sakura" $ git config --global user.email sakura@kogueisya.comgitの出力をカラーリング
$ git config --global color.ui autoディフォルトエディタの設定
$ git config --global core.editor vimエイリアス設定(「checkout」を「co」に設定)
$ git config --global alias.co checkout確認
$ git config user.name sakura $ git config -l user.name=sakura user.email=sakura@kougeisya.com color.ui=auto core.editor=vim alias.co=checkout core.repositoryformatversion=0 core.filemode=true core.bare=false core.logallrefupdates=true $ cd $ cat .gitconfig [user] name = sakura email = sakura@kougeisya.com [color] ui = auto [core] editor = vim [alias] co = checkout共有リポジトリ(ベアリポジトリ)作成
さくらVPSサーバーに共有リポジトリを作成します。
共有リポジトリ用ディレクトリ作成
/optの中に「プロジェクト名.git」というフォルダを作成します。
$ sudo mkdir -p /opt/helloworld.git/-pは/optフォルダがなければ作成します。
所有者変更
フォルダの所有者を 「4.Apache・PHPインストール」で作成したWebコンテンツ操作用のグループ(webadmin)にします。
$sudo chown root:webadmin /opt/helloworld.git/ $sudo chmod 2775 /opt/helloworld.git/ -Rベアリポジトリを作成
$ cd /opt/helloworld.git $ git init --bare --share Initialized empty shared Git repository in /opt/helloworld.git/---bareは、ベアリポジトリを作成するオプション
--shareは、ベアリポジトリを複数のユーザによって共有可能にするオプション本番ソース
前回「10.Ruby On Railsインストール」で作成したHelloWorldプロジェクトにローカルリポジトリを作成し、リモートリポジトリにプッシュします。
リポジトリのセットアップ
$ cd /var/www/app/HelloWorld $ git init Reinitialized existing Git repository in /var/www/app/HelloWorld/.git/除外ファイル指定
railsコマンド実行時に作成される.gitignoreファイルにリポジトリから除外するファイルを指定するためのルールが記載されています。
このファイルに以下を追加します。$ vi .gitignore# Ignore other unneeded files. doc/ *.swp *~ .project .DS_Store .idea .secretファイルをインデックスに追加(addコマンド)
プロジェクトのファイルをコミット待ちの変更が格納される「ステージングエリア」という一種の待機場所に追加
$ git add .確認
ステージングエリアにあるファイルのリスト表示$ git statusコミット
ローカルリポジトリへの変更反映
$ git commit -m "Initalize repository"コミットメッセージの履歴参照
$ git log commit d3f7327c74c7175b75ca7d78f4a1cd576f5b6d9a Author: sakura <sakura@kougeisya.com> Date: Mon Dec 2 15:49:37 2019 +0900 Initialize repositoryリモートリポジトリにプッシュ
リモートリポジトリのアドレスに「origin」という名前を付けて記録
(「origin」にしたのは、pushやpullコマンドは実行時にリモートリポジトリ名を省略するとoriginという名前を使用する為)$ git remote add origin /opt/helloworld.gitリポジトリをプッシュ
$ git push -u origin master Counting objects: 6674, done. Delta compression using up to 2 threads. Compressing objects: 100% (6272/6272), done. Writing objects: 100% (6674/6674), 29.86 MiB | 8.65 MiB/s, done. Total 6674 (delta 909), reused 0 (delta 0) To /opt/helloworld.git * [new branch] master -> master Branch master set up to track remote branch master from origin.-uオプションは、「git push -u origin master」とすると次回から「git push」だけでpushしてくれます。
本番ソース反映の自動化
本番ソースも開発と同様ローカルポジトリですので、開発側がアップした変更は手動で本番ソースに取り込む必要があります。
これだと不便ですので、共有リポジトリが変更を受け取った時自動的に本番ソースが取りに行くように自動化します。自動化にはフックを使います。
フックは、Gitでコマンドを実行する直前もしくは実行後に特定のスクリプトを実行する為の仕組みです。フックの種類(サーバーサイド)
フック 概要 pre-receive クライアントからpushを受け取った直後に起動。
更新内容の確認が可能。post-receive push内容の適用終了後起動。
通知を行う場合に便利。update pushによる更新対称ブランチごとに起動。 共有リポジトリの中にある「hooks」の中に、フック名のスクリプトファイルを作成することで実現できます。
今回は、開発のローカルリポジトリからプッシュされ、その内容が適用された後に本番のローカルリポジトリからプルするスクリプトを実行したいので、「/opt/helloworld.git/hokks/post-receive」というスクリプトファイルを作成します。
スクリプトの内容は、本番ソースのドキュメントルートに移動し、publlを実行します。$ vi /opt/helloworld.git/hooks/post-receive#!/bin/sh cd /var/www/app/HelloWorld/ git --git-dir=.git pullスクリプトファイルを他のユーザから実行できるようにパーミッションを設定します。
$ chmod 755 /opt/helloworld.git/hooks/post-receive以上でサーバー側の設定は完了です。
開発用ローカルマシン(Windows10)の設定
各ツールのインストール
Ruby、Rails、Git等をインストールしていきます。
Rubyインストール
下記サイトからダウンロードします。
最新版の「Ruby+Devkit 2.6.5-1 (x64)」をダウンロードしました。
https://rubyinstaller.org/downloads/
ダウンロードしたファイルを実行すると下記の画面が表示されます。
「◎I accept the License」を選択し[Next>]をクリックします。
下記画面が表示されたら、チェックボックス3つともチェックをいれ、[Install]をクリックします。
下記画面が表示されたらそのまま[Next>]をクリックします。
下記画面のようにインストールが開始されますので、終わるまでしばらく待ちます。
インストールが終了すると、下記画面が表示されます。
そのまま[Finish]をクリックすると画面が閉じ、インストールが完了します。
引き続き下記の画面が開きます。
1,2,3と入力して[Enter]キーを押すとインストールが開始されます。
インストールが完了すると下記画面のように「Which components shall be installed? If unsure press ENTER []」と表示されますので、[ENTER]キーを押すと画面が閉じます。
Railsに必要なgemをインストール
SQLite3インストール
コマンドプロンプトを開き、以下を実行します。
> ridk exec pacman -S mingw-w64-x86_64-sqlite3下記画面のように「インストールを行いますか? [Y/n]」と聞いてきますので「Y」を入力します。
プロンプトに戻ったらインストール終了です。
引き続き以下のコマンドを実行します。> gem install sqlite3 --platform rubynokogiriインストール
コマンドプロンプトで以下を実行します。
> ridk exec pacman -S mingw-w64-x86_64-libxslt下記画面のように「インストールを行いますか? [Y/n]」と聞いてきますので「Y」を入力します。
プロンプトに戻ったらインストール終了です。
引き続き以下のコマンドを実行します。> gem install nokogiri --platform ruby -- --use-system-librariesNode.jsインストール
下記サイトからダウンロードします。
https://nodejs.org/ja/download/
「LTS推奨版」「Windows Installer (.msi)」の「64-bit」をダウンロードしました。
ダウンロードしたファイルを実行すると下記の画面が表示されますので[Next]をクリックします。
下記画面が表示されたら「□I accept the terms in the License Agreement」にチェックを入れ[Next]をクリックします。
下記画面が表示されますので、そのまま[Next]をクリック。
下記画面が表示されたら「Automatically install the ・・・」にチェックをいれて[Next]をクリック。
下記画面の[Install]クリックで、インストールが開始されます。
インストールが終わると下記画面が開きますので[Finish]をクリックすると画面が閉じます。
引き続き下記の画面が開きます。
「継続するには何かキーを押してください ...」と2回表示されますので都度[Enter]キーを押してください。
下記のように「Tyoe ENTER to ext:」と表示されたら完了です。
[Enter]キーを押すと画面が閉じます。
Bundlerインストール
コマンプロンプトを開き下記のコマンドを実行します。
> gem install bundlerGitインストール
GUIでGitが使えるTortoiseGitを使います。
画面のキャプチャは取っていませんでしたm(__)mGit For Windows
下記サイトの[Download]ボタンよりダウンロード
ダウンロードしたファイルを実行し、画面に従いインストール
https://gitforwindows.org/
TortoiseGit
下記サイトのfro 64-bit Windowsの「Download TortoiseGit 2.9.0-64.bit(~19.5Mib)」をクリックしてダウンロード
ダウンロードしたファイルを実行し、画面に従いインストール
https://tortoisegit.org/download/
メニュー等を日本語に設定
下記サイトからLanguage PacksのJapanese 64Bitの[Setup]をクリックしてダウンロード
ダウンロードしたファイルを実行し、画面に従いインストール
https://tortoisegit.org/download/
インストール後、デスクトップの適当な場所で右クリックし表示されるメニューから[TortoisGit]-[設定]を選択
開いた画面の[全般][TotoiseGit][言語(Language)]より「日本語(日本)」を選択、[OK]ボタンをクリック
以上でWIndows10の設定は完了です。クローンを作成
共有リポジトリからクローンを作成します。
作成場所は、C:\Prj\HelloWorldにします。TortoiseGitを利用してクローン作製
デスクトップ等適当な場所で右クリックし、表示されたメニューから[Gitクローン (複製)...]を選択します。
下記の設定画面が開きます
URLには以下の値を設定します。
"ssh://" + ユーザー名 + "@" + さくらVPSサーバーのアドレス + ":" + SSHポート番号 + "共有リポジトリディレクトリ"ディレクトリは、クローンを作成するフォルダ名を設定します。
ここでは、「C:\Prj\HelloWorld」にしました。
フォルダは無ければ作成されます。Putty鍵のロードにチェックを入れ、[...]をクリックし、秘密鍵を選択します。
ここで選択する秘密鍵は、「 7.sftp接続」の設定時に作成した秘密鍵ファイルです。[OK]をクリックすると、秘密鍵のパスフレーズを聞いてきますので、「 7.sftp接続」で設定したパスフレーズを入力し[OK]をクリックします。
動作確認
サーバーを起動してみる
コマンドプロンプトを起動し、ディレクトリの移動。
> cd \Prj\HelloWorldとりあえずサーバーを立ち上げてみたら、railsコマンドがみつからないので、「bundle install」しろと出ました。
>bundle exec rails s bundler: command not found: rails Install missing gem executables with `bundle install`「bundle install」を実行すると、派手にエラーが出ました(T0T)
>bundle install Fetching gem metadata from https://rubygems.org/. Retrying fetcher due to error (2/4): Bundler::PermissionError There was an error while trying to read from `C:/Users/sugi/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/mini_portile2`. It is likely that you need to grant read permissions for that path. . Retrying fetcher due to error (3/4): Bundler::PermissionError There was an error while trying to read from `C:/Users/sugi/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/thread_safe`. It is likely that you need to grant read permissions for that path. . Retrying fetcher due to error (4/4): Bundler::PermissionError There was an error while trying to read from `C:/Users/sugi/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/websocket-driver`. It is likely that you need to grant read permissions for that path. . There was an error while trying to read from `C:/Users/sugi/.bundle/cache/compact_index/rubygems.org.443.29b0360b937aa4d161703e6160654e47/info/globalid`. It is likely that you need to grant read permissions for that path.どうもファイルの読み取り権限に問題があるようです。
調べてもよくわからなかったので、ちと乱暴ですが管理者としてコマンドプロンプトを実行してみました。
再度「bundle install」したら、無事動きました。
サーバー起動に再チャレンジ。
>bundle exec rails server -b 0.0.0.0 => Booting Puma => Rails 5.2.4 application starting in development => Run `rails server -h` for more startup options Please add the following to your Gemfile to avoid polling for changes: gem 'wdm', '>= 0.1.0' if Gem.win_platform? Please add the following to your Gemfile to avoid polling for changes: gem 'wdm', '>= 0.1.0' if Gem.win_platform? *** 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.1 (ruby 2.6.5-p114), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stopコントローラーを作成しプッシュしてみる
コントローラを作成
>bundle exec rails generate controller hello Please add the following to your Gemfile to avoid polling for changes: gem 'wdm', '>= 0.1.0' if Gem.win_platform? Please add the following to your Gemfile to avoid polling for changes: gem 'wdm', '>= 0.1.0' if Gem.win_platform? create app/controllers/hello_controller.rb invoke erb create app/views/hello invoke test_unit create test/controllers/hello_controller_test.rb invoke helper create app/helpers/hello_helper.rb invoke test_unit invoke assets invoke coffee create app/assets/javascripts/hello.coffee invoke scss create app/assets/stylesheets/hello.scssapp/controllers/hello_controller.rbにアクションメソッドindexを追加
class HelloController < ApplicationController def index render plain: 'こんにちは、世界!' end endconfig/routes.rbにルーティング設定
Rails.application.routes.draw do # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html get 'hello/index', to: 'hello#index' endローカルサーバーを起動し動作確認
> bundle exec rails sプッシュ
変更したファイルをさくらVPSサーバの共有リポジトリにプッシュします。
その前に、ローカルリポジトリに変更をコミットします。HelloWorldフォルダの上で右クリックし、表示されたメニューより[Gitコミット(C)->"master"...]を選択。
下記のようにコミット画面が表示されますので、「メッセージ」を入力し、追加ファイルにチェックを入れ[コミット]ボタンをクリックします。
下記画面のようにコミットが実行され、成功と表示されるとコミットは成功です。
コミットが成功したら、共有リポジトリへプッシュします。
上記画面の[プッシュ]ボタンをクリックすると下記のプッシュ画面が開きます。
[OK]をクリックし、サーバーの共有リポジトリにプッシュします。
さくらVPSサーバーでの動作確認
Git確認
共有リポジトリへのプッシュ確認
$ cd /opt/helloworld.git $ git log commit 941e28b8e44d6dfcbe2ba2bdf5189cba76b92b38 Author: Kouichi Sugimoto <sugi@kougeisya.com> Date: Tue Dec 3 18:06:20 2019 +0900 Windowsからのプッシュテスト commit d3f7327c74c7175b75ca7d78f4a1cd576f5b6d9a Author: sugi <sugi@kougeisya.com> Date: Mon Dec 2 15:49:37 2019 +0900 Initialize repositoryプッシュはうまくいっているようです。
開発リポジトリから共有リポジトリにプッシュがあると、自動的に本番サーバーがプルするように設定していました。
これが動いているかログをチェックします。$ cd /var/www/app/HelloWorld $ git log commit 941e28b8e44d6dfcbe2ba2bdf5189cba76b92b38 Author: Kouichi Sugimoto <sugi@kougeisya.com> Date: Tue Dec 3 18:06:20 2019 +0900 Windowsからのプッシュテスト commit d3f7327c74c7175b75ca7d78f4a1cd576f5b6d9a Author: sugi <sugi@kougeisya.com> Date: Mon Dec 2 15:49:37 2019 +0900 Initialize repositoryhello_controller.rbが出来ているかチェック
$ cd /var/www/app/HelloWorld/app/controllers $ ls application_controller.rb concerns hello_controller.rb動作確認
本番ソースのサーバーを起動します。
$cd /var/www/app/HelloWorld $ rails server -b 0.0.0.0 => Booting Puma => Rails 5.2.4 application starting in development => Run `rails server -h` for more startup options Puma starting in single mode... * Version 3.12.1 (ruby 2.6.5-p114), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://0.0.0.0:3000 Use Ctrl-C to stopブラウザで確認
無事動きました。
これでGitを使った共同開発の環境が出来ました。次回
次回は、Pythonのインストールの予定です。
- 投稿日:2019-12-03T18:19:50+09:00
rails-tutorial第9章
8章補足。
sessionを変数と考えていたが、正確には
sessionメソッドを使うと、ユーザーIDなどをブラウザに一時的に保存できるということである。発展的なログイン機構
sessionだけを使ったログインだとサーバーやブラウザを閉じてしまうと、またログインが必要となる。そこを改善できないものだろうか。
ベターなのは、ユーザーの意思で、期限のあるセッションか、永続的なセッションかを選べるようにするといい。
cookiesとsessionの違い。
超わかりやすい説明。
cookiesは診察券、session idは整理番号と考えるとわかりやすい。
cookiesはクライアント側に保存されるので、その情報から、以前何を買ったとかショッピングカートに入れたとかがわかる。session idもcookieに保存されるのだが、session idはブラウザとサーバーの通信状態を呼ぶから、
別のページに移動したり、別のデバイスから入ろうとすると、その時点でsession idはsessionというハッシュから削除されてしまう。なので、たとえcookieからsessionidを盗み出せたとしても、別のページに移動したり、別のデバイスから入ろうとすると、その時点でsession idはsessionというハッシュから削除されてしまうという理由から、そんなsession idはそもそもハッシュに保存されていませんよーとなってしまう。
ただ、cookieは診察券みたいなものなので、「あ、前回はこの病気を見てもらったんですねー」というようなことができる。
以上!!!!!!!!!!!!!!!!!!!!!!
sessionはサーバとブラウザが相互にやり取りをして、どちらかが切れたら終了というものだった。
cookieの実装方法
cookieはクライアント側に記憶トークン(rememberトークンともいう)を付与し、それをパスワードダイジェストのように、ハッシュ化したものをDBに保存する。
そのため、まずは記憶トークンをハッシュ化したものを保存する場所をDBに作っていこう。
$ rails generate migration add_remember_digest_to_users remember_digest:string
ここでも、rails g migration add_追加するカラム名toテーブル名 カラム名:データ型
という風に指定してあげると、rails側で以下のようなファイルを勝手に作ってくれる。db/migrate/[timestamp]_add_remember_digest_to_users.rbclass AddRememberDigestToUsers < ActiveRecord::Migration[5.0] def change add_column :users, :remember_digest, :string end endこれを確認したら rails db:migrate
記憶トークンに使われるランダムな文字列をどうやって作るか?
Ruby標準ライブラリのSecureRandomモジュールにあるurlsafe_base64メソッドなら、この用途にぴったり合いそうです3。このメソッドは、A–Z、a–z、0–9、"-"、"_"のいずれかの文字 (64種類) からなる長さ22のランダムな文字列を返します (64種類なのでbase64と呼ばれています)。典型的なbase64の文字列は、次のようなものです。
$ rails console >> SecureRandom.urlsafe_base64 => "q5lt38hQDc_959PVoo6b7A"SecureRandomクラスのクラスメソッドってことだよね。
この文字列をクライアント側に送って、さらに、この文字列をハッシュ化したものをDBに保存する。
トークン生成用のメソッドの定義
app/models/user.rbclass User < ApplicationRecord before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end endランダムなトークンを作るためだけに、インスタンスを生成するのは勿体無い。
なので、user.rbにクラスメソッドとして定義すれば良い。ただ、今の状態だと、記憶トークンdigestを参照することができるが、記憶トークンの平文を参照することができない。
記憶トークンの平文は、password_digest実装時のpasswordやpassword_confirmationのような仮想的な属性である。これを実装するにはどうすればいいだろうか?
実は上記のような仮想的な属性はゲッター、セッターの実装ができる。一時的に保存できるがDBに保存はされない。
これは、
attr_accessor :remember_tokenとすることで、自動的にゲッターとセッターを実装してくれる。
言い換えると、attr_accessorはメソッドを定義するメソッドと言える。attr_accessor :remember_tokenを実装
app/models/user.rbclass User < ApplicationRecord attr_accessor :remember_token before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end end次は上記の最後に定義されているrememberメソッドを見ていこう
rememberメソッドはユーザーがチェックボックスにチェックを入れてログインをした。
その時に呼び出されるメソッドである。ここで注意点
rememberはインスタンスメソッド。これが呼び出されてる時は必ず呼び出し元がいるということ。で、self.remember_tokenのselfには rememberメソッドの呼び出し元が代入される。
selfの省略について
update_attributeはself.update_attributeの省略形である。
しかし、一つ前のself.remember_tokenのselfは省略してはいけない。どのようなルールがあるのだろうか?
省略してはいけないのは、
def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end1文目は、selfを省略してしまうと、remember_tokenというローカル変数にUser.new_tokenを代入するという意味になってしまう。
つまり、代入文であり、代入文の左辺だった時はselfが必要。
update_attributeはメソッド(インスタンスメソッド)であることが明白なので、selfを省略してもOK
次は、
cookieからsessionの状態を復元する機能を実装しよう。
ログインした時にはユーザー自身が入力したemailからユーザーインスタンスをfindし、入力したパスワードを元に@user.authenticateをして認証することができた。
しかし、cookieの場合は、emailが存在しないので、どうやってユーザーインスタンスを引っ張ってくるかが課題になる。
これを解決するために、署名付きユーザーidというものを使う。
これは、cookieを送る時に、@user.idを暗号化したものを一緒に送る。
これを、元の@user.idに復号化してあげて、
そこから得たuser.idを使って、find_byしてインスタンスを引っ張ってくる。
で、authenticateメソッドはパスワードを比較するためのメソッドなので、記憶トークンには使えない。
なので、自分で定義する必要がある。app/models/user.rbclass User < ApplicationRecord attr_accessor :remember_token before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end endrememberメソッドをsession_controllerのcreateアクションに実装する。
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user remember user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out redirect_to root_url end endこれは、ユーザーがメールとパスワードを入れてログインしたら、記憶トークンをハッシュ化したものをDBに保存するよーって処理。
ただ、ここでremember userというように引数を取っていることにお気づきだろうか?
実はこのメソッドはsession_helperに定義された別のメソッドだったのだ!!!
実はこのremember(user)メソッドで記憶トークンをクライアント側に送るなどの処理もしている。
このメソッドを定義していこう
app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # ユーザーのセッションを永続的にする def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end # 現在ログインしているユーザーを返す (いる場合) def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end # ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end # 現在のユーザーをログアウトする def log_out session.delete(:user_id) @current_user = nil end endsessionメソッドと同様、:user_idをキーにして、user.idを代入できる。
この場合、ユーザーIDが生のテキストとしてcookieに保存される。署名付きcookieを使うためには、cookies.signedメソッドを使用する。
cookieをブラウザに保存する前に暗号化を行う。じゃあ、結局どうやってsession状態を復元するんだよ。
current_userメソッドを変える。
app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # ユーザーのセッションを永続的にする def remember(user) user.remember cookies.permanent.signed[:user_id] = user.id cookies.permanent[:remember_token] = user.remember_token end # 記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end # ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end # 現在のユーザーをログアウトする def log_out session.delete(:user_id) @current_user = nil end end簡単にいうと、
sessionでログインできればそれでログインし、
できなければ、cookieを使ってログインしてというメソッド。if (user_id = session[:user_id])は
user_idにsession[:user_id]を代入した結果、値が存在すればという条件式になる。elsif (user_id = cookies.signed[:user_id])
また、このコードのsignedは暗号化された文字列を復号化する役割がある。
つまり、signedは暗号化もできるし、復号化することもできる。ちなみに
if user && user.authenticated?(cookies[:remember_token])のcookies[:remember_token]は、クライアント側に保存されているもの。
つまり、DBに保存されているユーザーインスタンスの記憶トークンのハッシュ化された値と、クライアント側に保存されている記憶トークンをハッシュ化したものを比較してくれている。
もし、if文もelsif文も失敗したら、nilが返ってくるという仕様。
そのため、logged_in?メソッドをそのまま使える。この時点でテストは失敗している。
ユーザーを忘れる。
これはrememberメソッド、remember(user)メソッドの全く逆のことを実装すれば良い
app/models/user.rbclass User < ApplicationRecord attr_accessor :remember_token before_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end # ランダムなトークンを返す def User.new_token SecureRandom.urlsafe_base64 end # 永続セッションのためにユーザーをデータベースに記憶する def remember self.remember_token = User.new_token update_attribute(:remember_digest, User.digest(remember_token)) end # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) BCrypt::Password.new(remember_digest).is_password?(remember_token) end # ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end endこれはrememberメソッドと対になるメソッド。
DBの値をnilにしたので、
次は焼いたクッキーを消すメソッドを実装しよう。app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end . . . # 永続的セッションを破棄する def forget(user) user.forget cookies.delete(:user_id) cookies.delete(:remember_token) end # 現在のユーザーをログアウトする def log_out forget(current_user) session.delete(:user_id) @current_user = nil end endforget(user)はremember(user)の対になるメソッド。
また、log_outメソッドに forget(current_user)を追加しないと、cookieを使ってまたログインできてしまう。さっきテストで失敗してしまったのは、ログアウトしたらログアウトパスが本来0個のはずなのに、cookieをつかったログインが成功してしまい、1つ発見されてしまったからだろう。
実はこれだけじゃあ実装は終わらないぜ
目立たないバグ潰し
二つのログイン済みのタブがあり、どちらか一方をログアウトさせ、もう片方もログアウトさせようとするとエラーが起こる。
これは1回目でcurrent_userがなくなり、2回目で、nilにforgetメソッドを呼び出そうとしているため、NoMethodErrorが起こってしまうからだ。これを解決するには、log_outメソッドを使えるのはlog_inしている時のみという条件をつける。
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController . . . def destroy log_out if logged_in? redirect_to root_url end endこれで1つ目のバグは解決。
2つ目のバグを解決しよう!!!
cookieの暴走問題
1つはSafari、もう1つはChromeでログインする。
そうするとどちらにもcookieが付与された状態になる。これで、Chromeのタブでログアウトを実行。
さらに、Safariのタブを消してしまう。
それで、Safariでもう一度アプリのページを開こうとするとエラーになってしまう。
これは、Chromeのログアウトの時点でDBのremember_digestはnilになっている。
そしてSafariのcookie情報でcurrent_userを見つけようとするもDBの値がnilになっているので見つけられず、例外を出してエラーを出すようになってしまうかららしい。bcryptによるもの。じゃあ、どうやって解決する?
まずは回帰バグを防ぐためにテストコードを書こう。
test/models/user_test.rbrequire 'test_helper' class UserTest < ActiveSupport::TestCase def setup @user = User.new(name: "Example User", email: "user@example.com", password: "foobar", password_confirmation: "foobar") end . . . test "authenticated? should return false for a user with nil digest" do assert_not @user.authenticated?('') end end最後のテストは、authenticated?()メソッドの引数にnilや空文字を入れたらfalseを返すでしょ?というテストである。ちなみにcookieの暴走バグはfalseすら返さず例外を出しているために起きている。
テスト結果は以下。
ERROR["test_authenticated?_should_return_false_for_a_user_with_nil_digest", UserTest, 0.47229603000005227] test_authenticated?_should_return_false_for_a_user_with_nil_digest#UserTest (0.47s) BCrypt::Errors::InvalidHash: BCrypt::Errors::InvalidHash: invalid hash app/models/user.rb:32:in `new' app/models/user.rb:32:in `authenticated?' test/models/user_test.rb:70:in `block in <class:UserTest>' 21/21: [===========================================================================================] 100% Time: 00:00:00, Time: 00:00:00 Finished in 0.57426s 21 tests, 50 assertions, 0 failures, 1 errors, 0 skipsちなみに、failuresは期待された値にならなかった時。
errorsは期待された値とか関係なく、例外などが出た時に表示される。cookieの暴走バグ解決法
これの解決方法は、remember_digestがnilの時はbcryptを実行せずに、falseを返してあげれば良い
app/models/user.rbclass User < ApplicationRecord . . . # 渡されたトークンがダイジェストと一致したらtrueを返す def authenticated?(remember_token) return false if remember_digest.nil? BCrypt::Password.new(remember_digest).is_password?(remember_token) end # ユーザーのログイン情報を破棄する def forget update_attribute(:remember_digest, nil) end endこれで、cookieの暴走バグを解決できる。
returnを実行されると、それがメソッドの戻り値になるので、以降のメソッド処理は実行されなくなる。チェックボックスの実装
まずはチェックボックスをログインフォームに実装しよう
app/views/sessions/new.html.erb<% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :remember_me, class: "checkbox inline" do %> <%= f.check_box :remember_me %> <span>Remember me on this computer</span> <% end %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>チェックボックスを実装すると、
params[:session][:remember_me]の値が、チェックされてる時は'1'
チェックされてない時は'0'となる。これを利用していこう。
if params[:session][:remember_me] == '1' remember(user) else forget(user) endこのように条件分岐していけばいいのだが、
これを三項演算子を使うとスマートに実装することができる。app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user params[:session][:remember_me] == '1' ? remember(user) : forget(user) redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out if logged_in? redirect_to root_url end end基本的にチェックボックスにチェックをつけないとcookieは焼かれないが、一応万全を期すためにforget()メソッドを呼び出している。
Remember meのテストを書こう。
まずテスト環境でログインしたユーザーを作るためにtest_helperにメソッドを定義していく
test/test_helper.rbENV['RAILS_ENV'] ||= 'test' . . . class ActiveSupport::TestCase fixtures :all # テストユーザーがログイン中の場合にtrueを返す def is_logged_in? !session[:user_id].nil? end # テストユーザーとしてログインする def log_in_as(user) session[:user_id] = user.id end end class ActionDispatch::IntegrationTest # テストユーザーとしてログインする def log_in_as(user, password: 'password', remember_me: '1') post login_path, params: { session: { email: user.email, password: password, remember_me: remember_me } } end end下のクラス定義されてるやつはなんぞや??
統合テストは基本的にブラウザでできることができるようなテスト、
だからこのようにいちいち情報を入力してログインしてもらう必要がある。
そのためメソッドを分けて定義しているつまり、上のlog_in_asはケーステスト用のメソッド。
下のlog_in_asは統合テスト用のメソッドとなっている。ちなみに password: 'password'と remember_me: '1'はデフォルト値として設定されている。
チェックボックスの統合テストをしよう
test/integration/users_login_test.rbrequire 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "login with remembering" do log_in_as(@user, remember_me: '1') assert_not_empty cookies['remember_token'] end test "login without remembering" do # クッキーを保存してログイン log_in_as(@user, remember_me: '1') delete logout_path # クッキーを削除してログイン log_in_as(@user, remember_me: '0') assert_empty cookies['remember_token'] end endここではテスト通る。
raiseを理解する。
raiseはテストの途中で例外を発生させる機能。
app/helpers/sessions_helper.rbmodule SessionsHelper . . . # 記憶トークンcookieに対応するユーザーを返す def current_user if (user_id = session[:user_id]) @current_user ||= User.find_by(id: user_id) elsif (user_id = cookies.signed[:user_id]) raise # テストがパスすれば、この部分がテストされていないことがわかる user = User.find_by(id: user_id) if user && user.authenticated?(cookies[:remember_token]) log_in user @current_user = user end end end . . . endつまり、テストがパスしてしまうと、raise以下のテストが実行されていないことがわかる。
これは問題。
解決するには、raise以下の使うテストを追加してあげれば良い
本当にここテストされてるかなー?って思ったらraiseを使ってみよう
メンテナンスモードについて
開発者側からはアプリに入れてクライアント側からは入れなくしたい時は、メンテナンスモードをonにする。
heroku maintenance:on
これを解除するには、
$ heroku maintenance:off
- 投稿日:2019-12-03T17:47:42+09:00
Macでrbenvが使えない!【binutilsの悪夢シリーズ】
以下のことがしたかった
- rbenvでruby 2.6.5をインストール
- 結論としては,binutilsが色々と悪さをする
環境
- Mac Catalina 10.15.1
普通に入れようとした記録
$ brew install rbenv ... 略 $ rbenv install 2.6.5 Downloading openssl-1.1.1d.tar.gz... -> https://dqw8nmjcqpjn7.cloudfront.net/1e3a91bc1f9dfce01af26026f856e064eab4c8ee0a8f457b5ae30b40b8b711f2 Installing openssl-1.1.1d... BUILD FAILED (OS X 10.15.1 using ruby-build 20191124) Inspect or clean up the working tree at /var/folders/df/0q1vdcy17j32kzq9xlb5zgg40000gn/T/ruby-build.20191203170740.4037.sKb82m Results logged to /var/folders/df/0q1vdcy17j32kzq9xlb5zgg40000gn/T/ruby-build.20191203170740.4037.log Last 10 log lines: _s_server_main in s_server.o "_verify_stateless_cookie_callback", referenced from: _s_server_main in s_server.o "_wait_for_async", referenced from: _s_client_main in s_client.o _sv_body in s_server.o ld: symbol(s) not found for architecture x86_64 clang: error: linker command failed with exit code 1 (use -v to see invocation) make[1]: *** [apps/openssl] Error 1 make: *** [all] Error 2はて,Rubyを入れる前のOpenSSLのビルドでコケているみたいだ...
解決策
ログが
/var/folders/df/0q1vdcy17j32kzq9xlb5zgg40000gn/T/ruby-build.20191203170740.4037.log
にあるようなので覗いてみる${LDCMD:-clang} -arch x86_64 -O3 -Wall -L. -Wl,-search_paths_first -L/Users/hiragi/.rbenv/versions/2.6.5/lib \ -o apps/openssl apps/asn1pars.o apps/ca.o apps/ciphers.o apps/cms.o apps/crl.o apps/crl2p7.o apps/dgst.o apps/dhparam.o apps/dsa.o apps/dsaparam.o apps/ec.o apps/ecparam.o apps/enc.o apps/engine.o apps/errstr.o apps/gendsa.o apps/genpkey.o apps/genrsa.o apps/nseq.o apps/ocsp.o apps/openssl.o apps/passwd.o apps/pkcs12.o apps/pkcs7.o apps/pkcs8.o apps/pkey.o apps/pkeyparam.o apps/pkeyutl.o apps/prime.o apps/rand.o apps/rehash.o apps/req.o apps/rsa.o apps/rsautl.o apps/s_client.o apps/s_server.o apps/s_time.o apps/sess_id.o apps/smime.o apps/speed.o apps/spkac.o apps/srp.o apps/storeutl.o apps/ts.o apps/verify.o apps/version.o apps/x509.o \ apps/libapps.a -lssl -lcrypto ld: warning: directory not found for option '-L/Users/hiragi/.rbenv/versions/2.6.5/lib' ld: warning: ignoring file apps/libapps.a, building for macOS-x86_64 but attempting to link with file built for unknown-unsupported file format ( 0x21 0x3C 0x61 0x72 0x63 0x68 0x3E 0x0A 0x2F 0x20 0x20 0x20 0x20 0x20 0x20 0x20 ) Undefined symbols for architecture x86_64:
#{LDCMD:-clang}
はたぶんld
のこと,コンパイルはちゃんと通ってるけど,リンクするときにうまく行っていない.
で,apps/libapps.a
とかいう多分shared-library的なもののフォーマットが合っていないといった旨のエラー.それともうちょっと上側を見てみると,
ar: creating libcrypto.a ranlib -c libcrypto.a || echo Never mind. ranlib: invalid option -- c
invalid option -- c
て...なんか別のコマンドが実行されている...?
ranlib error
とかでググってみると,以下のページが出たhttps://tetsuok.hatenablog.com/entry/2012/05/07/054356
なるほど,なんかOS標準の
ar
とranlib
を使わないとなにか別のものができてしまう模様...
そして更に調べるとhttps://naoyat.hatenablog.jp/entry/2012/01/31/033312
こんな記事も見つけた,どうやらbrewの
binutils
を入れていると,そいつの依存でついてくるar
とranlib
が勝手にパスを通してしまうらしい.... こんなのわかるわけ無いやろ(半ギレ)
そういえば以前もなにか自前でビルドしようとしたときになんとなくbinutils
を消していた気がする...恐るべしbinutils
....https://qiita.com/nagomiso/items/dc6021beb72d09f2128f
似たような記事がありました,ただ原因と問題が発生する場所が違いすぎて原因にたどり着きにくい...
本格的にLinuxを動かすことを検討しないと,こういう問題が起きる毎に無駄に時間を使ってしまう.
- 投稿日:2019-12-03T15:04:44+09:00
Excelって名前しか知らないPCに不慣れな感覚派22歳プログラミング完全未経験者がRubyを習得するまでの軌跡。Part4
【HTML-CSS編】
はじめはpart2,3のように用語の説明や文型をまとめようとおもったがprogateの説明だけで完成されており、理解することが可能。最低限大事なことだけまとめる。
1.HTMLとCSSってなんなのか CSSは色、大きさ、形などデザインに関するプログラミング HTMLが素材でCSSが職人。
2.CSSはHTMLとは別にファイルを作って記述する。
3.プロパティはCSSの機能 ex.color.font-size,background-color,width,heightやってて感じたこと
HTML-CSSはRubyと比べると内容自体はすごく簡単で意味がわからないということはほぼないと想う。ただRubyが詩だとするとHTML-CSSは単文といった感じで1文1文で完結していて面白みはあまりない。
- 投稿日:2019-12-03T12:54:31+09:00
Rails 6.0.x標準で Ajax+(jQuery+Partial) でHTML部分更新する世界一シンプルなサンプル
TL;DR (長い! 3行で!)
↓ の記事をRails 6.0.1 版に書き直したものです.
Rails 5.x標準で Ajax+(jQuery+Partial) でHTML部分更新する世界一シンプルなサンプルFront EndがWebpackに変わっており,
・ Coffee Script → JavaScript への変更
・ jQuery導入方法の変更
が大きな変更点です.動作確認環境
$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 14.04.6 LTS ← 古いLTSなのでわりとダメ,できれば最新LTS使ってください Release: 14.04 Codename: trusty $ ruby --version ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux] $ rails --version Rails 6.0.1 $ yarn --version 1.16.0 $ nvm --version 0.35.1 $ node --version v12.13.1NOTICE
node
のversionが古いと(LTSじゃないと?),$ rails new
の途中で ↓ のようなErrorが出ます.error get-caller-file@2.0.5: The engine "node" is incompatible with this module. Expected version "6.* || 8.* || >= 10.*". Got "9.10.1" error Found incompatible module.これ以降,webpackが使えなくなります.
(error Command "webpack" not found.
)
$ rails new
自体は "Webpacker successfully installed" のメッセージがでて正常終了してるように見えてしまうため気付けない...ざっくりシーケンス
だいたい ↓ のようなシーケンスを実現します.
Client-Server間でSession張って双方向通信とかそういうところはRails Frameworkが全部やってくれます.Front End : UserがInputしたTextをAjaxでBack End側にPost ↓ Back End : PostされたTextとPartialを使って部分的に差し替えるHTMLをRendering ↓ Front End : JavaScriptでAjaxのCallback(部分的に差し替えるHTML)を受け取る ↓ Front End : jQueryでHTMLの部分差し替え
Rails 6 でFront EndがWebpack化されたことで,Rails 5 に比べてReactなども導入しやすくなっています.そのため,Back Endから返すCallbackはpre-renderしたHTMLでなく,Jsonなどでデータを返して,Front End側でrenderする,という方法もあります.
Project初期化 + Default動作確認
$ rails new ajax-testでRails Projectを初期化します.
$ cd ajax-test
でRailsのrootに移動して,$ rails serverでサーバを起動して,
http://localhost:3000/
にアクセスして "Yay! You're on Rails!" のDefaultページが出たらOKです.Static Page作成
Ajax動作のベースになるStatic Page部分を実装します.
Controller
$ rails generate controller AjaxTestでControllerのTemplateを作成後,Action(Method)を追加します.
app/controller/ajax_test_controller.rbclass AjaxTestController < ApplicationController def top # NOP. end def update # TBD. end end
top
: Topページ表示用,特に処理はありません.
update
: Ajaxリクエストを受ける用,実装はあとで追加します.Route Config
ControllerのActionに繋げるためのRoute定義を追加します.
config/routes.rbget 'ajax_test/top', to: 'ajax_test#top', as: 'ajax_test_top' post 'ajax_test/update', to: 'ajax_test#update', as: 'ajax_test_update'
GET
: Topページ表示用.
POST
: Ajaxリクエスト用.View
ajax/test/top
にアクセスしたときに表示するViewを追加します.app/views/ajax_test/top.html.erb<div id="request_ajax_update" > <%= form_tag(ajax_test_update_path, method: :post, remote: true) { %> <%= text_field :data, :text %> <%= submit_tag 'Post AJAX' %> <% } %> </div> <hr> <div id="updated_by_ajax" > DEFAULT </div> <hr>大きく2つのブロックだけです.
・
<div id="request_ajax_update" >
Ajax RequestをPostするFormを持つブロック.
Userが入力したTextをparams[:data][:text]
に詰めて,
ajax_test_update_path
( =/ajax_test/update
) にPOSTします.・
<div id="updated_by_ajax" >
Ajax Callbackを受けてHTMLの部分更新をするブロック.Static Page動作確認
この時点で,
http://localhost:3000/ajax_test/top
にアクセスすると,↓ のようなページが表示されると思います.
ただし,Ajaxの実装がまだ無いので,
Post AJAX
ボタンをClickしても見た目上は何も起こりませんが,$ rails server
が動いているConsoleにText Fieldに入れた文字列がParametersとして通知されているのを確認できると思います.Parameters: {"data"=>{"text"=>"Hello World !"}, "commit"=>"Post AJAX"}Ajax実装
いままでに作ったStatic PageにAjax実装を組み込んでInteractiveな機能を追加します.
Partial View
部分的に更新するHTMLの部品を追加します.
app/views/ajax_test/_ajax_partial.html.erb<div> Callback Msg = <%= results[:message] %> </div>このPartialで
<div id="updated_by_ajax" >
の中身を差し替えます.Controller Action
さきほど作ったControllerの
update
の中身を実装します.app/controller/ajax_test_controller.rbclass AjaxTestController < ApplicationController def top # NOP. end def update post_text = params[:data][:text] results = { :message => post_text } render partial: 'ajax_partial', locals: { :results => results } end endControllerに渡ってきた Parameters の中に格納されているUserが入力したTextを使ってPartialをrenderしています.
Partialのファイル名_ajax_partial.html.erb
がajax_partial
で使えるあたりはRailsの規約に沿っています.ここで,
res = render_to_string partial: 'ajax_partial', locals: { :results => results } puts resのようなCodeを書いておくとPartialをrenderしたときの実際のOutputが見れます.
<div> Callback Msg = Hello World ! </div>JavaScript実装
Rails 6 からFront EndにWebpackが標準で使われることになり,Coffee ScriptがDefaultで使われなくなっています.
かわりに,
app/javascript/packs/application.js
のようなWebpackに対応したJavaScriptのDir構成に変わっています.
(Rails 5 でWebpack使っていた人にはおなじみの構成かも)ViewにJavaScriptのEntry Pointを追加
ViewのHTMLにJavaScriptの読み込み部分を追加します.
app/views/ajax_test/top.html.erb<div id="request_ajax_update" > <%= form_tag(ajax_test_update_path, method: :post, remote: true) { %> <%= text_field :data, :text %> <%= submit_tag 'Post AJAX' %> <% } %> </div> <hr> <div id="updated_by_ajax" > DEFAULT </div> <hr> <!-- ↓ 追加 --> <%= javascript_pack_tag 'ajax_test' %> <script> register_callback(); </script>
<%= javascript_pack_tag 'ajax_test' %>
はapp/javascript/packs/
以下に置いてあるajax_test
というファイル(の中身)を読み込む,という指示です.
実際にはWebpackがひとかたまりの.js
にしてしまうので,この名前のファイルを読み込んでいるわけではありません.その後,
<script></script>
タグでregister_callback()
という関数を呼び出します.
この関数の実装は次で追加します.JavaScript本体の実装
Webpack用Dir構成に沿って,Viewから呼び出せるように ↓ のJavaScriptを追加します.
app/javascript/packs/ajax_test.jsimport * as $ from "jquery"; function register_callback() { $("#request_ajax_update").on( "ajax:complete", function(event) { var res = event.detail[0].response $('#updated_by_ajax').html(res) } ); } window.register_callback = register_callback;Viewから呼び出す
register_callback()
関数の中で,AjaxリクエストをPOSTする#request_ajax_update
Tagにajax:complete
のCallback関数を登録しています.Viewから呼び出せるようにするため,
window.register_callback = register_callbcak;
の行でwindow
の名前空間(global)にexportしています.
注) この方法は動作はしますがあまり行儀よくないはずです...ただし,このままでTopページから
Post AJAX
をClickしても何も起こりません.
ChromeのDeveloper Tool等でConsole Logをみると,Uncaught Error: Cannot find module 'jquery' at webpackMissingModule (ajax_test.js:1) at Module../app/javascript/packs/ajax_test.js (ajax_test.js:1)のようなError Logがでていて,
jquery
の名前解決ができてないことがわかります.jQueryを使えるようにする
Rails 5 から引き続きRails標準ではjQueryはサポートされていないため,WebpackからjQueryを使えるようにします.
$ yarn add jqueryのCommandでjqueryをInstallします.
必要かもしれない追加
"rails6 + jquery" とかのキーワードでぐぐると,↓ の2つの追加も必要,と出てきますが,手元の環境だと必要ありませんでした.
RailsのVersionによるのか,他の環境によるのか...ref: https://www.botreetechnologies.com/blog/introducing-jquery-in-rails-6-using-webpacker
app/javascript/packs/application.js// 他のrequireがいろいろ... require("jquery") // 追加config/webpack/environment.jsconst { environment } = require('@rails/webpacker') // 追加 const webpack = require('webpack') environment.plugins.prepend('Provide', new webpack.ProvidePlugin({ $: 'jquery/src/jquery', jQuery: 'jquery/src/jquery' }) ) module.exports = environmentAjax動作確認
これで必要な変更はすべてです.
http://localhost:3000/ajax_test/top
にアクセスして,Text Fieldになにか文字列を入力してPost AJAX
ボタンを押したら,DEFAULT
の文字列が更新されて ↓ のような画面になっていれば成功です.ChromeのDeveloper ToolなどでDOM構造を見てみると,↓ のようになっていて,
<div id="updated_by_ajax" >
の中身が差し替わっているのが見えます.おわり
Rails 6 でAjaxを使ったシーケンスを一本通すまでをできる限りDefaultのままで実現してみました.
環境依存でたまたまうまく動いている部分などあるかもしれません.もし何かおかしな点がありましたら教えていただけると助かります.---///
- 投稿日:2019-12-03T11:18:01+09:00
CC0ライセンスの空白PDFファイル
はじめに
ライセンス的に問題のない「空白PDFファイル」が欲しかったのですが、すぐに見つからなかったので作りました。リポジトリはこちらです。
https://github.com/kaityo256/blank_pdf
空白PDFが欲しい
あるPDFファイルと別のPDFを
pdftk
で結合することを考えます。この時、二つ目のPDFを、いわゆる「奇数ページ起こし」にしたいことがあります。つまり、1つ目のファイル(A)が奇数ページ(例えば3ページ)で終わっており、二つ目のファイル(B)が偶数ページ(例えば4ページ)ある時、そのまま結合して両面印刷するとA1,A2 | A3,B1 | B2, B3| B4みたいになって読みづらくなります。これを、空白を一枚挟んで
A1,A2 | A3,空白 | B1, B2| B3, B4としたくなりますね。もし空白のPDF
blank.pdf
があれば、pdftk
を使ってpdftk A.pdf blank.pdf B.pdf cat output output.pdf
でおしまいです。というわけで、PDF
blank.pdf
が欲しいわけですが・・・、空白のPDFってどうやって作るんだ?で、ググって見ると、Rubyを使う方法も出てくるんですが、オンラインツール使う方法とか、Adobe Acrobatを使う方法とかが多い感じです。英語でググっても、なんかツールを使えみたいなのが多くて、うーん、という感じ。
たまに「白紙PDFをどうぞ」的なサイトもあるのですが、ライセンスが不明なのと、ダウンロードしてみると、ファイルに余計な情報が入っていたりして微妙だったりします。
というわけで、こちらでも紹介されている、Ruby + Prawnで空白PDFを生成することにします1。
Ruby + Prawnで空白PDF
といっても、コードはこれだけ。
blank_pdf.rbrequire "prawn" doc = Prawn::Document.new(page_size: "A4") doc.render_file "blank.pdf"できあがるPDFも
%PDF-1.3 %???? 1 0 obj << /Creator <feff0050007200610077006e> /Producer <feff0050007200610077006e> >> endobj 2 0 obj << /Type /Catalog /Pages 3 0 R >> endobj 3 0 obj << /Type /Pages /Count 1 /Kids [5 0 R] >> endobj 4 0 obj << /Length 4 >> stream q Q endstream endobj 5 0 obj << /Type /Page /Parent 3 0 R /MediaBox [0 0 595.28 841.89] /CropBox [0 0 595.28 841.89] /BleedBox [0 0 595.28 841.89] /TrimBox [0 0 595.28 841.89] /ArtBox [0 0 595.28 841.89] /Contents 4 0 R /Resources << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI] >> >> endobj xref 0 6 0000000000 65535 f 0000000015 00000 n 0000000109 00000 n 0000000158 00000 n 0000000215 00000 n 0000000268 00000 n trailer << /Size 6 /Root 2 0 R /Info 1 0 R >> startxref 540 %%EOFと最低限で、余計な情報が入っていなくていい感じです。ちなみに二行目の「????」は、このファイルがバイナリであることを知らせるための非ASCIIデータです。一応このファイルをCC0ライセンスでリポジトリに置いておくので、必要な人はダウンロードして使ってください。
まとめ
なんか空白PDFが必要な人が、その都度スクリプトその他で作ってる気がしてアレな気がしますね。
pdftk
に白紙PDFを生成する機能があると一番幸せな気がしますが、作ってプルリクするのがいいのかな。
最初、このページ見て「RubyとlibHaruを使っている」ところまで見て、自分でPrawnで書いてから、後で見返してみたら「追記」にPrawnを使う方法が書いてあることに気が付きました。 ↩
- 投稿日:2019-12-03T10:24:41+09:00
よくあるデータ出力機能を二段階に分けて作ったら機能の汎用性がとても高くなったはなし
この記事は食べログ Advent Calendar 2019 7日目の記事です。
よくあるデータ出力機能をふたつに分けて作ったら、機能の汎用性がとても高くなったはなしをします。おそらくよくあるデータ出力処理
ふだん、データ出力ってどのように作りますか?
自分は、こんな感じで
レコードの絞り込みとデータの取得を同時に行なって
1レコードずつ出力したい順に情報を並べ替えてファイルに出力
することが多いです。おそらくよくあるデータ出力実装.rbrecords = Employee.select(:age, :first_name, :last_name).where(job: :engineer) CSV.open("engineer.csv") do |csv| csv << [:first_name, :last_name, :age] # headers records.each do |record| csv << [record.first_name, record.last_name ,record.age] end endいつものようにしたけれど、コードが長くなっちゃった
その日は出力したい情報の種類が多いデータ出力を作っていました。
いつものように作ってみたけれど、データの取得先だけで5種類もあってコードがとても長い、、、
なんとかしたい。
そのときふと
出力したいレコードの絞り込みと、出力したいデータの取得処理を
分けてみたらどうだろうと思いました。レコードの絞り込みと、データの取得をわけてみた
そうして出来上がったのがこちらです。
欲しいレコードの検索条件と
知りたいデータの項目名を定義したファイルを渡します。
id name tel address station url reviews_url yoyaku するとまず、レコード絞り込み機能が
レコードの特定条件で絞り込み検索をして出力対象レコードのidのみを出力します。
id name tel address station url reviews_url yoyaku 100 200 300 続いて、このファイルがデータ取得機能に渡されて
id name tel address station url reviews_url yoyaku 100 カフェ 03-0000-0000 恵比寿南 恵比寿駅 /100/ /100/dtlrvwlst なし 200 ラーメン屋 03-0000-0000 恵比寿南 恵比寿駅 /200/ /200/dtlrvwlst なし 300 イタ飯屋 03-0000-0000 恵比寿南 恵比寿駅 /300/ /300/dtlrvwlst あり idをキーにデータを取ってきて、項目名列に対応した情報を付加してくれます。
表の項目名は、データ取得先ごと色分けしてみました。
データ取得先はDBだったりAPIだったりするので、単純に結合もできず
特にAPIはデータを取るときにページングや並列処理をしたりもするのでわりと手間がかかります。データ取得機能の汎用性がとてもよかった
長いコードを整理するために機能を分割したのですが
思いがけず、データ取得機能の汎用性がとても高いことに気づきました。たとえば
レコードを絞り込む条件は違うけど、出力したいデータが似ている機能を新しく作りたいときは、
レコードの絞り込み部分だけ作れば、あとはデータ取得機能に渡すだけ!あるいは
既にレコードは特定できていて、詳細情報を知りたいとき
id列を埋めたファイルをデータ取得機能に渡すだけ!渡すだけ!これはなかなか汎用性があるぞう。
単純なデータ出力ではあまり恩恵はなさそうですが
今回ご紹介したように、データ出力のために用意する種類が多くて手間がかかるケースで
それを利用したいシーンが複数あると、たくさんの恩恵を受けられそうです。ドメイン間は疎結合のまま、情報満載なデータ出力ができそうな予感
今回はひとつのドメインのデータ取得機能だけ作りましたが、別のドメインにもデータ取得機能を作り
知りたいデータの項目定義ファイルに、いろんなドメインのデータ取得機能を旅させることで
情報満載なCSVファイルを作ることもできそうです。感想
ふだん何気なくやっていたことを少し変えてみたら新しい発見につながったので
今後も何気ないことを改めて考えてみて、いろんな発見をしていきたいですね!明日は @hiroteru_ さんによる「UIViewの角丸と影のおはなし」です。
お楽しみに!!最後までご覧いただきありがとうございました!
おまけ
データ出力機能を作るときに役立ちそうなコード集です。
最近のExcelってUTF-8を理解できるんですって(ただしBOMつきに限る)
最近のExcelは、UTF-8でもBOMつきであれば文字化けせずにcsvファイルを開いてくれるようです。
BOMつきUTF-8のCSVを作るFile.write(filepath, "\xEF\xBB\xBF") # excelで直接UTF-8を開けるようにするためにBOMをつける CSV.open(filepath, 'a') do |csv| # 追加書き込みモードで開くこれで「ファイルをインポート」機能とお別れできます。
この方法で作成したCSVを読み込むときは、BOMついてるかもよ。と教えてあげる必要があります。
BOMつきUTF-8のCSVを読み込むCSV.open(filepath, encoding: "BOM|UTF-8") do |csv|データ取得機能で活躍した遅延ロード
不必要なデータまで取得していると、処理に無駄な時間がかかってしまいます。
1つの取得先のデータだけあれば済むときに、5つの取得先からデータを取得したくないですよね。
こんなときに遅延ロードが大活躍しました。class Hoge def initialize(id) @id = id end def name data.name end def tel data.tel end private def data return @data if defined? @data # メモ化 @data = HogeData.select(:name, :tel).where(id: @id).take end end header = csv.headers # 出力したいと指定されている項目名を取得 -> ["name", "tel"] hoge = Hoge.new(id) header.each do |column| hoge.public_send(column.to_sym) # 初回にnameが呼び出されたときに、中のdataメソッドがデータを取得してくれる # 続いて tel が呼び出されたときは、メモ化された@dataを見るので改めてデータ取得はしない endこのように、nameの情報を出力したい。と呼び出されたときにはじめてデータを取得することで不必要なデータは取得せずに済むようになりました。
あるクラスのメソッドをまとめてdelegateに定義する
delegateって明示的にメソッド名を定義する必要があって、面倒ですよね。
Hogeクラスのメソッド全部をdelegateしたいときは、こんな書き方で定義できました。delegate *Hoge.instance_methods(false), to: :hogeデータ取得機能では、データの取得先毎にクラスをわけてオブジェクトを作っています。
メソッドが呼び出されたら、そのデータを持っているオブジェクトにdelegateするように、この方法で実現しています。
- 投稿日:2019-12-03T08:46:31+09:00
サンプルコードでわかる!Ruby 2.7の主な新機能と変更点 Part 1 - 番号指定パラメータ(numbered parameter)
はじめに
Rubyは毎年12月25日にアップデートされます。
Ruby 2.7については2019年11月23日にpreview3がリリースされました。この記事ではRuby 2.7で導入される変更点や新機能について、サンプルコード付きでできるだけわかりやすく紹介していきます。
ただし、Ruby 2.7は多くの新機能や変更点があり、1つの記事に収まらないのでいくつかの記事に分けて書いていきます。
本記事で紹介するのは番号指定パラメータ(numbered parameter)です。本記事の情報源
本記事は以下のような情報源をベースにして、記事を執筆しています。
- Ruby 2.7.0のリリースノート
- Ruby 2.7.0のNEWS
- リリースノートやNEWSに記載されている各種issue
動作確認したRubyのバージョン
本記事は以下の環境で実行した結果を記載しています。
$ ruby -v ruby 2.7.0preview3 (2019-11-23 master b563439274) [x86_64-darwin19]フィードバックお待ちしています
本文の説明内容に間違いや不十分な点があった場合は、コメント欄や編集リクエスト等で指摘 or 修正をお願いします?
それでは以下が本編です!
ブロックの仮引数として番号指定パラメータが試験的に導入された
Ruby 2.7ではブロックの仮引数として番号指定パラメータ(numbered parameter)が試験的に導入されました。
これにより、|s|
のように明示的に引数名を指定する代わりに、_1
のような連番でブロックの仮引数を受け取ることができます。# 番号指定パラメータを使わない場合 %w(1 20 300).map { |s| s.rjust(3, '0') } #=> ["001", "020", "300"] # 番号指定パラメータを使う場合 %w(1 20 300).map { _1.rjust(3, '0') } #=> ["001", "020", "300"]# 番号指定パラメータを使わない場合 [1, 2, 3, 4].inject(0) { |memo, n| memo + n } #=> 10 # 番号指定パラメータを使う場合 [1, 2, 3, 4].inject(0) { _1 + _2 } #=> 10(Ruby初心者さん向けの補足)
上のコードに登場する%w()
という構文は、文字列の配列を作成するための構文です。%w(1 20 300) # ↑のコードは、↓と書いているのと同じ ["1", "20", "300"]割り当てられていない連番はnil
割り当てられない連番を指定するとnilが返ります。
# 9番目の仮引数は何も割り当てられないのでnil %w(1 20 300).map { _9 } #=> [nil, nil, nil]使用できる連番は_1から_9まで
使用できる連番は
_1
から_9
までです。
_10
のような連番を指定するとNameErrorが発生します。%w(1 20 300).map { _10 } #=> NameError (undefined local variable or method `_10' for main:Object)_1〜_9のようなローカル変数を宣言していると警告が出る
番号指定パラメータを使っているかどうかにかかわらず、
_1
〜_9
のような変数名を宣言していると、コードを読み込んだタイミングで警告が出ます。# 番号指定パラメータと同名の変数を宣言すると警告が出る _1 = "999" #=> warning: `_1' is used as numbered parameterですので、既存のコードをRuby 2.7環境で動かすと、場合によってはこのような警告が出る可能性があります。
同名のローカル変数があるとブロック内でも変数が優先される
ブロックの外で同名の変数が宣言されている場合、ブロック内では番号指定パラメータではなく、ブロックの外で宣言されたローカル変数として参照されます。
# 番号指定パラメータと同名の変数を宣言しておく _1 = "999" # _1はローカル変数の"999"として扱われる %w(1 20 300).map { _1.rjust(3, '0') } #=> ["999", "999", "999"]ただし、同名のローカル変数がブロックの後ろで宣言されていた場合はこの限りではありません。
# _1が番号指定パラメータとして機能する %w(1 20 300).map { _1.rjust(3, '0') } #=> ["001", "020", "300"] # 同名のローカル変数をブロックの後ろで宣言する _1 = "999"同名のメソッドがあると番号指定パラメータが優先される
_1
のような名前のメソッドが定義されている場合は、番号指定パラメータが優先されます。# 番号指定パラメータと同名のメソッドを定義しておく def _1 '123' end # _1は番号指定パラメータとして扱われる %w(1 20 300).map { _1.rjust(3, '0') } #=> ["001", "020", "300"] # _1() または self._1 とすればメソッドを明示的に呼び出せる %w(1 20 300).map { _1().rjust(3, '0') } #=> ["123", "123", "123"] %w(1 20 300).map { self._1.rjust(3, '0') } #=> ["123", "123", "123"]ちなみに、Ruby 2.6だと番号指定パラメータの構文が導入されていないため、メソッドの
_1
が呼ばれます。def _1 '123' end # Ruby 2.6の場合はメソッドの_1が呼ばれる(Ruby 2.7と挙動が異なる) %w(1 20 300).map { _1.rjust(3, '0') } #=> ["123", "123", "123"]Ruby 2.6と2.7では挙動が異なりますが、この場合は警告も出ないため、運が悪いと「Ruby 2.7に上げたら、なぜかちゃんと動かなくなった!」というトラブルが起きるかもしれません。(まあ、こんなコードを書いている人は滅多にないと思いますが・・・)
ネストしたブロックで番号指定パラメータを使う際の注意点
番号指定パラメータをネストしたブロックの中で使おうとするとsyntax errorが発生します。
sum = 0 [*1..4].each_slice(3) do # 外側のブロックで番号指定パラメータを使う _1.each do # 内側のブロックでも番号指定パラメータを使おうとするとエラーになる sum += _1 end end #=> numbered parameter is already used in (SyntaxError) # outer block here # sum += _1 # ^~~~~~~~~~~~~~~~~外側のブロックだけ、もしくは内側のブロックだけで使うのはOKです。
sum = 0 [1, 2, 3, 4].each_slice(3) do |arr| arr.each do # 内側のブロックでだけ番号指定パラメータを使う sum += _1 end end sum #=> 10従来のブロック仮引数と混在させると構文エラーになる
次のコードのように、従来のブロック仮引数と混在させると構文エラーが発生します。
# 従来のブロック仮引数 |memo, n| と、番号指定パラメータ _1 が混在すると構文エラー [1, 2, 3, 4].inject(0) { |memo, n| _1 + n } #=> ordinary parameter is defined (SyntaxError)ただし、明示的に番号指定パラメータと同名のブロック仮引数を指定した場合は構文エラーになりません。
# 番号指定パラメータと同名のブロック仮引数を使えば構文エラーにならない [1, 2, 3, 4].inject(0) { |_1, n| _1 + n } #=> 10コラム:濫用厳禁!?番号指定パラメータの使いどころ
タイプ量が減るので一見便利に見える番号指定パラメータですが、個人的には「濫用厳禁、用量と用法を守って使いましょう」な新機能だなと感じます。
なぜなら連番だとコードを書いた人の意図がまったく見えないので、可読性が落ちるからです。
(単純に「連番は脳への負担が大きい」という問題もあります)# 引数名を見れば「1, 2, 3, 4の値が渡されるのはnの方だな」と推測できる [1, 2, 3, 4].inject(0) { |memo, n| memo + n } # ん?どっちがどっちだ!?わからん!! [1, 2, 3, 4].inject(0) { _1 + _2 }irbなどでサクッと実行結果を確認したりするときに使うのは便利だと思いますが、実務で書く寿命の長いコードの場合はなるべく使用を控えた方が良い気がします。(そもそもRuby 2.7では「試験的な新機能」の扱いですので!)
まとめ
本記事ではRuby 2.7で試験的に導入された番号指定パラメータ(numbered parameter)を紹介しました。
冒頭でも説明したように「サンプルコードでわかる!Ruby 2.7の主な新機能と変更点」はいくつかの記事に分けて執筆します。
新しい記事を書いたらQiitaの「変更通知」でお知らせしますので、いちはやくキャッチしたい方は本記事のストックをよろしくお願いします!
- 投稿日:2019-12-03T08:38:56+09:00
Fullstaq Ruby を投入してみた(結果編に続く)
こんにちは。
以前見かけた Fullstaq Ruby を試してみようと思います。
Fullstaq Ruby
触れ込みについて
公式サイト の触れ込みでは
Ruby, optimized for production
A new Ruby distribution, with the Ruby you know and love, but better.本番環境向けに調整されたより優れたRubyとのことです。
Less memory — save 30-50%
メモリ消費を3~5割抑え
Faster
高速で
More secure
セキュアで
Fully open source, based on MRI
MRIベースの完全なオープンソース
とのこと。
作者について
サイトをみていると、創設者は Hongli Lai と Fullstaq という会社のようです。
Hongli Lai はそこに記載があるように、 Passenger や Ruby Enterprise Edition の作者だそうですね。最近の方は Ruby Enterprise Edition をご存じないかもしれません。
Ruby 1.8系の時代にメモリ使用率が激減することで、よく本番環境で用いられていたRuby処理系でした。
Rubyおじさんとしては「まじか、使えるんじゃないか」という気持ちにもなってきますね。
しくみ
週刊Railsウォッチで取り上げられていたのでそちらをお読みください。
週刊Railsウォッチ(20190821-2/2後編)11のgemにバックドア、ruby-jp Slackがとてもアツい、Fullstaq Rubyでチューンアップ、HTTPサービス監視chaoほか
導入
Server Edition 、Container Edition 、Heroku Edition と提供する予定のようですが、2019年12月3日現在は Server Edition のみのようです。
GET STARTED - Installation に RHEL/CentOS と Debian/Ubuntu のパッケージマネージャを使った方法が載っているので、そちらを読んで導入してみます。
今自由にできる環境がDocker環境しかなかったので、既存のDockerfileを手直しして入れてみました。
2019年12月3日現在、最新の Debian 10 buster 向けのパッケージは用意されていないようだったので、Debian 9 stretch をベースに使いました。FROM buildpack-deps:stretch ENV RUBY_VERSION 2.6.5 # Install dependencies RUN apt-get update -qq \ && apt-get install -y gnupg apt-transport-https ca-certificates curl RUN echo 'deb https://apt.fullstaqruby.org debian-9 main' > /etc/apt/sources.list.d/fullstaq-ruby.list \ && curl -SLfO https://raw.githubusercontent.com/fullstaq-labs/fullstaq-ruby-server-edition/master/fullstaq-ruby.asc \ && apt-key add fullstaq-ruby.asc \ && apt-get update -qq \ && apt-get install -y fullstaq-ruby-${RUBY_VERSION} \ && apt-get clean ENV PATH $PATH:/usr/lib/fullstaq-ruby/versions/${RUBY_VERSION}/bin # (省略)結果
まだデータ不足。
1、2週様子を見て載せます。動作には問題なく、テストはすべてパスし、エラートラッカーには特に情報は上がってきていません。
- 投稿日:2019-12-03T03:14:00+09:00
三井住友信託銀行プログラミングコンテスト2019に参加してみた
感想
AtCoderのコンテスト初参加。結構疲れた。てかムズイ。普通に数学的な発想が求められるんだな〜。
解ければ快感、解けなければうつ病。
https://atcoder.jp/contests/sumitrust2019
以下自分の回答A November 30
M1,D1 = gets.chomp.split.map(&:to_i) M2,D2 = gets.chomp.split.map(&:to_i) ret = 0 if M1 == 12 if D1 == 31 ret = 1 end end if M1 + 1 == M2 ret = 1 end puts retD2が1かどうか判定すれば良かっただけなのに、なにしてるんだろうか。ぐぬぬ。
B Tax Rate
N = gets.to_i X = ":(" a = N * 100 / 108 if ( N * 100 ) % 108 != 0 a += 1 end if a * 108 / 100 == N X = a end puts X結構迷ってしまった。てか正規のやり方なのか怪しい。
Xに文字列いれた上に数値入れてグロい。Ruby凄いな。C 100 to 105
X = gets.to_i N = X / 100 can = 0 for i in 0..N x = X - i * 100 if 0 <= x && x <= 5 * i can = 1 break end end puts can特にコメントなし(実際に解いてる時は一瞬で解けた嬉しさで内心ウッキウキだった)
D Lucky PIN
N = gets.to_i S = gets.chomp count = 0 for i in 0..9 a = S.index(i.to_s) if a != nil for j in 0..9 b = S.index(j.to_s, a+1) if b != nil for k in 0..9 c = S.index(k.to_s, b+1) if c != nil count += 1 end end end end end end puts count最初30000個を3重ループして時間オーバーになった(あたりまえやん)
ここまでは100分以内にできた。E Colorful Hats 2
ここから後日解き直した。
N = gets.to_i A = gets.split.map &:to_i t = [ 0, 0, 0 ] result = 1 for i in 0..N-1 if t.count( A[i] ) == 0 result = 0 break end result = ( result * t.count( A[i] ) ) % 1000000007 for j in 0..2 if t[j] == A[i] t[j] += 1 break end end end puts resultこれみんな簡単って言ってるのにわからなくて泣いた。
それぞれの帽子の数を保持して、その値と一致した分だけ場合分けができるという発想が無かった。
ブログラミングじゃなくても解けなかったと思う。悲しい。理系とは何だったのか。F Interval Running
T = gets.split.map(&:to_i) A = gets.split.map(&:to_i) B = gets.split.map(&:to_i) b = ( T[0] * A[0] + T[1] * A[1] ) - ( T[0] * B[0] + T[1] * B[1] ) if b == 0 puts "infinity" else a = T[0] * ( A[0] - B[0] ) if a * b > 0 puts 0 else c = ( a / b ) * -2 if a % b != 0 c -= 1 end puts c end endE問題で精神がやられて変数名とか見づらいしif文もおかしいけど一応解けた。
実際のコーディングだったら絶対に修正する。
A問題では変数に1つずつ入れているのにF問題では配列にいれる謎。(表記揺れってやつになるのかな)
自分で書いといてなんだけど*-2
とかいう箇所気持ち悪すぎる。
横軸時間、縦軸距離のグラフを書くのがわかりやすかった。
- 投稿日:2019-12-03T03:06:33+09:00
rails-tutorial第8章
ログイン機能を作ろう!!
モデルを使わないSessionリソースを扱う。
Sessionsコントローラを作ろう
$ rails generate controller Sessions new
ルーティング設定
config/routes.rbRails.application.routes.draw do root 'static_pages#home' get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' get '/signup', to: 'users#new' get '/login', to: 'sessions#new' post '/login', to: 'sessions#create' delete '/logout', to: 'sessions#destroy' resources :users end別にresourcesを使ってないからといってrestfulじゃないわけではない。本質は同じurlにhttpリクエストメソッドで指定してアクションを分けるということ。
また、これにより、login_pathなどの名前付きルートが設定される。
ログインフォーム作る。
app/views/sessions/new.html.erb<% provide(:title, "Log in") %> <h1>Log in</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(:session, url: login_path) do |f| %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.submit "Log in", class: "btn btn-primary" %> <% end %> <p>New user? <%= link_to "Sign up now!", signup_path %></p> </div> </div>form_forの引数はsignupの時は@userを渡していたが、:sessionを渡すことにより、
params = {session: {email: ~~~, password: ~~~}}という形で情報を送ることができる。で、urlオプションに名前付きルートを渡せば完成。
次にSessionsコントローラのcreateアクションを実装していこう。
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # ユーザーログイン後にユーザー情報のページにリダイレクトする else # エラーメッセージを作成する render 'new' end end def destroy end endfind_byに注目。
find()だと、idでしか探すことができず、()の中に数字しか入れられない。IDがわかっている場合は、findメソッド
IDが不明で、別の条件でレコード検索をしたい場合は、find_byメソッド
このように覚えておこう。注意点!!
user = User.find_by(email: params[:session][:email].downcase) if user.authenticate(params[:session][:password]) . . .上記のように書いてしまうと、致命的な欠陥がある。
find_byはオブジェクトが見つからない時にnilを返すので、もし存在していないメールアドレスを渡してしまうと、、
nil.authenticateとなり、Nomethoderrorが起こってしまう。
なので、
user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password])ユーザーが存在するかつ、アドレスの認証が通れば、という条件をif文に当てている。
ちなみに userが存在しないとわかった時点で、右側の条件式は評価されないという特徴がある。
これにより、nil.authenticateは実行されなくなる。ログイン失敗時のメッセージを表示する。
ActiveRecordを継承しているモデルと、バリデーションを設定すれば@user.errors.full_messagesに実際のエラーメッセージが表示されたが、
Sessionsの場合、モデルでもなければバリデーションもないので、
flashを使ってメッセージを表示する必要がある。
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) # ユーザーログイン後にユーザー情報のページにリダイレクトする else flash[:danger] = 'Invalid email/password combination' # 本当は正しくない render 'new' end end def destroy end end上記の場合、ちょっとしたバグがある。
それは、flashメッセージがhome画面やhelp画面に移動しても表示されてしまうことだ。なぜこれが起きるのか?
redirect_toの場合は、それでリクエストが一回。
別のページに飛ぼうとすると2回目なので飛ぶ寸前でflashメッセージは消える。しかし、今回は、
render 'new'となっている。
renderはリクエストには入らないため、
例えば、home画面に飛ぶので一回目、
再度リロードすると、2回目なので消える、という流れになってしまう。回帰バグを防ぐためにテストを書こう。
今回もブラウザを行ったり来たりしているテストなので、インテグレーションテストで行う。
$ rails generate integration_test users_login
test/integration/users_login_test.rbrequire 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest test "login with invalid information" do get login_path assert_template 'sessions/new' post login_path, params: { session: { email: "", password: "" } } assert_template 'sessions/new' assert_not flash.empty? get root_path assert flash.empty? end endこの状態だとテストは落ちる。
じゃあ、どうやってflashメッセージを1回だけ表示するの?
flashはメソッドなので、flash.now[:danger] = 'Invalid email/password combination'
というようにできる。
.nowは1度目のリクエストが来たらflashメッセージを消す。
これはflashメソッドがrailsで元から設定されたメソッドだからこういうことができる。これで失敗時の実装は完了
成功した時の処理を実装しよう
ユーザーが存在し、アドレスの認証が通れば、ログインしている状態を作り出さないといけない。
sessionという特殊な変数を使ってそれを実現する。
session[:user_id] = user.id
ここに何か値が入れば、ログイン中とし、nilになればログアウトとする。それを実現するためにhelperにloginメソッドを定義しよう。
app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end endこのhelperメソッドを使ってログイン機能を実装
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy end end最後に、loginしているかどうかは、様々なコントローラで使うので、loginメソッドを書いたhelperをどのコントローラでも使えるようにしておこう。
app/controllers/application_controller.rbclass ApplicationController < ActionController::Base protect_from_forgery with: :exception include SessionsHelper endそのためには、全コントローラの親クラスであるapplication コントローラに
createアクションを実装したら
その次は、session[:user_id]に値が入っていれば、〜〜〜〜、入っていなければ、〜〜〜〜というようにしていく。
また、今どのユーザーがログインしているかがわからないと、showアクションでどのユーザーページを表示するべきかなどの問題が発生してしまうので、なんとかして、sessionの情報から現在ログインしているユーザーを参照する必要がある。
では、現在ログインしているユーザーを返すメソッドをhelperに定義しよう。
app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # 現在ログイン中のユーザーを返す (いる場合) def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end end@current_userとなっているのは、インスタンス変数をview側で使いたいから。
また、@current_userを使いたいたびにfind_byをするのはパフォーマンス上あまり良くない。
なので、 ||= を使って、存在すれば@current_userを返す。 なかったら、find_byを使うというようにしている。これで、1リクエストで最大で1問い合わせにできる。
view側でログインユーザーと非ログインユーザーを分けるには
<% if logged_in? %> # ログインユーザー用のリンク <% else %> # ログインしていないユーザー用のリンク <% end %>というようにrubyのコードを使ってあげれば良い。
そのために、上記のlogged_in?メソッドを定義しよう。
app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end # 現在ログイン中のユーザーを返す (いる場合) def current_user if session[:user_id] @current_user ||= User.find_by(id: session[:user_id]) end end # ユーザーがログインしていればtrue、その他ならfalseを返す def logged_in? !current_user.nil? end endlogged_in?メソッドはcurrent_userメソッドを呼び出して、インスタンス変数があれば、trueを返し、なければfalseを返すようにする。
このままだと、nilの時にtrueを返してしまう。
!は否定演算子である。true falseが逆になる。
これで、nilの時はfalseをnilじゃない時はtrueを返すようにする。
ログインしているかでviewを分ける
app/views/layouts/_header.html.erb<header class="navbar navbar-fixed-top navbar-inverse"> <div class="container"> <%= link_to "sample app", root_path, id: "logo" %> <nav> <ul class="nav navbar-nav navbar-right"> <li><%= link_to "Home", root_path %></li> <li><%= link_to "Help", help_path %></li> <% if logged_in? %> <li><%= link_to "Users", '#' %></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"> Account <b class="caret"></b> </a> <ul class="dropdown-menu"> <li><%= link_to "Profile", current_user %></li> <li><%= link_to "Settings", '#' %></li> <li class="divider"></li> <li> <%= link_to "Log out", logout_path, method: :delete %> </li> </ul> </li> <% else %> <li><%= link_to "Log in", login_path %></li> <% end %> </ul> </nav> </div> </header>ここで注意しなければいけないのは、
<li><%= link_to "Profile", current_user %></li>これは本来
<li><%= link_to "Profile", user_path(@current_user) %></li>となっている。
ただ、これは @current_userに省略することができたよね。
だから、current_userメソッドを書くだけで良い。
もう一つ注意する点が、
<%= link_to "Log out", logout_path, method: :delete %>link_toメソッドはデフォルトではgetリクエストを送るのでmethod: :deleteを書かないと、
/logout に getリクエストを送ってしまう。
なので、link_toでgetリクエスト以外を指定するときは、
method: :deleteのようにオプションを追加しなければいけない。注意点としては、method: と :deleteどちらも:が必要だということ。
bootstrapのドロップダウンを使えるようにするために
app/assets/javascripts/application.js//= require rails-ujs //= require jquery //= require bootstrap //= require turbolinks //= require_tree .2,3行目を書き加えるとドロップダウンが使えるようになる。
ユーザーログインのテスト
テスト時に登録済みユーザーとしてログインしておく必要があります。当然ながら、データベースにそのためのユーザーが登録されていなければなりません。Railsでは、このようなテスト用データをfixture (フィクスチャ) で作成できます。
test/fixtures/users.ymlmichael: name: Michael Example email: michael@example.com password_digest: <%= User.digest('password') %>でもこれじゃあdigestメソッドがないから、ハッシュ化できない。
digestメソッドはUserに関連するときしか使わないので、userモデルに定義する。
app/models/user.rbbefore_save { self.email = email.downcase } validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } has_secure_password validates :password, presence: true, length: { minimum: 6 } # 渡された文字列のハッシュ値を返す def User.digest(string) cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost BCrypt::Password.create(string, cost: cost) end end三項演算子について
condition ? expr1 : expr2condition
trueかfalseかを評価する式です。
expr1, expr2
各々の値の場合に実行する式です。 conditionがtrueの場合、演算子はexpr1の値を返します。そうでない場合はexpr2の値を返します。ユーザーログインテストコード
test/integration/users_login_test.rbrequire 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) end . . . test "login with valid information" do get login_path post login_path, params: { session: { email: @user.email, password: 'password' } } 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) end end@user = users(:michael)はtest/fixtures/users.ymlで作った:michaelを@userに代入している。
また、
assert_redirected_to @user follow_redirect!1行目は、 @userにリダイレクトされますよね?(行き先は〜〜駅ですよね)
2行目は、 assert_redirected_to @userが通った上で@userにリダイレクトされる。ちなみにこの状態でテストは通る。
signup後はそのままログイン済みにしよう。
app/controllers/users_controller.rbclass UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @user = User.new end def create @user = User.new(user_params) if @user.save log_in @user flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end endlog_in @userでセッション変数に@user.idを保存する。
ちなみに、log_in @userが使えるのはsessions_helperがapplication controllerにincludeされてるから。ログインのテスト
テストにもテストのhelperが存在する。
そこに、ログインしているかどうかを判断するメソッドを定義しておこう。test/test_helper.rbENV['RAILS_ENV'] ||= 'test' . . . class ActiveSupport::TestCase fixtures :all # テストユーザーがログイン中の場合にtrueを返す def is_logged_in? !session[:user_id].nil? end endユーザー登録後ログイン状態になっているかテスト。
test/integration/users_signup_test.rbrequire 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest . . . test "valid signup information" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end follow_redirect! assert_template 'users/show' assert is_logged_in? end end先ほどymlにログインしたユーザー、test_helperにis_logged_in?メソッドを定義したので、テストは通る。
ログアウト機能を実装
ログアウトメソッドもログインメソッドと同じようにsessions_helperに定義しておく。
app/helpers/sessions_helper.rbmodule SessionsHelper # 渡されたユーザーでログインする def log_in(user) session[:user_id] = user.id end . . . # 現在のユーザーをログアウトする def log_out session.delete(:user_id) @current_user = nil end endここで気をつけるのが、
sessionというのはハッシュな訳で、session[:user_id] = user.id
session = {user_id: user.id}という形になっている。
で、ハッシュの値を消すときは、
hash.delete(key)
というdeleteメソッドにキーを引数に渡すと実現できる。
で、今回sessionでも同じことが起きている。
session.delete(:user_id)
これにより、sessionというハッシュに格納されていた{user_id: user.id}が消える。
じゃあ、なんで@current_user = nilまでする必要があるの?
これは、なるべくサーバーの問い合わせ?的なものを無くして負荷を少なくするためらしい。
destroyアクションの実装
app/controllers/sessions_controller.rbclass SessionsController < ApplicationController def new end def create user = User.find_by(email: params[:session][:email].downcase) if user && user.authenticate(params[:session][:password]) log_in user redirect_to user else flash.now[:danger] = 'Invalid email/password combination' render 'new' end end def destroy log_out redirect_to root_url end endここで注目したいのが、destroyアクションはviewがないので、最後にどのviewに飛ぶかを指定してあげる必要があるということ。これはupdateアクションとかにも言えそうだよね。
ログアウトのテスト
統合テストのストーリーを拡張しよう。
具体的には、ログインテストの中で、ログアウトのテストのアサーションも書いちゃおうというもの。test/integration/users_login_test.rbrequire 'test_helper' class UsersLoginTest < ActionDispatch::IntegrationTest . . . test "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 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 end endassert_not is_logged_in?
is_logged_in?はtest_helperに定義したから使える。なぜsession変数はなくならない?なくなる?
調べろ。ブラウザとrails sに保存されるから?
- 投稿日:2019-12-03T01:10:49+09:00
ログイン中ユーザーの投稿内容をshowアクション以外で出力する方法
SNSっぽいアプリケーションのマイページで、ログイン中ユーザの投稿一覧を取得したかっただけなのにかなり詰まったので備忘録として残しておきます。
アプリの仕様上showアクションやshow.html.erbファイルを使用できず、index.html.erbで表示させるというレア?パターンではありますが。
環境
Rails 5.2.3
Ruby 2.5.1
gemのdeviseを使用
当記事で使用しているモデル名はplace詰まった所
homes/index.html.erbファイル内にてeach doを利用し投稿内容を全部出力したい。
homes/index.html.erb<% @myplaces.each do |myplace| %> #省略 <% end %>全部のplaceを取得するのはhomesコントローラー内のindexアクションで
./controllers/homes_controller@places = Place.allこう書けばいい。そりゃそうだ。
問題はログイン中ユーザの投稿内容だけを取得したい時。./controllers/homes_controller@myplaces = Place.find(current_user.id)と入力するとundefined method 'each' for nil:NilClassでエラーが発生する。これは@myplacesが空だからeachする元ネタがありませんよっていう怒られ方。できそうな気もするのに。
試しにターミナルでfind(1)として出力してみる。
ターミナルpry(main)> Place.find(1) (0.5ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483 Place Load (0.3ms) SELECT `places`.* FROM `places` WHERE `places`.`id` = 1 LIMIT 1 => #<Place:0x00007f831ad85618 id: 1, name: #以下省略問題ない。
idが1であるcurrent_user.idを適当な変数に代入して出力してみる./controllers/homes_controller@user = current_user.idhomes/index.html.erb<%= @user %> #=> 1こっちも問題がない。
なんで出力されないのか不思議で仕方がなかったんですが、驚いた事にこんな書き方だけで解決しました。./controllers/homes_controller@places = Place.all places = @places @myplaces = current_user.placeshomes/index.html.erb<% @myplaces.each do |myplace| %> #省略 <% end %>目ン玉飛び出そうになりました。こんな簡単な記述でよかったんかと。。。普通にidを指定すれば出来るやろって余裕こいてたら痛い目見ました。
原因は今度時間を見つけて探してみます。今度作る時にはハマらないように…