【環境構築】MySQLのパスワードが分からず、最終的にディレクトリごと削除して解決した話(オススメしない)。
(※2020/05のメモを記録)
はじめに
環境構築の際、MySQLのパスワードが分からず詰まったので、以下に解決まで至った流れを記していきます。 これはあくまで私の環境上の話なので、どこまで参考になるか分かりませんし、皆様のお時間を無駄にしたくはないので、最初に解決策と主に使用したコマンドを記載しておきます。
ご興味ある方は最後までご一読頂ければと思います。
解決策
- mysqlの5.7系と8系がインストールされてたから8系を削除。
- mysqlが5.7系のパスを指すように
zshrc
で設定。 - 過去にインストールしていたMySQL5.7系のパスワードが分からないから、
sudo rm -rf /usr/local/var/mysql
でディレクトリごと削除。 - 改めて
brew install mysql@5.7
を実行し、完全に新しいrootユーザーが作成されたことで、パスワード無しでログインできた。
主に使用したコマンド
MySQL
- brew uninstall mysql
- which mysql
- ps aux | grep mysql
- source ~/.zshrc
- sudo rm -rf /usr/local/var/mysql
試したこと①とりあえずログインしようとしてみる
MySQLのバージョン確認。8系が入ってる。
$ mysql --version > mysql Ver 8.0.19 for osx10.15 on x86_64 (Homebrew)
パスワードが分からない。何も覚えてねえ、、
$ mysql.server start Starting MySQL .. SUCCESS! $ mysql -u root -p > Enter password: ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) $ mysql -u root > ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
勿論bundle exec rails db:create
は実行できない。
$ bundle exec rails db:create Access denied for user 'root'@'localhost' (using password: NO) Couldn't create 'runteq_rails_advanced_development' database. Please check your configuration. rails aborted! Mysql2::Error::ConnectionError: Access denied for user 'root'@'localhost' (using password: NO)
$ which mysql > /usr/local/bin/mysql
試したこと②MySQL8系をアンインストールし、5.7系を使ってみる
パスワードがわからんから一旦brew uninstall mysql
してみると、8系がアンインストールされた。
which mysql
を打つとnot found
と出る。
$ brew uninstall mysql Uninstalling /usr/local/Cellar/mysql/8.0.19... (286 files, 289.2MB) $ which mysql mysql not found
ps aux
で現在実行されているプロセスを確認できる。grep
コマンドで文字列検索を行えるので、ps aux | grep mysql
を実行し、実行中のMySQLが無いか確認してみる。すると、mysqlの5.7系のプロセスが実行されている事が分かった。
whichコマンドでは5.7系が見つからなかったけど、5.7系のプロセス自体は実行されているため、mysqlと打てば5.7系のディレクトリにパスが通るよう設定する。
$ ps aux | grep mysql fune 617 0.0 0.0 4682956 2000 ?? S 30 420 1:29.79 /usr/local/opt/mysql@5.7/bin/mysqld --basedir=/usr/local/opt/mysql@5.7 --datadir=/usr/local/var/mysql --plugin-dir=/usr/local/opt/mysql@5.7/lib/plugin --log-error=ryota21.local.err --pid-file=ryota21.local.pid fune 497 0.0 0.0 4280012 8 ?? S 30 420 0:00.04 /bin/sh /usr/local/opt/mysql@5.7/bin/mysqld_safe --datadir=/usr/local/var/mysql fune 30815 0.0 0.0 4268280 636 s000 R+ 3:27PM 0:00.01 grep mysql
zshrc
にexport PATH="/usr/local/opt/mysql@5.7/bin:$PATH"
と記載。
export PATH="/usr/local/opt/mysql@5.7/bin:$PATH
source
コマンドでzshrcファイルに記載されているコマンドを実行し、パスを通す。
$ source ~/.zshrc`
which mysql
と打つと、zshrcで指定したパスを指すようになった。
$ which mysql /usr/local/opt/mysql@5.7/bin/mysql
まだpasswordを空でEnter押してもログインできない。。。
$ mysql -u root -p > Enter password: ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
MySQL5.7系もアンインストールし、再インストールしてみる。 やっぱりパスワードが分からず、ログインできない、、😭
$ mysql.server stop Shutting down MySQL .... SUCCESS! mysqlの5.7系をアンインストール $ brew uninstall mysql@5.7 mysqlの5.7系を再インストール $ brew install mysql@5.7 mysqlを起動 $ mysql.server start mysqlにログイン $ mysql -uroot > ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO) $ mysql -u root -p > Enter password: ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)
試したこと③:ディレクトリごとMySQLを削除
パスワードがマジで分からないので、もうディレクトリごと消してやれ、、!と決意。
一旦プロセスに残っているMySQLをmysql.server stop
で停止させる。
$ ps aux | grep mysql fune 32217 0.0 0.0 4278520 676 s000 S+ 3:41PM 0:00.01 grep mysql fune 32210 0.0 0.4 4681888 31748 s000 S 3:38PM 0:00.41 /usr/local/Cellar/mysql@5.7/5.7.29/bin/mysqld --basedir=/usr/local/Cellar/mysql@5.7/5.7.29 --datadir=/usr/local/var/mysql --plugin-dir=/usr/local/Cellar/mysql@5.7/5.7.29/lib/plugin --log-error=ryota21.local.err --pid-file=/usr/local/var/mysql/ryota21.local.pid fune 32111 0.0 0.0 4280120 688 s000 S 3:38PM 0:00.04 /bin/sh /usr/local/Cellar/mysql@5.7/5.7.29/bin/mysqld_safe --datadir=/usr/local/var/mysql --pid-file=/usr/local/var/mysql/ryota21.local.pid プロセスを停止する $ mysql.server stop
grepしてみると、MySQLでgrepしたというプロセスしか残ってない。 MySQL自体のプロセスは止まっているということ。
$ ps aux | grep mysql fune 32236 0.0 0.0 4278520 672 s000 S+ 3:42PM 0:00.00 grep mysql
MySQLをアンインストールする $ brew uninstall mysql@5.7 ディレクトリの中に削除してはいけないものが無いか確認しておく。 $ open /usr/local/var/mysql MySQLのディレクトリ自体を削除。 $ sudo rm -rf /usr/local/var/mysql
5.7系を再インストールしてみたら、やっとログインできた!!!
5.7系をインストール $ brew install mysql@5.7 $ mysql.server start Starting MySQL . SUCCESS! $ mysql -u root Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 2 Server version: 5.7.29 Homebrew Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. mysql>
おわりに
やっぱり環境構築は大変だと改めて実感しました。。。 いい勉強にはなったので、もっと場数踏んで、環境構築を行う速度を上げたいものですね。
参照
mergeメソッドのメモ
以下は、あるテーブルのデータと、そのテーブルの関連テーブルの外部キーを代入している。
しかし、処理が2行に分かれていて冗長。
@meal_record = current_user.meal_records.build(meal_record_params) @meal_record.food_id = params[:food_id]
mergeメソッドを使えば、かんたんに書ける。
@meal_record = current_user.meal_records.build(meal_record_params.merge(food_id: params[:food_id]))
ブロックでも書ける。
@meal_record = current_user.meal_records.build(meal_record_params) do |meal_record| meal_record.food_id = params[:food_id] end
変数の中身を確認してみる。
current_user.meal_records.build(meal_record_params) => #<MealRecord:0x00007f7fd4bf87f8 id: nil, meal_time: Sat, 31 Oct 2020 00:00:00 JST +09:00, created_at: nil, updated_at: nil, user_id: 2, food_id: nil> current_user.meal_records.build(meal_record_params.merge(food_id: params[:food_id])) => #<MealRecord:0x00007f7fd0fc4a98 id: nil, meal_time: Sat, 31 Oct 2020 00:00:00 JST +09:00, created_at: nil, updated_at: nil, user_id: 2, food_id: 84> current_user.meal_records.build(meal_record_params.merge(food_id: params[:food_id])).food Food Load (0.6ms) SELECT `foods`.* FROM `foods` WHERE `foods`.`id` = 84 LIMIT 1 ↳ (pry):27:in `create' => #<Food:0x00007f7fd0de8800 id: 84, name: "アイスクリーム", labels: ["Food", "Ice cream", "Frozen dessert", "Dish", "Dondurma", "Cuisine", "Vanilla ice cream", "Ingredient", "Sorbet", "Gelato", "Dessert", "Frozen yogurt", "Cream", "Vanilla", "Dairy"], calorie: 0, calorie_theory: "", created_at: Fri, 30 Oct 2020 21:33:27 JST +09:00, updated_at: Fri, 30 Oct 2020 21:33:27 JST +09:00>
範囲オブジェクトrange。 ..でリテラル表現
範囲オブジェクトとは
範囲オブジェクトのクラス。範囲オブジェクトは文字どおり何らかの意味での範囲を表します。数の範囲はもちろん、日付の範囲や、「"a" から "z" まで」といった文字列の範囲を表すこともできます。 docs.ruby-lang.org
範囲の開始値と範囲の終了値を「..」で繋いで書けば、範囲オブジェクトのリテラル表現になる。
range = 1..10 range.class => Range range.count => 10 range.map { |n| n * 2 } => [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
mapメソッドでループする(ついでにreject(&:blank?)とflattenを使ってる)
わざわざ空配列を用意するのダサいし、冗長。
@food_lists = [] food_labels.each do |food_label| @food_lists = Food.search_by_label(food_label) end
mapメソッドを使う。
reject(&:blank?)で配列からnilと空文字を除く。
flattenで多次元配列を一次元配列にする。
food_lists = food_labels.map do |food_label| Food.with_label(food_label) end # 空配列を削除し、多次元配列を一次元配列にする @food_lists = food_lists.reject(&:blank?).flatten
food_labels = response.responses.map do |res| res.label_annotations.map do |label| label.description end end # mapと&:を使えばシンプルに food_labels = response.responses.map do |res| res.label_annotations.map(&:description) end
マイグレーションファイルについて
マイグレーションファイルとは
データベースの設計図。
マイグレーションファイルを実行すれば、マイグレーションファイルを元にDBが作成される。
→マイグレーションファイルを作成しただけではDBに反映されない。
マイグレーションファイルを作成
rails g migration マイグレーション名
rails generate model モデル名 カラム名:型 カラム名:型
$ be rails g model ApiKey user:references access_token:string expires_at:datetime Running via Spring preloader in process 47115 invoke active_record create db/migrate/20200719073831_create_api_keys.rb create app/models/api_key.rb invoke rspec create spec/models/api_key_spec.rb invoke factory_bot create spec/factories/api_keys.rb
マイグレーションファイルを実行
dbにマイグレーションファイルの内容が反映された。
$ be rails db:migrate == 20200719073831 CreateApiKeys: migrating ==================================== -- create_table(:api_keys) -> 0.0065s -- add_index(:api_keys, :access_token, {:unique=>true}) -> 0.0020s == 20200719073831 CreateApiKeys: migrated (0.0091s) ===========================
DBに反映されているマイグレーションファイルを確認する
upと書いているファイルがデータベースに反映されているもの(マイグレーション済みということ)
$ rails db:migrate:status database: db/learning_api_development Status Migration ID Migration Name -------------------------------------------------- up 20200627072315 Sorcery core up 20200627073523 Create social profiles up 20200627074538 Create articles up 20200627075238 Create comments up 20200719073831 Create api keys
スキーマファイルで現在のDBの構造を確認する
マイグレーションファイルの内容をDBに反映されていない状態に戻す
rails db:rollbackで、最新のマイグレーションファイルをdown状態にできる。
down状態のマイグレーションファイル = データベースに反映されていない状態にあるということ。
※up状態にあるマイグレーションファイルを削除・編集することは避けよう。
rails db:rollback
be rails db:reset
全てのテーブルを dropし、"db/schema.rb"を元にテーブルの再作成を行う。レコードは空になる。
be rails db:migrate:reset
migrateとresetを両方行う。
$ be rails db:migrate:reset Dropped database 'db/runteq_curriculum_learning_api_development.sqlite3' Dropped database 'db/runteq_curriculum_learning_api_test.sqlite3' Created database 'db/runteq_curriculum_learning_api_development.sqlite3' Created database 'db/runteq_curriculum_learning_api_test.sqlite3' == 20200627072315 SorceryCore: migrating ====================================== -- create_table(:users) -> 0.0037s -- add_index(:users, :email, {:unique=>true}) -> 0.0021s == 20200627072315 SorceryCore: migrated (0.0060s) ============================= == 20200627073523 CreateSocialProfiles: migrating ============================= -- create_table(:social_profiles) -> 0.0093s == 20200627073523 CreateSocialProfiles: migrated (0.0094s) ==================== == 20200627074538 CreateArticles: migrating =================================== -- create_table(:articles) -> 0.0058s == 20200627074538 CreateArticles: migrated (0.0059s) ========================== $ rcs Running via Spring preloader in process 60310 Loading development environment in sandbox (Rails 6.0.2.1) Any modifications you make will be rolled back on exit [1] pry(main)> User.first (0.3ms) begin transaction User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => nil
be rails db:seed
Seedファイルのデータを、テーブルのレコードに投入
$ be rails db:seed $ rcs Running via Spring preloader in process 60874 Loading development environment in sandbox (Rails 6.0.2.1) Any modifications you make will be rolled back on exit [1] pry(main)> User.first (1.7ms) begin transaction User Load (0.4ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User:0x00007fb1841d4808 id: 1, name: "user-1", email: "user-1@example.com", crypted_password: "$2a$10$FWgdOQ7TTpcNzgGVxhRLfus46sJg8WP3DPi0yvj4U72rH7RW0PMxa", salt: "8QYWYUM7Vb-yp44eVzBP", created_at: Sun, 19 Jul 2020 08:06:58 UTC +00:00, updated_at: Sun, 19 Jul 2020 08:06:58 UTC +00:00>
Rakeタスクのメモ的な(メールの自動配信機能)
Rakeタスク
タスクファイル作成
rails g task mail_summary
タスクファイルの編集
(lib/tasks/mail_summary.rake) namespace :mail_summary do desc '公開済の記事の件数と、昨日公開された記事の件数とタイトルをメールで送信' # task :article doだとDBには接続しない task article: :environment do # mailerのメソッドを呼び出す # articles = Article.all ArticleMailer.report_summary.deliver_now end end
メールをcronjobなどから今すぐ送信したい場合は、deliver_now でいつでも実行できる。
# すぐにメール送信したい場合は#deliver_nowを使用 UserMailer.welcome(@user).deliver_now # Active Jobを使用して後でメール送信したい場合は#deliver_laterを使用 UserMailer.welcome(@user).deliver_later
一般に、非同期キュー(.deliver_laterでメールを送信するなど)はRakeタスクに書いても動きません。Rakeが終了すると、.deliver_laterがメールの処理を完了する前にインプロセスのスレッドプールを削除する可能性があるためです。この問題を回避するには、.deliver_nowを用いるか、development環境で永続的キューを実行してください。
これをdeliver_later
にすると、メールが届かなかった。
ArticleMailer.report_summary.deliver_later
講師 deliverは非推奨、deliver_nowは同期処理、deliver_laterは非同期処理 nowだと同期処理だからキューに入らないと思う もしSidekiqとかResqueのような永続化キューを使用してない場合、rakeタスクの中でdeliver_laterするとメール届かない可能性があるみたい。deliver_laterでメールの送信処理を完了する前にrakeタスクのプロセスが終わる可能性があるかららしい。
タスク一覧
rake -vT rake mail_summary:article # 公開済の記事の総記事数と、昨日公開された記事の件数とタイトルをメールで送信 rake status_update:article_status # 記事が公開待ち状態で、公開日時が過去の場合、ステータスを「公開」にする
タスク実行
rake status_update:article_status
Mailer
$ rails g mailer ArticleMailer report_summary Running via Spring preloader in process 99411 create app/mailers/application_mailer.rb create app/mailers/article_mailer.rb invoke erb create app/views/layouts/mailer.html.erb create app/views/layouts/mailer.text.erb create app/views/article_mailer create app/views/article_mailer/report_summary.html.erb create app/views/article_mailer/report_summary.text.erb
(app/mailers/application_mailer.rb) class ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' end
- 良い書き方
(app/mailers/article_mailer.rb) class ArticleMailer < ApplicationMailer # 共通処理を書く。 # article_mailer/report_summary.~のビューを呼び出す def report_summary(articles) @published_articles = articles.published @published_articles_yesterday = Article.published_yesterday mail(to: 'admin@example.org', subject: '公開記事の集計結果') end end
- 良くない書き方
# BAD: # @published_articles_yesterday = @published_articles.where('? <= published_at && published_at <= ?', Time.current.yesterday.beginning_of_day, Time.current.yesterday.end_of_day) # SOSO: # @published_articles_yesterday = @published_articles.where(published_at: Time.current.yesterday.all_day)
- scopeを定義
(app/models/article.rb) scope :published_yesterday, -> { where(published_at: 1.day.ago.all_day) }
# メーラーメソッドでcountしておくのもあり @published_articles_count = articles.published
(app/views/layouts/mailer.html.erb) <html> <body> <%= yield %> </body> </html>
(app/views/article_mailer/report_summary.html.erb) <p>公開済の記事数: <%= "#{@published_articles.count}" %>件</p> <% if @published_articles_yesterday.present? %> <p>昨日公開された記事数: <%= "#{@published_articles_yesterday.count}" %>件</p> <% @published_articles_yesterday.each do |article| %> <p>タイトル: <%= article.title %></p> <% end %> <% else %> <p>昨日公開された記事はありません</p> <% end %>
Letter Opener
https://github.com/ryanb/letter_opener ・実際にメールを送信するのではなく、デフォルトのブラウザでメールをプレビューする。 これにより、開発環境で実際にメールを送らずに済む。
(config/environments/development.rb) # letter_opener_webの設定 config.action_mailer.delivery_method = :letter_opener_web config.action_mailer.default_url_options = 'localhost:3000'
(app/views/password_resets/edit.html.erb) <%= form_with model: @user, url: password_reset_path(@token), local: true do |f| %> 引数の@token無しでもいけた。。。 <%=form_withmodel:@user, url:password_reset_path, local:truedo |f| %>
Whenever
config/schedule.rb # 初期設定ファイル # このファイルを使って、すべてのcronジョブを簡単に定義することができます。 # Rails.rootを使用するために必要(cronはRailsとは全く別物だから) require File.expand_path(File.dirname(__FILE__) + '/environment') # cronを実行した際のログを出力するファイルを指定 set :output, "#{Rails.root}/log/cron.log" # TODO: cronログの出力内容を記述できてないっぽい # cronを実行する環境 set :environment, :development every :minute do rake 'status_update:article_status' end every 1.day, at: '9:00 am' do rake 'mail_summary:article' end
config/schedule.rbをcron構文に変換した内容を表示してくれてるが、crontabファイルが更新されているわけではない。
$ be whenever * * * * * /bin/bash -l -c 'cd /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced && RAILS_ENV=development bundle exec rake status_update:article_status --silent >> /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/log/cron.log 2>&1' * * * * * /bin/bash -l -c 'cd /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced && RAILS_ENV=development bundle exec rake mail_summary:article --silent >> /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/log/cron.log 2>&1' ## [message] Above is your schedule file converted to cron syntax; your crontab file was not updated. ## [message] Run `whenever --help' for more options.
設定されているcronを確認
$ crontab -l # Begin Whenever generated tasks for: /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/config/schedule.rb at: 2020-05-14 16:09:28 +0900 0 * * * * /bin/bash -l -c 'cd /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced && RAILS_ENV=development bundle exec rake status_update:article_status --silent >> /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/log/cron.log 2>&1' # End Whenever generated tasks for: /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/config/schedule.rb at: 2020-05-14 16:09:28 +0900
cronにデータを反映させる
$ bundle exec whenever --update-crontab [write] crontab file updated
OKを押す。
再度cronを確認するとデータが反映されていることがわかる
$ crontab -l # Begin Whenever generated tasks for: /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/config/schedule.rb at: 2020-05-31 22:22:28 +0900 * * * * * /bin/bash -l -c 'cd /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced && RAILS_ENV=development bundle exec rake status_update:article_status --silent >> /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/log/cron.log 2>&1' * * * * * /bin/bash -l -c 'cd /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced && RAILS_ENV=development bundle exec rake mail_summary:article --silent >> /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/log/cron.log 2>&1' # End Whenever generated tasks for: /Users/funesakisuke/workspace/runteq/332_ryota1116_runteq_learning_advanced/config/schedule.rb at: 2020-05-31 22:22:28 +0900
cronからデータを削除する
$ bundle exec whenever --clear-crontab
【Ruby】procとblock
procとblockについて
block
ブロックとは
do...end
や{ }
のカタマリのこと。
do...endと{ }
- 改行を含む長いブロックを書く場合はdo...end
- 1行でコンパクトに書きたい場合は{ } プロを目指す人のためのRuby入門 言語仕様からテスト駆動開発・デバッグ技法まで (Software Design plusシリーズ) | 伊藤 淳一 |本 | 通販 | Amazon
ブロックの中の|i|
のi
はブロック引数と呼ばれるもの。
ブロックの中で| |
を書くことで、eachメソッドから渡された配列の要素が順番に入ってくる。
[1, 2, 3].each do |i| puts i*2 end 2 4 6 => [1, 2, 3]
[1, 2, 3].each { |a| p a } 1 2 3 => [1, 2, 3]
値を受け取らない場合は、| |
を書かなければいい。| |
を使わなければ、単に配列の要素の数だけ処理が実行される。
3.times do p 'おはよ!' end "おはよ!" "おはよ!" "おはよ!" => 3
[1, 2, 3].each { p 'おはよ' } "おはよ" "おはよ" "おはよ" => [1, 2, 3]
do...endの応用
# @meal_record = MealRecord.new(food_id: params[:food_id]) # @meal_record.meal_record_pictures = ActiveStorage::Blob.find(session[:meal_picture_id]) if session[:meal_picture_id] @meal_record = MealRecord.new(food_id: params[:food_id]) do |meal_record| meal_record.meal_record_pictures = ActiveStorage::Blob.find(session[:meal_picture_id]) if session[:meal_picture_id] end
ブロック引数
ブロック引数は引数と同じように、関数の()に入れてあげればいい。
そしてブロック引数を受け取る関数を定義するには&block
という仮引数を指定してあげるといい。この&block
でブロック引数を受け取る宣言をしているといった感じである。
注意点は、
- ブロック引数は1つしか受け取れない。
- 複数の引数が存在する場合、ブロック引数は最後の引数として指定してあげる。
以下の例では、&blockという仮引数に、puts "Hello block"
というブロックが渡されている。
def receive_block(&block) puts "受け取ったブロックを実行する" # block.callで、受け取ったブロック(puts "Hello block"のこと)を実行 block.call puts "実行したよ" end receive_block do puts "Hello block" end #出力結果 受け取ったブロックを実行する Hello block 実行したよ
proc
procとは、ブロックという処理のカタマリをオブジェクト化するためのクラスのこと。
callメソッドで呼び出せる。
hello_proc = Proc.new do 'Hello' end # 上記と同じ hello_proc = Proc.new { 'Hello' } hello_proc => #<Proc:0x00007fcbae10af50 (irb):66> hello_proc.call => "Hello"
引数を受け取る処理もできる。
proc = Proc.new do |message| puts message end proc.call("Hello proc") =>Hello proc
以下の処理は、上記の処理と同じ挙動となる。
つまり、以下の&block
とblock
は、上記のブロックとProcインスタンスの関係にあるということ。以下では、内部的に受け取ったブロックをProcインスタンス化しているのである。
def receive_block(&block) block.call end receive_block do puts "Hello block" end =>Hello block
&はブロックを表す記号であり&blockのblockはブロック引数につけた仮引数名にあたるので実は何でも良い。 つまり引数として受け取ったblockという名のブロックを、blockという名のProcインスタンスに変換しているからblock.callで実行することができる。
Rubyのブロック、ブロック引数、Proc、yieldをまとめてみた – Ruby on Rails 始めました
&:method
配列の要素1つ1つに、(&:メソッド名)
で与えられたメソッドを呼び出してくれる。
['a' ,'b'].map(&:upcase) => ["A", "B"]
以下の2つは同じ意味になる。
→do...end に限らず { |object| object.published! } も同じく短く出来る!
find_each do |object| object.published! end find_each(&:published)
以下であれば、array.map(&:メソッド名)
の(&:メソッド名)部分は、:メソッド名.to_proc.call
を省略した形。
# array.map(&:メソッド名)の記法 ['a','b'].map(&:upcase) #=> ["A", "B"] # 省略しない記法 ['a','b'].map do |string| :upcase.to_proc.call(string) end #=> ["A", "B"]
さらに例文で理解する(余計ややこしいかも)
(app/models/article.rb) class Article < ApplicationRecord enum state: { draft: 0, published: 1, publish_wait: 2 } # 公開日時が過去の日付になっている記事を取得 scope :past_publish, -> { where('published_at <= ?', Time.current) } end
(lib/tasks/status_update.rake) namespace :status_update do desc '記事が公開待ち状態で、公開日時が過去の場合、ステータスを「公開」にする' task article_status: :environment do # 公開待ち(publish_wait)の記事の中で、公開日時が過去(past_publish)になっているものがあれば、ステータスを「公開(published!で)」に変更する # `Article.publish_wait`で、enum値がpublish_waitに該当するデータを取得 @articles = Article.publish_wait.past_publish.find_each(&:published!) # 以下と同じ # @articles.each do |article| # if article.published_at.past? # article.state = :published # article.save! # end # end end end
【Rspec】build,create / sequence, trait, transientなど
build, createなどの違い
DBへ保存するか否かなど違いがある。
メソッド | 戻り値 | DB保存 | DB保存(アソシエーション) | ID |
---|---|---|---|---|
build() | モデルインスタンス | x | o | nil |
build_stubbed() | モデルインスタンス | x | x | 適当な値 |
create() | モデルインスタンス | o | o | DB保存された値 |
attributes_for() | パラメータハッシュ | x | x | なし |
sequence(連番)
sequenceで連番を作成できる。
(spec/factories/foods.rb) FactoryBot.define do factory :food do sequence(:name) { |n| "name-#{n}" } calorie { 0 } sequence(:calorie_theory) { |n| "calorie_theory-#{n}" } labels { '' } end end
p build(:food) p build(:food) #<Food id: nil, name: "name-1", labels: "", calorie: 0, calorie_theory: "calorie_theory-1", created_at: nil, updated_at: nil> #<Food id: nil, name: "name-2", labels: "", calorie: 0, calorie_theory: "calorie_theory-2", created_at: nil, updated_at: nil>
trait
複数の属性をグループ化できる。 例えば、userモデルのfactoryデータが存在するとして、あるときは一般ユーザーのデータを、あるときは管理ユーザーのデータを生成したい、という時に便利。
(spec/factories/foods.rb) FactoryBot.define do factory :food do sequence(:name) { |n| "name-#{n}" } calorie { 0 } sequence(:calorie_theory) { |n| "calorie_theory-#{n}" } labels { '' } trait :rice do name { '白ごはん' } calorie { 0 } calorie_theory { '白い食べ物はカロリーが白紙に戻る。' } labels { [''] } end trait :ice_coffee do name { 'アイスコーヒー' } calorie { 0 } calorie_theory { 'コーヒーを抽出している時、実はゼロカロリーの成分だけが抽出されている。' } labels { [''] } end end end
transient
モデルの属性以外のデータを一緒に生成できる。 transientは、各attributeの値にも利用できるし、callbackでも利用できる。
transientは生成した後に参照することはできない。 生成時にパラメータとして渡し、組み立ててファクトリの動的な生成に使える。
FactoryBot(FactoryGirl)チートシート - Qiita
FactoryGirlのtransientとtraitを活用する - Qiita
食べ物のテーブルのFactory。食べ物のジャンルのテーブルと多対多の関係。
(spec/factories/food_genres.rb) FactoryBot.define do factory :food_genre do sequence(:genre_name) { |n| "genre_name-#{n}" } calorie { 0 } sequence(:calorie_theory) { |n| "calorie_theory-#{n}" } end end
食べ物のジャンルのFactory。食べ物のテーブルと多対多の関係。
(spec/factories/foods.rb) FactoryBot.define do factory :food do sequence(:name) { |n| "name-#{n}" } calorie { 0 } sequence(:calorie_theory) { |n| "calorie_theory-#{n}" } labels { '' } # 多対多 trait :with_food_genre do transient do sequence(:genre_name) { |n| "genre_name-#{n}" } sequence(:calorie_theory) { |n| "calorie_theory-#{n}" } # calorie { 0 } end # 中間テーブルを作成する定義を内部で統合 after(:build) do |food, evaluator| food.food_genres << build(:food_genre, genre_name: evaluator.genre_name, calorie_theory: evaluator.calorie_theory) end end end end
中間テーブル。なんも書いてない。
(spec/factories/food_food_genres.rb) FactoryBot.define do factory :food_food_genre do end end
これでテストファイルの中でデータを生成したところ、関連テーブルのデータも生成されていた。
(テストファイル内) let(:food) { create(:food, :with_food_genre) } [6] pry(#<RSpec::ExampleGroups::MealRecordDecorator::DrawingCalorieTheory::Food_2::FoodGenre>)> food => #<Food:0x00007fddf8e920b8 id: 4, name: "name-1", labels: "", calorie: 0, calorie_theory: nil, created_at: Fri, 30 Oct 2020 10:24:06 JST +09:00, updated_at: Fri, 30 Oct 2020 10:24:06 JST +09:00> # 多対多のテーブル [7] pry(#<RSpec::ExampleGroups::MealRecordDecorator::DrawingCalorieTheory::Food_2::FoodGenre>)> food.food_genres => [#<FoodGenre:0x00007fddf4fb7a28 id: 1, genre_name: "genre_name-1", calorie: 0, calorie_theory: "calorie_theory-1", created_at: Fri, 30 Oct 2020 10:24:06 JST +09:00, updated_at: Fri, 30 Oct 2020 10:24:06 JST +09:00>] # 中間テーブル [8] pry(#<RSpec::ExampleGroups::MealRecordDecorator::DrawingCalorieTheory::Food_2::FoodGenre>)> food.food_food_genres => [#<FoodFoodGenre:0x00007fddf8ed24b0 id: 1, food_id: 4, food_genre_id: 1, created_at: Fri, 30 Oct 2020 10:24:06 JST +09:00, updated_at: Fri, 30 Oct 2020 10:24:06 JST +09:00>] # 配列の一つ目を取り出す [9] pry(#<RSpec::ExampleGroups::MealRecordDecorator::DrawingCalorieTheory::Food_2::FoodGenre>)> food.food_genres[0] => #<FoodGenre:0x00007fddf4fb7a28 id: 1, genre_name: "genre_name-1", calorie: 0, calorie_theory: "calorie_theory-1", created_at: Fri, 30 Oct 2020 10:24:06 JST +09:00, updated_at: Fri, 30 Oct 2020 10:24:06 JST +09:00>
【個人開発】ポートフォリオ作成にオススメなtipsやツールをまとめる
自分がポートフォリオ作成に活用したtipsやツールを以下にまとめてみました。
↪︎「こんな課題があるのか〜」と知ることも大事だと思います。
↪︎ツクログで色々な個人開発を見て、アイデアや発想力を養うのもアリだと思います。
↪︎クソアプリをいっぱい見て、脳みそを柔らかくしましょう。
↪︎少しでもBootstrap臭を避けたければ是非。
↪︎無料でアプリのロゴを作成できます。便利な時代ですね。
↪︎トップページのおしゃれなイラストとかを作成できます。 しかも、色とかスタイルとか柔軟に変更できます。
↪︎面倒な利用規約などを自動で作成してくれます。
MVPとは、製品を提供する上で必要最小限の機能のみをもつ、もっともシンプルな製品です。
↪︎個人開発においても、MVPを作成して、ユーザーの反応を見て改善を繰り返すのも大事だと思います。
↪︎どうせならアプリのREADMEを綺麗に整えたいですよね。
↪︎READMEとかでよく見るバッジを作成できる。
【Rails】404、500エラーをハンドリング(slack通知も)
エラーハンドリングのメモが残ってたから、体系的な内容に纏めてみた。
どうせなのでコッチに移しておく。
エラーハンドリングをする
- 404エラー時は、404エラー用のテンプレートを表示させる。
- 500エラー時は例外(エラー)クラス、エラーメッセージ 、バックトレースをログに出力させ、500エラー用のテンプレートを表示させる。
application_controller.rb
にエラー時の設定を書く
→どんなページを表示するか、どんなログを残すかなど。
(application_controller.rb) class ApplicationController < ActionController::Base rescue_from ActiveRecord::RecordNotFound, with: :render_404 rescue_from StandardError, with: :render500 def render_404 render file: Rails.root.join('public/404.html').to_s, status: :not_found, layout: false, content_type: 'text/html' end def render_500(error) logger.error("エラークラス: #{error.class}") logger.error("エラーメッセージ : #{error.message}") logger.error('バックトレース -------------') logger.error(error.backtrace.("\n")) logger.error('-------------') render file: Rails.root.join('public/500.html').to_s, status: :internal_server_error, layout: false, content_type: 'text/html' end end
config.consider_all_requests_localをfalseにするだけで、development環境でエラーページの動作を確認できる
trueに設定すると、どのような種類のエラーが発生しても、詳細なデバッグ情報がHTTPレスポンスに出力される。
developmentとtest環境ではtrue、production環境ではfalseに設定されている。
(config/environments/development.rb) # Show full error reports. # falseにするだけでdevelopment環境でエラーページの動作を確認できる config.consider_all_requests_local = false
意図的にエラーを発生させる
get '*path', to: 'application#render_500' get '*path', to: 'application#render_404'
エラーログの参考資料
エラーログ
log/development.log Started GET "/boards/599" for ::1 at 2020-04-29 11:58:10 +0900 Processing by BoardsController#show as HTML Parameters: {"id"=>"599"} [1m[36mUser Load (2.5ms)[0m [1m[34mSELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?[0m [["id", 43], ["LIMIT", 1]] ↳ vendor/bundle/ruby/2.6.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98 [1m[36mBoard Load (0.2ms)[0m [1m[34mSELECT "boards".* FROM "boards" WHERE "boards"."id" = ? LIMIT ?[0m [["id", 599], ["LIMIT", 1]] ↳ app/controllers/boards_controller.rb:26 Rendering public/404.html Rendered public/404.html (4.7ms) Completed 404 Not Found in 61ms (Views: 16.7ms | ActiveRecord: 2.7ms)
log/development.log Started GET "/login" for ::1 at 2020-04-29 11:59:39 +0900 Processing by UserSessionsController#new as HTML エラークラス: NameError エラーメッセージ : undefined local variable or method `hello' for #<UserSessionsController:0x00007fd3cda92010> バックトレース ------------- Completed 500 Internal Server Error in 219ms (ActiveRecord: 0.0ms) NoMethodError (undefined method `call' for #<Array:0x00007fd3c77ebf88>): app/controllers/application_controller.rb:20:in `error500'
エラーページを作成する
Railsは標準で、public/404.htmlとpublic/422.html、public/500.htmlが作成されているが、以下のサイトなどを使って自前のエラーページを作るのもロマンがあるというか、面白味があると思う。
たった1分で素敵なエラーページが作れるサイト「Better Error Pages」 | ライフハッカー[日本版]
エラー時にslack通知する
gemのインストール。
gem 'slack-notifier' gem'exception_notification'
チャンネルの右上の「iマーク」を押す。
詳細が表示されるから、その他→アプリを追加する。
こんなページへ飛ぶ。
チャンネルのWebhookURLを取得
例外エラーをslackチャンネルに通知するよう設定
$ be rails g exception_notification:install
create config/initializers/exception_notification.rb
(config/initializers/exception_notification.rb) ExceptionNotification.configure do |config| config.add_notifier :slack, { webhook_url: Rails.application.credentials.dig(:slack, :webhook_url), channel: Settings.slack.exception_notification_channel } end
config.add_notifier :slack, webhook_url: Rails.application.credentials.slack[:webhook_url], channel: チャンネル名
settingsファイルで設定もあり
(config/settings/development.yml) slack: exception_notification_channel: '#基礎編通知'
※500番エラー(サーバー側の問題)が発生した時だけ、通知が来るようになっている
→通知が来ることで、改修などを行える
※404などの間違ったURLを入力した等のエラー(クライアント側の問題)で毎回通知が来る必要はない。
credentialsファイルに秘匿情報(チャンネルURL)を保存しておく。
$ EDITOR="vi" bin/rails credentials:edit
# credentialの中身 slack: webhook_url: 取得したURL
config/credentials.yml.enc
に、暗号化した情報が入る(リモート上で管理したくない秘匿情報)。
→そしてmaster_keyに暗号を解くための鍵が入っており、情報の暗号化、複合を行ってくれる。
また、master_keyはバージョン管理対象外となっている。
.gitignore # Ignore master key for decrypting credentials and more. /config/master.key
こんな感じでslackに通知される。
credentials:editを実行できなかった場合 →credentials.yml.encを別フォルダに退避してからrmコマンドで削除し、credentials:editを再実行したら、新しいcredentialsファイルが作成された
【Rails5.2】秘匿情報はsecret.ymlではなくcredentials.yml.encで管理する【初心者】 - Qiita
参考資料
Railsアプリの例外ハンドリングとエラーページの表示についてまとめてみた - Qiita
Rails のエラー処理について知ってる範囲でまとめ - Qiita