- 投稿日:2020-09-06T23:43:41+09:00
mac環境でrailsを起動する(mysql)
今回はrailsを用いたポートフォリオ作成の為に今までcloud9で開発していたのをローカル環境で行う。その備忘とアウトプットです。
今後も掲載していくので何か気付きがありましたら報告よろしくお願いします!rails ruby などなどはすでにインストールを以前済ませた為mysqlの起動から
mysqldのターミナル上での起動
mysql.server startmysqldのターミナル上での起動ステータスの確認
mysql.server statusmysqlが起動していなかった場合
ERROR! MySQL is not runningmysqlが起動していた場合
Starting MySQL
.. SUCCESS!となるmysqlが起動されステータスokであれば
rails sでrailsサーバーを起動するtと以下のログが時発生するので
https://gyazo.com/35a5d81be14535389ca6fce42a2b62e3最終行の
://localhost:3000をコピーしhttp://localhost:3000
ネットワークで検索をかける見覚えのあるこちらの画面が出ればローカル環境でのrails起動が完了する。
- 投稿日:2020-09-06T23:30:15+09:00
Railsチュートリアル テストについて
はじめに
Railsチュートリアルを学習すると、第3章にいかに「自動化テスト」が重要であるか書いてあります。テストの書き方については色々議論の余地があるそうですが、本記事ではテストについて自分なりに抑えていたほうが良いと思われることをまとめました。
テストフレームワークについて
テストフレームワークにもいくつか種類があり、それぞれコード書き方等の特徴があります、ここでは主だった2種類のフレームワークを紹介します。
参考にしました →Railsの人気テストフレームワーク6選!
RSpecとMinitest、使うならどっち?Minitest
・Railsのデフォルトのフレームワーク。
・学習コストは比較的低い。
・Railsチュートリアルでも使用されている。
・処理速度は比較的早いといわれている。
・デフォルトの機能は必要最小限。
・コード量は少なく、シンプルRspec
・自然言語で近い方たちで書かれている。
・利用率が高い。
・慣れるまでが時間がかかる。
・Minitestに比べると処理速度は遅いといわれている。
・デフォルトの機能は豊富。
・コード量は多くて、複雑。どちらも一長一短みたいですね。初心者はMinitestから入って、その後Rspecも学ぶのが一番ですかね。
テストの種類について
参考する文献によって2種類だったり、それ以上だったりしますが今回は2種類のテストについて紹介します。
参考にしました →Rails テスティングガイド
ソフトウェアテスト -wilki単体テスト
関数やメソッドなど小さな単位で行うテスト。モデルやビューヘルパー単体の動作をチェックします。
例:
test/helpers/application_helper_testtest "full title helper" do assert_equal full_title, "Ruby on Rails Tutorial Sample App" endassertとはオブジェクトまたは式を評価して、期待された結果(true)が得られるかどうかをチェックするコードです。
上記はassert_equalがfull_titleが"Ruby on Rails Tutorial Sample App"と完全一致しているかどうか検証しています。test/models/user_test.rbtest "name should not be too long" do @user.name = "a" * 51 assert_not @user.valid? endassert_notとは、assertとは逆にfalseが得られるかどうかをチェックするコードです。
上記はユーザーの名前が51文字の場合、ユーザーが有効でないことを検証しています。統合テスト
プログラムを組み合わせて行うテスト。個々の機能が正しく連動しているか、ユーザーの実際の操作を想定してチェックします。
例:
test/integration/users_login_testtest "login with valid email/invalid password" do get login_path assert_template 'sessions/new' post login_path,params: {session: { email: @user.email, password: "invalid"}} assert_not is_logged_in? assert_template 'sessions/new' assert_not flash.empty? get root_path assert flash.empty? end上記は、有効でないパスワードを入力したユーザーに対するテストを行っています。
1. getメソッドでログインページ(login_path)を生成する。
→get login_path2. ログインページのテンプレートが選択されているか検証する。
→assert_template 'sessions/new'3. login_pathへ有効なメールアドレスと有効でないパスワードを投稿する。
→post login_path,params: {session: { email: @user.email,password: "invalid"}}4. ユーザーがログインしていないことを検証する。
→assert_not is_logged_in?is_logged_in?メソッドは下記により定義されています。
test/test_helper.rb#テストユーザーがログイン中の場合にtrueを返す def is_logged_in? !session[:user_id].nil? end5. ログインページのテンプレートが選択されているか検証する。
→assert_template 'sessions/new'6. エラーメッセージが表示されていることを検証する。
→assert_not flash.empty?7. getメソッドでホームページ(root_path)を生成する。
→get root_path8. エラーメッセージが表示されていないことを検証する。
→assert flash.empty?という流れになっています。
まとめ
正直Railsチュートリアル終えても自力でテストをかけるか自信がないです。また、テストはどこまで厳密にやるかセンスが問われそうですね。数をこなしてなれていくのが大事だと思いました。
- 投稿日:2020-09-06T22:10:07+09:00
Rails 6で認証認可入り掲示板APIを構築する #1 環境構築
はじめに
Rails APIに関して、チュートリアルがあまり見当たらなかったため作成しました。
なお、筆者自身もRails API触り始めて半年程度なので、誤っている点などあればご指摘ください。Rails APIモードを使い、掲示板APIを構築してみようと思います。
なお、フロントエンドは本チュートリアルに含みません。
API完成後、ご自由なものを選択してください。対象読者
- Ruby on Railsチュートリアルを1周し、Railsアプリケーションの実装方法や用語がなんとなく分かっている方
- モノリシックなRailsではなく、RailsのAPIサーバを構築したい方
RailsのCRUD操作に関する最低限の知識はないと、追いつくのは難しいかもしれません。
最終的な環境・Gem
AWS Cloud9
PostgreSQL 9.5.15
Ruby 2.7.1
Ruby on Rails 6.0.3.2Gem
シリアライザ:active_model_serializers
認証:devise_token_auth
認可:pundit
ダミーデータ:Faker
静的コード解析:rubocop
テスト:RSpec, FactoryBot
(記事執筆中なので、今後増減の可能性あり)Cloud9環境の構築
まずはAWSアカウントをお持ちでない場合、アカウントを取得しましょう。
本記事ではアカウントを所持していること前提として解説をします。分かりやすい名前と説明を入れ作成します。
2ページ目は基本的に全てデフォルトで問題ないでしょう。構築に数分かかるので放置します。
完了したらこんなIDE画面が表示されます。Rubyのバージョンアップ
最初からRubyもRailsも入っていますが、バージョンが古いですね。
$ ruby -v ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux] $ rails -v Rails 5.0.0記事執筆当時の安定バージョンは2.7.1なので上げましょう。
$ rvm install 2.7.1 ... $ rvm use 2.7.1 $ ruby -v ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-linux]無事上がりました。
参考:【AWS】Cloud9でRubyのバージョンをアップデートする方法(rvm使用)
Railsのバージョンを上げる
こちらも、記事執筆当時最新が6.0.3に対し、インストールされているのが5.0.0とだいぶ古いので上げましょう。
$ gem install rails ... $ rails -v Rails 6.0.3.2簡単ですね。
postgreSQLを入れる
なぜpostgresをこのタイミングで入れるか?
Railsのデフォルト開発DBはSQLiteですが、Amazon LinuxのCloud9環境だとSQLiteのバージョンが低くアプリケーションが作れません。バージョン上げがなかなかしんどいので、今回はPostgreSQLを使います。
また、SQLiteはその名の通り機能的にもliteなので、他のRDBMSに比べてできないことも多く、無駄に詰まることがあるので、それを回避するのも要因の一つです。
まずインストールします。
$ sudo yum install postgresql95-devel postgresql95-server postgresql95-contrib $ psql --version psql (PostgreSQL) 9.5.15次に初期化と起動、ユーザー作成。Permission deniedは無視でOKです。
$ sudo service postgresql95 initdb Initializing database: sudo service postgresql95 start [ OK ] $ sudo service postgresql95 start Starting postgresql95 service: [ OK ] $ sudo -u postgres createuser -s ec2-user could not change directory to "/home/ec2-user/environment": Permission denied参考:[Rails6]AWS Cloud9(Amazon Linux)で動かしてみる
APIモードでrails newする
$ rails new bbs -d postgresql --api--apiと付けることでRails APIモードとなります。
APIに不要なファイルが生成されない状態で作られます。Railsのテストサーバを立ち上げる
New Terminalから新しいターミナルを立ち上げます。
ローカル等では
rails sだけでテストサーバが立ち上がるのですが、Cloud9はオプションが必要です。$ cd bbs/ $ rails s -b $IP -p $PORT ... * Listening on tcp://127.0.0.1:8080 Use Ctrl-C to stopこれでサーバが立ち上がります。
なお、書いてあるとおりCtrl+Cで止めることができます。
port8080で動いていることが分かりますね。先程のターミナルに戻り、ちゃんと立ち上がったかcurlで確認してみましょう。
テストサーバを立ち上げたターミナルウィンドウは、止めずに放置してください。$ cd bbs/ $ curl localhost:8080 {"status":500,"error":"Internal Server Error","exception":"#\u003cActiveRecord::NoDatabaseError: FATAL: database \"bbs_development\" does not existどうやらDBが無くて怒られてます。
railsでDBの初期化をしましょう。$ rails db:create ... Created database 'bbs_development' Created database 'bbs_test' $ curl localhost:8080なんかhtmlっぽい大量の文字列が出てきたら、とりあえず500系エラーは起きていないので大丈夫です。
IDEの初期設定をする
インデントはspace2にしたいので、画面右端にある歯車マークから設定画面へ。
Code Editor (Ace)を選択し、Soft Tabsを4から2に偏向
On Save, Strip Whitespaceして閉じます。
ホストを許可する
Rails6から、ホストを許可しないとエラーでページが表示されません。
Cloud9で実行しているので、AWSのhostsを追加します。
保存したらテストサーバを一度Ctrl+Cで止め、再起動してください。そうしないとconfigが反映されません。config/application.rb... module Bbs class Application < Rails::Application ... + config.hosts << '.amazonaws.com' ... end endWelcome画面を表示する
画面上部からPreview Running Applicationを押すと、小さくアプリケーションウィンドウが立ち上がります。
ですがこの小さいウィンドウでは正常に実行されないため、Pop Out Into New Windowを押して別ウィンドウを立ち上げます。
するとついに、正常にWelcome画面が表示されます。
続き
- 投稿日:2020-09-06T21:23:52+09:00
can't find gem bundler (>= 0.a) with executable bundleの対処法
既存のRailsプロジェクトをcloneし、
% bundle installをした際にこのエラーが発生。% bundle install Traceback (most recent call last): 2: from /Users/trilingual/.rbenv/versions/2.5.1/bin/bundle:23:in `<main>' 1: from /Users/trilingual/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems.rb:308:in `activate_bin_path' /Users/triringual/.rbenv/versions/2.5.1/lib/ruby/2.5.0/rubygems.rb:289:in `find_spec_for_exe': can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)原因
Gemfile.lock最下部に記載の
BUNDLED WITH2.0.2の記述と自身がインストールしたbundlerのバージョンと異なってしまっていることが原因。Gemfile.lock<省略> BUNDLED WITH 2.1.4これと
% bundler -v Bundler version 1.17.1これが違うということです。
解決策
①アプリケーションのGemfile.lock最下部に記載の
BUNDLED WITH○○の記載を確認Gemfile.lock<省略> BUNDLED WITH 2.1.4②Gemfile.lockに記載のバージョンをインストールする(今回は
2.1.4)% gem install bundler -v 2.1.4まとめ
アプリケーションで想定している環境と自身のPCの環境
を合わせてあげるようなイメージです。
・PCを買い替えてアプリを開いたとき
・アプリをgit cloneしたとき
・Rubyのバージョンを変更したとき
のようなパターンでこのエラーが頻発しているように感じます。同じエラーが発生した方へ届きますように!
- 投稿日:2020-09-06T20:55:36+09:00
[rails]ネストした関係でどうやって親情報を表示させるか
やりたいこと
前提・Itemの情報がないとOrderの情報は成り立たない関係
(Orderにitem_idカラムがある(どのitem情報を指しているのか?))
(親)itemテーブル>(子)orderテーブルになる。orderコントローラでindexアクションを起動させ(index.html.erb)に
(親)のitem情報を表示させたかったらどうやって記述するの?
※例えば
itemテーブル
id:1
ぬいぐるみ4000円orderテーブル
item_id:1
user_id:3
ぬいぐるみ4000円とuser_idが結びつくんだな・・・手順
config/routes.rbの記述内容 Rails.application.routes.draw do devise_for :users root 'items#index' resources :items do resources :orders, only: [:create, :index] end end1.親items>子ordersの関係をroutes.rbで記述する
resources :items do resources :orders, only: [:create, :index]これで(親)item>(子)orderの関係を指定することができる。ネストすることができる
2.ordersコントローラーで@item = Item.find(params[item_id])と記述する
def index @item = Item.find(params[:item_id]) end@itemの箱の中にitemテーブルの情報を入れる。
どのitemテーブルの情報を指定するか[:item_id]で指定できる
ユーザーが選択したitem情報を@itemに代入する3.@itemをorder#indexのビューファイルに記述する
orderコントローラーのindex.html.erbに記述 <%= @item.item_name %>これで(親)itemテーブルからデーターを抜き出して
(子)orderコントローラーのindex.html.erbのビューに表示することができる。まとめ
分かりにくいまとめですが
要は親テーブルの情報を抜き出して、子のビューに表示させたかったら
ネストを使って関係を示し、子コントローラーでparams[:親_id]と記述すれば
親から情報を抜き出して子のビュー表示することができるという内容でした。うまくまとめることができないのは私の力不足です・・・。
具体的な画像とテーブル情報が添付していると分かりやすくなったかも。
- 投稿日:2020-09-06T20:55:36+09:00
[rails]ネストした関係で親情報を子ビューに表示する方法
やりたいこと
前提・Itemの情報がないとOrderの情報は成り立たない関係
(Orderにitem_idカラムがある(どのitem情報を指しているのか?))
(親)itemテーブル>(子)orderテーブルになる。orderコントローラでindexアクションを起動させ(index.html.erb)に
(親)のitem情報を表示させたかったらどうやって記述するの?
※例えば
itemテーブル
id:1
ぬいぐるみ4000円orderテーブル
item_id:1
user_id:3
ぬいぐるみ4000円とuser_idが結びつくんだな・・・手順
config/routes.rbの記述内容 Rails.application.routes.draw do devise_for :users root 'items#index' resources :items do resources :orders, only: [:create, :index] end end1.親items>子ordersの関係をroutes.rbで記述する
resources :items do resources :orders, only: [:create, :index]これで(親)item>(子)orderの関係を指定することができる。ネストすることができる
2.ordersコントローラーで@item = Item.find(params[item_id])と記述する
def index @item = Item.find(params[:item_id]) end@itemの箱の中にitemテーブルの情報を入れる。
どのitemテーブルの情報を指定するか[:item_id]で指定できる
ユーザーが選択したitem情報を@itemに代入する3.@itemをorder#indexのビューファイルに記述する
orderコントローラーのindex.html.erbに記述 <%= @item.item_name %>これで(親)itemテーブルからデーターを抜き出して
(子)orderコントローラーのindex.html.erbのビューに表示することができる。まとめ
分かりにくいまとめですが
要は親テーブルの情報を抜き出して、子のビューに表示させたかったら
ネストを使って関係を示し、子コントローラーでparams[:親_id]と記述すれば
親から情報を抜き出して子のビュー表示することができるという内容でした。うまくまとめることができないのは私の力不足です・・・。
具体的な画像とテーブル情報が添付していると分かりやすくなったかも。
- 投稿日:2020-09-06T20:18:34+09:00
railsのsessionデータ更新でzero-length delimited identifier at or near """"のエラー
開発環境である時見慣れぬエラーが出て、もしも本番で発生したらどうすればいいのか途方に暮れそうなので、復旧手順を試行錯誤したメモです。同じエラーが出た場合にヒントになればと思うのですが、同じ状況とは限らないのでご注意。
環境 rails 5.2.4.3 postgresql 12.2 pg 1.2.2 activerecord-session_store 1.1.3
状況
ログインするサイトで、ブラウザからアクセスした際に以下のエラーが出て、サイトをまったく利用できない。development.logActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: zero-length delimited identifier at or near """" LINE 1: ... SET "data" = $1, "updated_at" = $2 WHERE "sessions"."" = $3 : UPDATE "sessions" SET "data" = $1, "updated_at" = $2 WHERE "sessions"."" = $3) : activerecord (5.2.4.3) lib/active_record/connection_adapters/postgresql_adapter.rb:611:in `exec_params' activerecord (5.2.4.3) lib/active_record/connection_adapters/postgresql_adapter.rb:611:in `block (2 levels) in exec_no_cache' ...ログに羅列されているファイル名には自分が書いたファイルはなく心当たりなし。昨日何やったっけ。とりあえずこの状態のDBをダンプして保存。
セッションの情報はactiverecord-session_storeでactiverecordに記録していて、ユーザがアクセスするとこの情報を更新する。その際にこのエラーが出ている。
正常に動いている本番データと比べると、whereのところは本来は
WHERE "sessions"."id" = $3 のはずであることがわかった。activerecord-5.2.4.3/lib/active_record/connection_adapters/abstract/database_statements.rb
までは追っかけて、発行するsqlを生成する関数の入力値arelでname="id"であるところが、エラーが出るパターンではname=""になっていたところまではわかったが、そこから遡るのは断念。pg_dumpしてpsqlでloadしても同じエラー。そこで、pg_dumpをデータだけにして、dbは一度初期化、migrateしてデータを流し込んだら復旧できた。復旧後改めてpg_dumpして比較すると、
ALTER TABLE ONLY public.sessions ADD CONSTRAINT sessions_pkey PRIMARY KEY (id);が欠けていた。どこで/何をしてこれが消えたのか、心当たりなし。
sessionsのテーブルだけであれば、邪道っぽいけど、以下のようにactiverecord-session_store適用でやったのと同じmigrationを一時的に用意し、
class ResetSessionTable < ActiveRecord::Migration[5.2] def up drop_table :sessions create_table :sessions do |t| t.string :session_id, :null => false t.text :data t.timestamps end add_index :sessions, :session_id, :unique => true add_index :sessions, :updated_at end def down end end# pg_dump -b DB名 --data-only --table sessions >sessions_bak.sql # ここで上記をdb:migrateしてすぐにdb:rollbackしてファイルを削除 # cat sessions_bak.sql |/usr/local/pgsql/bin/psql -e DB名こんな感じで停止もそこそこで復旧できる見通しはついた。もっと良い対応があるかもしれない。
結局発生の原因はわかっていないが、開発環境は初期化とかデータロードを頻繁に行っているため、そこで何らかエラーがあった可能性が高いけど、pgのバージョンの制約とか、他のgemとかの影響の可能性もなくはないので、情報共有で投稿してみました。
- 投稿日:2020-09-06T20:05:48+09:00
Rails+mecab+Herokuでデプロイエラーが発生した場合の対策
事象
- Rails(6.0.0)
- Heroku
- Heroku Buildpacksにmecab(https://github.com/diasks2/heroku-buildpack-mecab.git)を追加
この条件で、Herokuでデプロイを行った場合、エラーが発生するケースがあると思います。
調査
デプロイエラーの内容は下記のようです。
! To update to the latest version installed on your system, run `bundle update --bundler`. ! To install the missing version, run `gem install bundler:2.0.2`確かに、Gemfile.lockに下記のようにbundlerのバージョン指定をしています。
BUNDLED WITH 2.0.2そこで、Gemfile.lockのBUNDLED WITH指定を手作業で削除し、再度Herokuでデプロイします。
⇒正常にデプロイ完了します。しかし、Heroku Buildpacksからmecabを外し、BUNDLED WITH 2.0.2指定を復活させ、再度Herokuでデプロイしてみたところ…
⇒正常にデプロイ完了します。解せません。つまり、下記のような状態です。
- mecabあり mecabなし BUNDLED WITH 2.0.2 デプロイNG デプロイOK BUNDLED WITH なし デプロイOK デプロイOK mecabとbundler v2.0.2は相性が悪いように読み取れます。
根本解決になっておらず恐縮ですが、
Rails+mecab+Herokuという(マニアックな)組み合わせでアプリケーションを作りたい場合、この事象に遭遇した際は、
手作業でGemfile.lockのBUNDLED WITH指定を削除すると、この事象を突破できると思います。備忘
Config versに
MECAB_PATH=/app/vendor/mecab/lib/libmecab.soの設定をお忘れなく!
- 投稿日:2020-09-06T19:02:24+09:00
文字コードの指定ミスによる、マイグレーションファイルが実行できないエラーについて。
エラーが発生いたしました。
An error has occurredマイグレーションファイルの実行をした時です。
rails db:migrate:Error: Specified key was too long; max key length is 767 bytesキーの長さが最大767バイトを超えてしまったとエラー文です。
直前の自身の操作を思い返してみます。
t.string :name, null: falseキーについてマイグレーションファイルに記述を行なっていました。
では、このキー(:name)が怪しいです。
名前の文字数に関連する何かであると考えられます。エラー文を検索すると以下の記事に詳細が書かれていました。
https://qiita.com/terufumi1122/items/9ea764618eba01144e09RailsやCakePHPのデフォルトで生成される255文字のVARCHARでは、3バイトのutf8では255×3=765でほぼぴったり収まります。一方、utf8mb4ではオーバーしてしまうので、エラーとなりました。
ここでVARCHARとは、データベースのフィールド定義などで用いられるデータ型の一つで、可変長の文字列を意味するデータ型のことです。
http://e-words.jp/w/VARCHAR.html#:~:text=VARCHAR%E3%81%A8%E3%81%AF%E3%80%81%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E3%81%AE,%E3%82%92%E6%84%8F%E5%91%B3%E3%81%99%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B%E3%80%82つまり推測になりますが、カラムの型と、そのキーを記述してマイグレーションファイルを実行するということは、
そのキー(テーブルにおけるカラム)について767バイト分のデータ容量を確保するといことになるのでしょうか。
可変長ということなので、レコードごとにその容量は異なりますが、767バイトは超えてはいけなく、かつnull: falseによって0バイトでもいけないということでしょう。ここでもう一度、上記の引用です。
RailsやCakePHPのデフォルトで生成される255文字のVARCHARでは、3バイトのutf8では255×3=765でほぼぴったり収まります。一方、utf8mb4ではオーバーしてしまうので、エラーとなりました。Railsにおいて、255文字分のVARCHARは自動生成され、
そして、一文字ごとのデータの大きさは文字コードによって異なるみたいです。文字コードutf8は255文字生成されたとしてもそのデータの大きさは765バイト、可変長容量内です。
よって今回、文字コードの指定がutf8mb4となっていると考えられます。
アプリケーションの開発において、
文字コードの記述があるパスは、
config/database.ymlです。行ってみると
encoding: utf8mb4と記述がされていました。
ecoding: utf8に記述を書き直し。
再度マイグレーションファイルの実行をします。
:Error: Table 'users' already exists不安を覚えましたので
Sequel Proを確認しにいきます、
文字コードに関連しているだろう記述がある部分を見つけました。
utf8mb4のままの部分がございます。
データベースのリセットを一度行います。
rails db:resetその上でマイグレーションファイルを実行いたします。
rails db:migrate今回はエラー文が表示されませんでした。

再度、確認しに行きますと
しっかりとutf8となっています。以上でエラー解決とさせていただきます。
最後まで、お読み頂きありがとうございます。
誤った認識をしている箇所、認識が不足している部分について、ご指摘して頂けますと幸いに存じます。
- 投稿日:2020-09-06T18:39:26+09:00
simple_formを使ってみる/子モデルまで編集する
概要
simple_formは高機能なフォームを自動作成してくれるgemです。https://github.com/heartcombo/simple_form
フォームを作ってくれるだけでなく、エラーメッセージや項目名なども自動で表示します。今回はテーブルが2つのシンプルなrailsプロジェクトで、
simple_formを使ってみました※bootstrapやCSSは書いていません
実装
まずは一番シンプルな実装userモデルのみ実装します
gemfilegem 'simple_form'user.rbclass User < ApplicationRecord validates :name, presence: true validates :email, presence: true validates :age, presence: true endroutes.rbresources :usersusers_controller.rbclass UsersController < ApplicationController def new @user = User.new end def create @user = User.new(user_params) if @user.save redirect_to user_path(@user) else render 'users/new' end end private def user_params params.require(:user).permit( :name, :email, :age, :date ) end下記migrationファイルを実行します。
rails db:migratedb/migrate/20200906_create_users.rbclass CreateUsers < ActiveRecord::Migration[5.1] def change create_table :users do |t| t.string :name t.string :email t.integer :age t.datetime :date t.string :url t.timestamps end end endviewファイル(new)にsimple_formを書きます。ほぼreadmeのコピペです
app/views/users/new.html.erb<%= simple_form_for user do |f| %> <%= f.input :name %> <%= f.input :email %> <%= f.input :age, collection: 18..60 %> <%= f.input :date %> <%= f.input :url %> <%= f.button :submit %> <% end %>これでフォームは表示されます。localhost:3000/users/newにアクセスした結果が以下です。
presenceのバリデーションを設定している項目にはcan't be blank と自動で表示されています。
また、emailとurlはカラム名からsimple_formが判断して、各々最適なバリデーションをしてくれます。
吹き出しのデザインも勝手にsimple_formが生成してくれたものです。
また、dateとageの項目を見れば分かるように、型に応じて入力形式を勝手に変えてくれます
ここに書いたのは一例で、
その他 colorやcountryなど、様々な入力パターンが用意されています。(reademeに書いてあります)
simple_formの使い方はこんな感じです。子モデルを一緒に編集したい場合
userに紐ついている子モデルを、userモデルから編集することができます。
userモデルが主であるようなページで同時に編集したい場合に使います。今回は user_propertyという子モデルを用意します。
user_properties_controller のcreateアクションではなく、
users_controllerのcreate で編集をできるようにします。(updateも同様ですまたそれによってsubmitボタンを一つにできます。
下に先ほどから変化のあっるファイルを示します。
user_property.rbclass UserProperty < ApplicationRecord belongs_to :user validates :nickname, presence: true validates :hobby, presence: true enduser.rbclass User < ApplicationRecord validates :name, presence: true validates :email, presence: true validates :age, presence: true has_one :user_property accepts_nested_attributes_for :user_property endusers_controller.rbclass UsersController < ApplicationController def new @user = User.new @user.create_user_property(nickname: "takashi", hobby: "yamamoto") end private def user_params params.require(:user).permit( :name, :email, :age, :date, user_property_attributes: %i[ nickname hobby ] ) end下記migrationファイルを実行します。
rails db:migratedb/migrate/20200906_create_user_property.rbclass CreateUserProperties < ActiveRecord::Migration[5.1] def change create_table :user_properties do |t| t.integer :user_id t.string :nickname t.string :hobby t.timestamps end end endsimple_formは以下のようにかきます。
----user_proiperty-----以下が追加した項目です。
simple_field_forを使います。app/views/users/new.html.erb<%= simple_form_for user do |f| %> <%= f.input :name %> <%= f.input :email %> <%= f.input :age, collection: 18..60 %> <%= f.input :date %> <%= f.input :url %> -------user_property---------- <%= f.simple_fields_for :user_property, user.user_property do |ff| %> <%= ff.input :nickname %> <%= ff.input :hobby %> <% end %> <%= f.button :submit %> <% end %>これでできたのが以下です
user_propertyの項目が二つ追加されています。これで完了です。
以上です
- 投稿日:2020-09-06T18:05:29+09:00
(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第4章】
前提
・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者基本方針
・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。第4章です。この章は冗長な内容だった記憶が…。つべこべ言わずやっていきます。
本日のBGMはこちら。
matryoshka "zatracenie[full album]"
【4.1.2 カスタムヘルパー メモ】
・新しく作ったメソッド=カスタムヘルパー
・全てのページで使うヘルパー:app/helpers/application_helper.rb に設置
・特定のコントローラだけが使うヘルパー:app/helpers/(該当のコントローラ名).rb に設置
【4.2.2 文字列 メモと演習】
・putsメソッドは戻り値にnilを返す。改行文字である\nが出力の末尾に追加される。
・printメメソッドは改行文字を追加しない。
・シングルクォート内では式展開できない。入力した文字をエスケープせずに、そのまま保持するときに便利1. city変数に適当な市区町村を、prefecture変数に適当な都道府県を代入してください。
→ city = "sapporo" prefecture = "hokkaido"(今コンサドーレ札幌の試合観ながらやってるので)2. 先ほど作った変数と式展開を使って、「東京都 新宿区」のような住所の文字列を作ってみましょう。出力にはputsを使ってください。
→ 下記>> puts prefecture + " " + city hokkaido sapporo => nil3. 上記の文字列の間にある半角スペースをタブに置き換えてみてください。(ヒント: 改行文字と同じで、タブも特殊文字です)
→ 下記>> puts prefecture + "\t" + city hokkaido sapporo => nil4. タブに置き換えた文字列を、ダブルクォートからシングルクォートに置き換えてみるとどうなるでしょうか?
→ 下記>> puts prefecture + '\t' + city hokkaido\tsapporo => nil
【4.2.3 オブジェクトとメッセージ受け渡し 演習】
1. "racecar" の文字列の長さはいくつですか? lengthメソッドを使って調べてみてください。
→ 下記>> s = "racecar" => "racecar" >> s.length => 72. reverseメソッドを使って、"racecar"の文字列を逆から読むとどうなるか調べてみてください。
→ 下記>> s.reverse => "racecar"3. 変数sに "racecar" を代入してください。その後、比較演算子 (==) を使って変数sとs.reverseの値が同じであるかどうか、調べてみてください。
→ もう入れとったわ。下記>> s == s.reverse => true4. リスト 4.9を実行すると、どんな結果になるでしょうか? 変数sに "onomatopoeia" という文字列を代入するとどうなるでしょうか? ヒント: 上矢印 (またはCtrl-Pコマンド) を使って以前に使ったコマンドを再利用すると一からコマンドを全部打ち込む必要がなくて便利ですよ。)
→ 下記>> puts "It's a palindrome!" if s == s.reverse It's a palindrome! => nil>> s = "onomatopoeia" => "onomatopoeia" >> puts "It's a palindrome!" if s == s.reverse => nil
【4.2.4 メソッドの定義 演習】
1. リスト 4.10のFILL_INの部分を適切なコードに置き換え、回文かどうかをチェックするメソッドを定義してみてください。ヒント: リスト 4.9の比較方法を参考にしてください。
→ 下記>> def palindrome_tester(s) >> if s == s.reverse >> puts "It's a palindrome!" >> else >> puts "It's not a palindrome." >> end >> end => :palindrome_tester2. 上で定義したメソッドを使って “racecar” と “onomatopoeia” が回文かどうかを確かめてみてください。1つ目は回文である、2つ目は回文でない、という結果になれば成功です。
→ 下記>> palindrome_tester("racecar") It's a palindrome! => nil >> palindrome_tester("onomatopoeia") It's not a palindrome. => nil3. palindrome_tester("racecar")に対してnil?メソッドを呼び出し、戻り値がnilであるかどうかを確認してみてください (つまりnil?を呼び出した結果がtrueであることを確認してください)。このメソッドチェーンは、nil?メソッドがリスト 4.10の戻り値を受け取り、その結果を返しているという意味になります。
→ 下記>> palindrome_tester("racecar").nil? It's a palindrome! => true
【4.3.1 配列と範囲演算子 演習】
1. 文字列「A man, a plan, a canal, Panama」を ", " で分割して配列にし、変数aに代入してみてください。
→ 下記>> a = "A man, a plan, a canal, Panama".split(',') => ["A man", " a plan", " a canal", " Panama"]
2. 今度は、変数aの要素を連結した結果 (文字列) を、変数sに代入してみてください。
→ 下記>> s = a.join => "A man a plan a canal Panama"3. 変数sを半角スペースで分割した後、もう一度連結して文字列にしてください (ヒント: メソッドチェーンを使うと1行でもできます)。リスト 4.10で使った回文をチェックするメソッドを使って、(現状ではまだ) 変数sが回文ではないことを確認してください。downcaseメソッドを使って、s.downcaseは回文であることを確認してください。
→ ひとまとめにするとこうなる(一回コンソール落としてたからメソッドの再定義めんどくさかったやん!)>> palindrome_tester(s.split(' ').join.downcase) It's a palindrome! => nil4. aからzまでの範囲オブジェクトを作成し、7番目の要素を取り出してみてください。同様にして、後ろから7番目の要素を取り出してみてください。(ヒント: 範囲オブジェクトを配列に変換するのを忘れないでください)
→ 最初は0から始まるから6ですね。>> A = ("a".."z").to_a => ["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"] >> A[6] => "g" >> A[-7] => "t"
【4.3.2 ブロック 演習】
1. 範囲オブジェクト0..16を使って、各要素の2乗を出力してください。
→ 下記>> (0..16).each { |i| puts i**2 } 0 1 4 9 16 25 36 49 64 81 100 121 144 169 196 225 256 => 0..16
2. yeller (大声で叫ぶ) というメソッドを定義してください。このメソッドは、文字列の要素で構成された配列を受け取り、各要素を連結した後、大文字にして結果を返します。例えばyeller(['o', 'l', 'd'])と実行したとき、"OLD"という結果が返ってくれば成功です。ヒント: mapとupcaseとjoinメソッドを使ってみましょう。
→ 下記。これもっと賢いやり方ないかな。引数に配列じゃなくて文字列入れるだけでいいようにするとか。>> def yeller(s) >> s.map(&:upcase).join >> end => :yeller >> yeller(['o','l','d']) => "OLD"3. random_subdomainというメソッドを定義してください。このメソッドはランダムな8文字を生成し、文字列として返します。ヒント: サブドメインを作るときに使ったRubyコードをメソッド化したものです。
→ 下記>> def random_subdomain >> ("a".."z").to_a.shuffle[0..7].join >> end => :random_subdomain >> random_subdomain => "unwdemsp"4. リスト 4.12の「?」の部分を、それぞれ適切なメソッドに置き換えてみてください。ヒント:split、shuffle、joinメソッドを組み合わせると、メソッドに渡された文字列 (引数) をシャッフルさせることができます。
→ 下記>> def string_shuffle(s) >> s.split('').shuffle.join >> end => :string_shuffle >> string_shuffle("foobar") => "arbfoo"
【4.3.3 ハッシュとシンボル 演習】
1. キーが'one'、'two'、'three'となっていて、それぞれの値が'uno'、'dos'、'tres'となっているハッシュを作ってみてください。その後、ハッシュの各要素をみて、それぞれのキーと値を"'#{key}'のスペイン語は'#{value}'"といった形で出力してみてください。
→ 下記>> n = { one: 'uno', two: 'dos', three: 'tres' } => {:one=>"uno", :two=>"dos", :three=>"tres"} >> n.each do |key, value| ?> puts "#{key}のスペイン語は#{value}" >> end oneのスペイン語はuno twoのスペイン語はdos threeのスペイン語はtres => {:one=>"uno", :two=>"dos", :three=>"tres"}
2. person1、person2、person3という3つのハッシュを作成し、それぞれのハッシュに:firstと:lastキーを追加し、適当な値 (名前など) を入力してください。その後、次のようなparamsというハッシュのハッシュを作ってみてください。1.) キーparams[:father]の値にperson1を代入、2). キーparams[:mother]の値にperson2を代入、3). キーparams[:child]の値にperson3を代入。最後に、ハッシュのハッシュを調べていき、正しい値になっているか確かめてみてください。(例えばparams[:father][:first]がperson1[:first]と一致しているか確かめてみてください)
→ 下記。それぞれの名前とキーの意味が分かるアナタはガンバサポーター。(2020シーズン第14節のスタメンより)>> person1 = { first: "Yuki", last: "Yamamoto" } => {:first=>"Yuki", :last=>"Yamamoto"} >> person2 = { first: "Yosuke", last: "Ideguchi" } => {:first=>"Yosuke", :last=>"Ideguchi"} >> person3 = { first: "Shu", last: "Kurata" } => {:first=>"Shu", :last=>"Kurata"} >> params = { anchor: person1, rih: person2, lih: person3 } => {:anchor=>{:first=>"Yuki", :last=>"Yamamoto"}, :rih=>{:first=>"Yosuke", :last=>"Ideguchi"}, :lih=>{:first=>"Shu", :last=>"Kurata"}} >> params[:anchor][:first] == person1[:first] => true
3. userというハッシュを定義してみてください。このハッシュは3つのキー:name、:email、:password_digestを持っていて、それぞれの値にあなたの名前、あなたのメールアドレス、そして16文字からなるランダムな文字列が代入されています。
→ 下記>> user = { name: "tk", email: "tk@mail.com", password_digest: ("a".."z").to_a.shuffle[0..15].join } => {:name=>"tk", :email=>"tk@mail.com", :password_digest=>"socxlgerjatyinbw"}
4. Ruby API (訳注: もしくはるりまサーチ) を使って、Hashクラスのmergeメソッドについて調べてみてください。次のコードを実行せずに、どのような結果が返ってくるか推測できますか? 推測できたら、実際にコードを実行して推測があっていたか確認してみましょう。{ "a" => 100, "b" => 200 }.merge({ "b" => 300 })→ 正直、るりまサーチで調べてもよく分かんないんですよね。日本語が日本語じゃない。なので普通にグーグル先生に頼ると、mergeメソッドは複数のハッシュを結合させるメソッドとのこと。そして、merge「前」のハッシュに、merge「後」のハッシュを結合するのですが、重複するハッシュがある場合は、「後」のハッシュが上書きされます。ということは、上の結果はbが300になるはず。実際の結果は下記。合ってますね。
>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 }) => {"a"=>100, "b"=>300}
【4.4.1 コンストラクタ 演習】
1. 1から10の範囲オブジェクトを生成するリテラルコンストラクタは何でしたか? (復習です)
→ 下記>> r = 1..10 => 1..10
2. 今度はRangeクラスとnewメソッドを使って、1から10の範囲オブジェクトを作ってみてください。ヒント: newメソッドに2つの引数を渡す必要があります。
→ 下記>> r2 = Range.new(1,10) => 1..10
3. 比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。
→ 下記>> r == r2 => true
【4.4.2 クラス継承 演習】
1. Rangeクラスの継承階層を調べてみてください。同様にして、HashとSymbolクラスの継承階層も調べてみてください。
→ まずはRangeから。>> r = Range.new(1,3) => 1..3 >> r.class => Range >> r.class.superclass => Object >> r.class.superclass.superclass => BasicObject >> r.class.superclass.superclass.superclass => nil次にHash
>> h = {} => {} >> h.class => Hash >> h.class.superclass => Object >> h.class.superclass.superclass => BasicObject >> h.class.superclass.superclass.superclass => nil最後にSymbol
>> s = :symbol => :symbol >> s.class => Symbol >> s.class.superclass => Object >> s.class.superclass.superclass => BasicObject >> s.class.superclass.superclass.superclass => nil
2. リスト 4.15にあるself.reverseのselfを省略し、reverseと書いてもうまく動くことを確認してみてください。
→ 下記>> class Word < String >> def palindrome? >> self == reverse >> end >> end => :palindrome? >> s = Word.new("level") => "level" >> s.palindrome? => true
【4.4.3 組み込みクラスの変更 演習】
1. palindrome?メソッドを使って、“racecar”が回文であり、“onomatopoeia”が回文でないことを確認してみてください。南インドの言葉「Malayalam」は回文でしょうか? ヒント: downcaseメソッドで小文字にすることを忘れないで。
→ 4.4.2の演習の流れでいきましょう。s = Word.new("racecar") => "racecar" >> s.palindrome? => true >> s = Word.new("onomatopoeia") => "onomatopoeia" >> s.palindrome? => false >> s.downcase.palindrome? => true
2. リスト 4.16を参考に、Stringクラスにshuffleメソッドを追加してみてください。ヒント: リスト 4.12も参考になります。3. 比較演算子==を使って、上記2つの課題で作ったそれぞれのオブジェクトが同じであることを確認してみてください。
→ まとめて下記>> def shuffle >> self.split('').shuffle.join >> end >> end => :shuffle >> "foobar".shuffle => "fobaro" >> class String >> def shuffle >> split('').shuffle.join >> end >> end => :shuffle >> "foobar".shuffle => "boroaf"
【4.4.4 コントローラクラス 演習】
1. 第2章で作ったToyアプリケーションのディレクトリでRailsコンソールを開き、User.newと実行することでuserオブジェクトが生成できることを確認してみましょう。
→ toy_app消してますやん…ということで、git cloneを実行。また一つ賢くなりました。usernameとpasswordを聞かれましたが、Githubのアカウント名とログインパスワードを入力すれば実行できました。$ git clone 該当のリモートリポジトリのhttpsそして本題に入ろうとしたら、各種エラーが…。budle install --without productionして、rails db:migrateして解決。無事userオブジェクトを作成できました。
>> user = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
2. 生成したuserオブジェクトのクラスの継承階層を調べてみてください。
→ 下記>> user.class => User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime) >> user.class.superclass => ApplicationRecord(abstract) >> user.class.superclass.superclass => ActiveRecord::Base >> user.class.superclass.superclass.superclass => Object >> user.class.superclass.superclass.superclass.superclass => BasicObject >> user.class.superclass.superclass.superclass.superclass.superclass => nil
【4.4.5 ユーザークラス 演習】
1. Userクラスで定義されているname属性を修正して、first_name属性とlast_name属性に分割してみましょう。また、それらの属性を使って "Michael Hartl" といった文字列を返すfull_nameメソッドを定義してみてください。最後に、formatted_emailメソッドのnameの部分を、full_nameに置き換えてみましょう (元々の結果と同じになっていれば成功です)
→ 下記example_user.rbclass User attr_accessor :first_name, :last_name, :email def initialize(attributes = {}) @first_name = attributes[:first_name] @last_name = attributes[:last_name] @email = attributes[:email] end def full_name "#{@first_name} #{@last_name}" end def formatted_email "#{self.full_name} <#{@email}>" end endコンソール上で、
>> require './example_user' => true user = User.new(first_name: "t" ,last_name: "k", email: "tk@mail.com") => #<User:0x00000000030715e0 @first_name="t", @last_name="k", @email="tk@mail.com"> >> user.formatted_email=> "t k <tk@mail.com>"
2. "Hartl, Michael" といったフォーマット (苗字と名前がカンマ+半角スペースで区切られている文字列) で返すalphabetical_nameメソッドを定義してみましょう。
→ 下記def full_name "#{@first_name} #{@last_name}" end3. full_name.splitとalphabetical_name.split(', ').reverseの結果を比較し、同じ結果になるかどうか確認してみましょう。
→ 上の演習2を記入してるのでコンソールに読み込み直し。上の入力値も短すぎるので入れ直します。(ヤットさんのaが抜けてる、、、)>> require './example_user'=> true >> user = User.new(first_name: "Ysuhito", last_name: "Endo", email: "ye@ye.com") => #<User:0x0000000003059968 @first_name="Ysuhito", @last_name="Endo", @email="ye@ye.com"> >> user.full_name.split=> ["Ysuhito", "Endo"] >> user.alphabetical_name.split(', ').reverse => ["Ysuhito", "Endo"] >> user.full_name.split == user.alphabetical_name.split(', ').reverse => true
第4章まとめ
・全体で使うのか、特定のコントローラで使うのか、それらによってヘルパーの定義ファイルも使い分ける。
・オブジェクト指向を感覚に刷り込んでいけ。すべてがオブジェクトだ。
・クラスは継承できる。必ず大元のクラスがある。継承しているから色々な機能が使える。
・クラスに定義するのがクラスメソッド、インスタンスで定義するのがインスタンスメソッド。
・( )とか{ }省略できる。引数の最後のブロックの{ }など。
・:nameはシンボル。ハッシュ記法は ~~: "mojiretu" の書き方が一般的。
・ぼちぼち分からない単語があったので、用語集にまとめています。
この章は作業多かったな〜。でもコードリーディングには省略記法とかの知識が大事かも。次の5章から、再びアプリ開発に戻ります!
⇦第3章はこちら
学習にあたっての前提・著者ステータスはこちら
なんとなくイメージを掴む用語集
・組み込み関数
プログラミング言語などの仕様にあらかじめ用意され、標準で使用できる関数のこと。これに対し、プログラマがコード上で定義・実装した関数を「ユーザー定義関数」(user-defined function)という。・API(Application Programming Interface)
あるコンピュータプログラム(ソフトウェア)の機能や管理するデータなどを、外部の他のプログラムから呼び出して利用するための手順やデータ形式などを定めた規約のこと。APIの利用には、ソフトウェア開発の効率化・セキュリティの向上・最新情報を簡単に取得可能 といったメリットがある。・エスケープ(エスケープ文字・処理)
意味を持つ文字列をただの文字列にしたり、その逆で意味を与えたりすること、その働きを持つ文字(\など)のこと。・リテラル (literal)
ソースコードに書いた文字とか数字のこと。・ネスト(nest)
あるものの中に、それと同じ形や種類の(一回り小さい)ものが入っている状態や構造のこと。・アクセサー(accessir)
オブジェクト指向プログラミングで、オブジェクト内部のメンバ変数(属性、プロパティ)に外部からアクセスするために用意されたメソッド。メンバ変数をオブジェクト内部に隠蔽し、外部から直接参照させないようにするために用意される。・メンバ変数
インスタンス変数のこと。
- 投稿日:2020-09-06T17:35:12+09:00
GitHubActionsでRuby on RailsのRSpecテストを実行する
Ruby on RailsのRSpecテストをGitHubActionsで実行しようとしたら、そこそこ大変だったのでメモに残しておきます。
GitHubActionsとは
GitHub上で動作するサーバレスな実行環境です。
リポジトリ内のファイルに設定を記述することで、GitHubの各種操作にトリガして任意のアクションを実行できる仕組みです。
パブリックリポジトリであれば無料で使えます。今回はPull Requestの作成に紐づけてRSpecを実行することで、Rails用のCIにしようと思います。
結論
Railsを実行できるDockerイメージをDockerHubにアップロードして、DockerPullしてテストを実行するのが良さそうです。
環境
- Ubuntu 16.04 LTS
- Ruby on Rails 6.0
- Ruby 2.6.6
OS以外は違っても簡単に対応できます。
Dockerイメージを作ってDockerHubにアップロードする
Dockerfile
ベーシックな環境です。
必要なライブラリがあれば追記してください。tools/ci/DockerfileFROM ubuntu:16.04 SHELL ["/bin/bash", "-c"] ENV RUBY_VERSION="2.6.6" ENV BUNDLER_VERSION="2.1.4" ENV DEBIAN_FRONTEND="noninteractive" ENV PATH=/root/.rbenv/bin:/root/.rbenv/shims:$PATH WORKDIR /app COPY Gemfile . COPY Gemfile.lock . RUN set -x \ && apt update \ && apt install -y \ build-essential \ curl \ git \ libssl-dev \ libreadline-dev \ libmysqlclient-dev \ mysql-client \ mysql-server \ tzdata \ zlib1g-dev \ # Install rbenv and ruby && git clone https://github.com/sstephenson/rbenv.git ~/.rbenv \ && git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build \ && CONFIGURE_OPTS='--disable-install-rdoc' /root/.rbenv/bin/rbenv install ${RUBY_VERSION} -s \ && /root/.rbenv/bin/rbenv global ${RUBY_VERSION} \ # Install bundler && echo 'gem: --no-rdoc --no-ri' > /root/.gemrc \ && /root/.rbenv/shims/gem install bundler -v ${BUNDLER_VERSION} \ # bundle install && /root/.rbenv/shims/bundle config set without development \ && /root/.rbenv/shims/bundle install \ # delete unused file && rm -rf /var/lib/apt/lists/* \ && rm Gemfile Gemfile.lockRUNを複数書いてDockerのレイヤを重ねるとMySQL周りで実行時にエラーが出るので1つのコマンドで全環境を構築します。
Dockerのビルド
docker build -t utyosu/build-rails:latest . -f tools/ci/DockerfileDockerイメージのアップロード
DockerHubのアカウントが必要になります。
はじめてのDockerHubリポジトリ登録あたりが参考になりそうです。docker push utyosu/build-rails:latestGitHubActionsのトリガを設定する
プロジェクトのリポジトリ内にGitHubAction用のファイルを作成します。
.github\workflows\build.ymlname: build # Pull Requestをトリガにする on: [pull_request] jobs: build: # Ubuntuの最新版をベースにする runs-on: ubuntu-latest # DockerHubからイメージをPullする container: image: utyosu/build-rails:latest # このブランチをcheckoutする steps: - uses: actions/checkout@v1 # test.shを実行する - name: run test run: . tools/ci/test.shテストスクリプト
テストしたいことを記述します。
このスクリプトが失敗するとビルド失敗になります。tools/ci/test.sh#!/bin/bash # プロセスが何も起動してないので、まずはMySQLを立ち上げておく service mysql start # MySQLユーザの作成と権限の付与 mysql -e 'create user "user_name";' mysql -e 'grant all on *.* to "user_name";' export RAILS_ENV=test bundle config set without development # Gemをインストールする。 # 何もない状態から bundle install # Capistranoが動いているか確認する # (バージョンの不一致で動かないときがあるので念の為チェックしている) bundle exec cap -T # Rubocopによる静的解析 bundle exec rubocop # railsで定義されているデータベースを作成する bundle exec rails db:create # ridgepoleによるデータベースのスキーマ作成 # ridgepoleを使っていない人は代わりに bundle exec rails db:schema:load を実行する bundle exec ridgepole -c config/database.yml --apply -f db/schema -E test # RSpecの実行 bundle exec rspec実際にやってみる
PullRequestを作成すると
Some checks haven't completed yetと表示されます。DetailsをクリックするとCIの進捗状況を確認できます。
少し待っているとコンソール出力が表示されます。
しばらくすると完了します。
PullRequestに戻ると
All checks have passedと表示されて、問題なくテストが通ったことが分かります。経緯
GitHub標準のRuby実行環境が用意されているので、最初はそれをベースにして試していました。
しかし、RailsやGemのインストールで30分以上かかってしまうので、軽めのプロジェクトにおけるCIには向いてないと思いました。
DockerHubからイメージをPullして実行する方法であれば、40秒くらいでテストの実行が開始できることが分かったのでこの方法を用いています。メモ
別コンテナにMySQLとか用意してコンテナ同士でやり取りする方法もあるようですが仕組みが難しすぎて実現できませんでした。
Rails環境で上手く実現できたらいいなーって思ってます。
- 投稿日:2020-09-06T17:16:02+09:00
Rails(ActiveSupport)の*_since、*_agoはダサい!
結論
*_since、*_agoは使うな- 代わりに
since/ago/after/before+/-を適切に使おうはじめに
Rails便利ですね。
個人的にRailsの中ではActiveRecordが最高のツールですが、ActiveSupportも変態拡張が多くて大好きです。
current_user.authorized_at <= 1.day.ago # 1.day.before(Time.now)とか気持ちいいですね。あるPR
user.expires_on = Date.today.months_since(6)これを見たときにすっと思考に入ってきますか?
僕はこれが未来を指すのか過去を指すのか即座に判断できません。英語の例文を考えてみましょう。
「ユーザの権限は6ヶ月後に期限切れとなる」The user's permission will expire on 6 months after.どこにもsinceは現れません。
sinceを使った例文「ユーザの権限は1ヶ月前から切れている」The user's permission has expired since 1 month ago.なので私は、PRで
months_sinceを見るとuser.expires_on = 6.months.after # 他にもこんな書き方ができるよ! # user.expires_on = Date.today + 6.months # user.expires_on = 6.months.after(Date.today) # 丁寧な英語っぽく # user.expires_on = 6.months.from_now.to_dateという指摘をすることがあります。
何にこだわっているのか
コードを書くときには読み手に負担が少ないことが大事です。
Railsガイドにも*_since(ago)の記載があり、Rails開発者の中ではメジャーな存在となっていると思いますが、読み心地が極端に悪いと僕は考えています。
すべてのコードが英語としてリーダブルであることを求める必要はありませんが、別の書き方で読みやすくなるならそちらで記述した方がいいでしょう。sinceのもたらす語感
英語のsinceは、起点を示してそこから何かが継続しているイメージを想起させます。
引用元: https://www.english-speaking.jp/difference-between-from-and-since/しかし
months_since(6)と出てくるとその感覚がバカになるのです。1念のために原点を確認してみた
古いCHANGELOGを見ると、すでにその姿を見つけられました。1.0.0のころの記述ですね。
* Added Time::Calculations to ask for things like Time.now.tomorrow, Time.now.yesterday, Time.now.months_ago(4) #580 [DP|Flurin]. Examples: "Later today" => now.in(3.hours), "Tomorrow morning" => now.tomorrow.change(:hour => 9), "Tomorrow afternoon" => now.tomorrow.change(:hour => 14), "In a couple of days" => now.tomorrow.tomorrow.change(:hour => 9), "Next monday" => now.next_week.change(:hour => 9), "In a month" => now.next_month.change(:hour => 9), "In 6 months" => now.months_since(6).change(:hour => 9), "In a year" => now.in(1.year).change(:hour => 9)正直なところ、
sinceの使い方に関して6.months.since(user.created_at)とかの例が出てくるかなと思ってましたが拍子抜けです。
非ネイティブだからこその無駄なこだわりかもしれません。
since/agoについてプレフィクスを伴わない
since/agoは、加算(減算)のシンタックスシュガーです。どこかに起点を置いてそこからの経過を取得するという場合には有用なメソッドです。
ただ、やっぱりTime拡張のsince/agoは使いどころが見つかりません。
Duration#since/agoは使い勝手がいいので使い方を間違わずに使っていきたいと考えます。
また、それぞれafter/from_now/before/untilというエイリアスを持っているので、文脈に合わせてメソッドを選びたいですね。結局
*_sinceを使うか?先述のように1.0当初から
*_sinceがあり現在の使い方を想定されていたとはいえ、私はやはり可読性を損なう記述に関しては書き換えを推奨していきます。6.months.after # 6ヶ月後 1.month.after(Time.new(2020,1,31)) # 2020/1/31の1ヶ月後: ちゃんと2020/2/29が作れる Time.new(2020,1,31) + 1.month # 2020/1/31に1ヶ月足す: ちゃんと2020/2/29が作れる 2.weeks.since(user.created_at) # ユーザの作成日から2週間: 無料期間とか作るときこんな形だとその間というのがわかりやすいなどが結果を損なわず、文意もわかりやすいものだと考えます。
逆に推奨しない書き方
Time.now.months_since(6) # 本稿の主題。months_sinceは語感からずれる Time.now.months_ago(6) # sinceがagoに変わっただけで本質は同じ。 Time.now.since(6.month) # 同様にsinceにNumericを持ってきて加算するのは気持ち悪い。 Time.now.ago(6.month) # これもagoの語感と語順が気持ち悪い Time.now.since(user.created_at) # 機能的にはこんなことができるけどもはや意味不明
念のために社内の英語ネイティブスピーカー(非エンジニア)にも確認してみたところ、同様の感覚で答えてくれました。 ↩
- 投稿日:2020-09-06T16:26:06+09:00
ローカル環境からherokuにデプロイしたRailsアプリを更新したいとき
やりたいこと
ローカル環境からherokuにデプロイしたRailsアプリの内容を更新する。
具体的には、変更したデータベースを反映したい。
(忘備録に残しておきます)
更新方法
ターミナルにて、更新したいアプリがあるディレクトリに移動し、
$git add -A
$git commit -m "Update application"
$git push heroku masterからの、
$heroku run rails db:migrateこれでいけました。
git push heroku master時にエラーが発生する場合
gemfileをいじったとき、最後にbundle installをし忘れている可能性があります。
ということで、再びターミナルにて
$budnle install↓
$git add -A
$git commit -m "Update application"
$git push heroku master↓
$heroku run rails db:migrateこれでherokuにデプロイしていたアプリの内容を更新できました!
参考にした記事
以下の記事が参考になりました。
https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39
- 投稿日:2020-09-06T16:02:12+09:00
[rails]binding.pryで処理内容を確認する
やりたいこと
@itemに入っている中身valueを知りたい
items#show def show @item = Item.find(params[:id]) end#show.html.erbの記述 <%= link_to '購入画面に進む',item_orders_path(@item.id)%>処理内容
"購入画面に進む"ボタンを押すとorders#index(/items/:item_id/orders(.:format))に画面遷移する。
購入ボタンを押すと@itemの内容を参照するようになるがどのような情報を抜き出しているのか見たい処理内容の確認方法
1.
gem 'pry-rails'をGemfileに記述しインストールする
2.items#showにbinding.pryと記述するdef show @item = Item.find(params[:id]) binding.pry endbinding.pryによってshowアクションが実行された時処理を止めている
3.実際にブラウザを開きshow.hrml.erbのページを開くshowアクションを実行する。
4.処理が止まるのでコンソールのサーバーを見る
app/controllers/items_controller.rb:41 ItemsController#show: 40: def show => 41: binding.pry 42: end [1] pry(#<ItemsController>)>showアクションで処理が止まっていることがわかる
5[1] pry(#)>に@itemと記述する
[1] pry(#<ItemsController>)> @item => #<Item:0x00007f9ac9e01300 id: 8, item_name: "ラーメン", info: "いい味出てる!", category_id: 3, status_id: 3, shipping_id: 3, area_id: 5, schedule_id: 4, price: 1000, user_id: 1, created_at: Fri, 04 Sep 2020 01:29:20 UTC +00:00, updated_at: Fri, 04 Sep 2020 01:29:20 UTC +00:00>これで@itemに中身valueがわかった!
補足
[1] pry(#)>に@item.idとうつと
[2] pry(#<ItemsController>)> @item.id => 8@item.id の内容が”8”ということがわかった
まとめ
<%= link_to '購入画面に進む',item_orders_path(@item.id)%>
item_orders_pathに画面遷移したかったらitem_idが指定できないと遷移できない。item_orders /items/:item_id/orders(.:format) orders#index<%= link_to '購入画面に進む',item_orders_path(@item.id)%>(@item.id)と指定することでの
:item_idに必要な情報を抜き出すことができた!
- 投稿日:2020-09-06T15:21:23+09:00
VSCodeでファイル保存時に rubocopのauto-correct
掲題の通り。設定だけならそれほど大したことはなかったが、一点謎の不具合があってそこそこハマった。
プロジェクトにrubocopを導入。
この辺は公式のReadme等読みつつ雑に実施。
gemfileにrubocopを入れて、.rubocop.ymlを用意するだけ。
参考: https://github.com/rubocop-hq/rubocop元ファイルはRailsのRubocopをベースに作成。
rubocop-railsやrubocop-performanceを使っている様子なので、こちらもGemfile等に入れておく。
参考: https://github.com/rails/rails/blob/master/.rubocop.ymlrubocopの対象外にしたいファイルは除外する。
参考: https://qiita.com/necojackarc/items/8bc16092bbc69f17a16d#%E5%AF%BE%E8%B1%A1%E3%83%95%E3%82%A1%E3%82%A4%E3%83%ABあとは
bundle exec rubocopでrubocopが想定通り動作すればOKVSCode 上でrubocop の auto-correct
Prettierみたいなことがしたいだけだったが苦労した。なぜか。。。
基本設定
基本的には以下をVSCodeのsetting.jsonに入れるだけでおおよそ実現する
"ruby.lint": { "rubocop": true }, "[ruby]": { "editor.formatOnSave": false }, "ruby.format": "rubocop"参考: https://qiita.com/yumikokh/items/98be01df144c41d60e1e#formatting
上の参考サイトでは
"editor.formatOnSaveTimeout": 5000が必要とされていたが、私の環境では必要なかった。ハマり箇所
ただ、私の環境だとこの設定をした際、日本語が何故かユニコードに変換されてしまうという現象が起きた。コマンドラインからrubocop auto-correctを実施した際は全く問題無いが、VSCodeの自動整形でのみ発生する。
例えば、
context '未ログインの場合' do
が
context "\u672A\u30ED\u30B0\u30A4\u30F3\u306E\u5834\u5408" do
こうなる。ファイルはutf8になっており、文字コード周りの設定は問題ないとは思うのだが、、、
この辺りの話が関連しているのだろうか?わからん。
https://techracho.bpsinc.jp/hachi8833/2016_10_13/26969またこちらも理由は全くわからないが .rubocop.ymlの設定を以下のように変えたら上手くいった。
Style/StringLiterals: Enabled: true EnforcedStyle: double_quotes↓
Style/StringLiterals: Enabled: true EnforcedStyle: single_quotesdoubleをsingleに変えただけ。謎だ。。。
formatterとしてrubocop以外を利用した場合も日本語周りで問題が起きる。
おそらく何かしら問題があるだろうと思う。【今日の部会】
— みのも (@RNd8136) November 20, 2018
VScodeでRubyが自動整形されない
→Rufo拡張を入れる
それでも整形されない
→cmdでRufoをインストール
まだ整形されない
→日本語コメントが問題
→encoding: utf-8を最初に挿入
解決実際に私もやってみた結果、同じ結果に遭遇し、同じ対応で乗り越えることができた。
が、encoding: utf8 と書くのがなんとなく引っかかり、上記の対応を取ることにした。最後に
double_quotesが強制の環境だとキツイことになるな...どうしようか?
またもっと良い解決策をご存知の方がいましたら教えてください m(_ _)m
(しかしそもそもこれ再現性あるのだろうか。。。?)
- 投稿日:2020-09-06T14:25:27+09:00
ECSでデプロイしたEC2のコンテナインスタンスが落ち続けたら
前提
- ECSとECRを使用してデプロイ
- nginxとpumaをソケット通信
- ELBのルールを修正してHTTPSへリダイレクト
- ヘルスチェックの結果は、health checks failed with these codes: [301]
- URLを開くとnginxがBad gatewayやらService temporary unavailableとなる。
原因
ELBのヘルスチェックにリダイレクトを適応させていないことが原因でした。
解決方法
[LOAD BALANCING] で [ターゲットグループ] を選択する。
ターゲットグループを選択。
[ヘルスチェック] タブで、[編集] を選択。
[ターゲットグループの編集] ページで、必要に応じて設定を変更して終わり。
まとめ
HTTPからHTTPSへリダイレクトする設定をした場合、ヘルスチェックもHTTPS、ポート443へ変更しなければヘルスチェックは失敗してしまうようです。
ECSは設定が多くて扱うのが難しいですね。次の課題はCloudWatchでログを読み取れるようにすることです。
記事を読んでいただきありがとうございました。
参考にした記事
ヘルスチェックが失敗しましたが、トラブルシューティングと解決方法を教えてください。
https://aws.amazon.com/jp/premiumsupport/knowledge-center/troubleshoot-classic-health-checks/Health checks for your target groups
https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/application/target-group-health-checks.html
- 投稿日:2020-09-06T14:03:29+09:00
google vision apiをhocalhostでリファラーの制限を設定する
google vision api を開発環境で実行すると↓のエラーになります。
```ruby'"message"=>Requests from referer are blocked.' '"status"=>"PERMISSION_DENIED",
```APIのリファラーの制限をしている
開発環境でリファラーの制限しても動作させる方法header にRefererを設定するとhocalhostでも動作する
https://qiita.com/kasajei/items/78f934b943ebdff7ef08
headers = {
'Content-Type': 'application/json',
'Referer': 'your domain here'
}
- 投稿日:2020-09-06T11:45:12+09:00
bundle execが必要な理由
なぜbundle execが必要なのか
$bundle exec rails g controller userのようにすると、Gemfile.lockに記載されているものがrequireされる。
簡単に言うと、bundle exec をつけないで開発を行い、アップデートを行うという場合、動かなくなることがある。だから、bundle execを付けて開発を行うのが安牌なのかなという理解で、とりあえずは良いのかなと思いました。
参照
https://qiita.com/dawn_628/items/1821d4eef22b9f45eea8
この記事とかとても詳しく書かれていました。
- 投稿日:2020-09-06T11:44:42+09:00
Rails Tutorial 第13章 パーシャルのパスはカレントでも省略するとエラーになる
リスト13.43は演習で変更していたようで、テキストと違った
new.html.erbには修正すべきerror_messagesの行がありませんでした。
views/users/new.html.erb<%= render 'form' %>formに移してあったので、修正しました。
views/users/_form.html.erb<%= form_for(@user, url: yield(:url_path) ) do |f|%> <%#= render 'shared/error_messages', object: @user %> <%= render 'shared/error_messages', object: f.object %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %>おそらく過去に演習で変更していたと思われます。ネットで同じ方がいたため。
パーシャルのパスに、カレントを想定して省略するとエラーになった
パーシャルが見つからないとエラーになりました。
同じフォルダにあるので、フォルダ名を省略してファイル名を書いていました。ActionView::Template::Error (Missing partial microposts/_userandmicropost, application/_userandmicropost with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in: * "/home/ubuntu/environment/sample_app/app/views" ): 1: 2: <% if logged_in? %> 3: <%= render 'userandmicropost' %>パーシャルのパスに、static_pages/xxxと省略せずに書いたところ直りました。
1回目の呼び出しではエラーにならず、Postボタンを押したときにエラーになります。
理由は分かりませんが、ネットの他の方もstatic_pages/xxxxと書いていました。views/static_pages/home.html.erb<% if logged_in? %> <%= render 'static_pages/userandmicropost' %>演習13.3.3.2でエラー、再実行したら正常終了
1回目はfalseでエラーです。
>> Micropost.where("user_id = ?", user.id) == user.microposts Micropost Load (1.6ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC [["user_id", 1]] Micropost Load (0.2ms) SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC => true >> Micropost.where("user_id = ?", user.id) == user.feed => falseconsoleをいったん終了してやり直してみると、2回目はtrueで正常終了に変わりました。原因不明です。
>> Micropost.where("user_id = ?", user.id) == user.feed => true

























