- 投稿日:2020-07-08T23:16:27+09:00
gemでインストールしたrailsコマンドがなぜ使えるのか??
はじめに
※windowsのローカル環境下です。※
表題の通り、気になったので調べました。
bin/railsで実行するかrailsで実行するか??
まずは事前知識として、bin/railsで実行するかrailsで実行するか??結論、どちらを使っても基本問題ないですが、参照元が違います。
binをつけるとプロジェクトディレクトリを参照。
binを付けないとローカル(グローバル)のrailsを参照。
続いて本題。
gemでインストールしたrailsコマンドがなぜ使えるのか??
railsコマンドはローカル環境変数でPath通してるはず。。。→これはとても浅い勘違い!!
railsは以下のようにgemでインストールしています。
$ gem install rails -v "5.2.3" $ rails -v Rails 5.2.3
では、なぜgemでインストールしたrailsでrailsコマンドが通るんだろう??調べました。
環境変数には以下があります。
C:\Ruby26-x64\bin
ここを確認するとこんな感じ。
$ ls Ruby26-x64/bin _guard-core* coderay.bat nokogiri* rails.bat ruby_builtin_dlls/ spring* _guard-core.bat erb* nokogiri.bat rake* rubyw.exe* spring.bat bundle.cmd erb.cmd pry* rake.bat sass* sprockets* bundler.cmd gem* pry.bat rake.cmd sass.bat sprockets.bat byebug* gem.cmd puma* rdoc* sass-convert* sqlite3.def byebug.bat guard* puma.bat rdoc.cmd sass-convert.bat sqlite3.dll* chromedriver-helper* guard.bat pumactl* ri* scss* thor* chromedriver-helper.bat irb* pumactl.bat ri.cmd scss.bat thor.bat chromedriver-update* irb.cmd rackup* ridk.cmd setrbvars.cmd tilt* chromedriver-update.bat listen* rackup.bat ridk.ps1 slimrb* tilt.bat coderay* listen.bat rails* ruby.exe* slimrb.bat x64-msvcrt-ruby260.dll*
railsがありました。これを参照しているっぽい。ですが確証が得られないのでさらに調べる。
gemsでインストールした各種ライブラリは以下にあります。
C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems
rails 5.2.3の中身を確認
$ ls Ruby26-x64/lib/ruby/gems/2.6.0/gems/rails-5.2.3 README.md
あれ??どうやって参照している??whichコマンドを使ってみる。
$ which rails /c/Ruby26-x64/bin/rails
解決しました◎やはりRuby直下のbinディレクトリでrailsは参照されていました。
ちなみに、なんで参照されるか。。。
どうやらwhichコマンドは以下のルールらしい。
whichコマンドは、環境変数のPATHに設定されているディレクトリ順に調べ、最初に見つかったコマンドを表示
whichコマンドがこのような参照方法であればコマンドもそうであるはず!!合点✌
備考:bundle installする際にパスを指定する
指定しないで実行すると前述のとおり、以下にインストールされます。
C:\Ruby26-x64\lib\ruby\gems\2.6.0\gems
パスを指定する方法は以下
bundle install --path vendor/bundle
これでプロジェクト直下のvendor/bundleにインストールできます。ちなみにrails new時デフォルトでbundle installも実行されますが、Bオプションを付けることで実行スキップできます。
rails new rails_app -B
一度オプションを付けてインストールすれば、以下に設定が保存されて以降は指定不要。プロジェクト/.bundle/config
--- BUNDLE_PATH: "vendor/bundle"
参考
Rails 4.1以降のコンソールコマンドは必ず bin/ を付けなきゃいけないの?railsでbundle installする時にインストールパスを指定
おわりに
日ごろからローカル環境の扱いにはもっと意識を向けていきたいと思いました。
- 投稿日:2020-07-08T22:33:17+09:00
☆初投稿:TECH CAMP学習、個人アプリ作成①、駆け出しエンジニア
TECH CAMPに通い出して
初めて投稿させていただきました。
TECH CAMPの夜間コースに通い出して3ヶ月が経ちました。
カリキュラムではわからないことも沢山あり、その都度Googleで調べたり、メンターさんに質問したりと、苦労も沢山ありました。エラーを繰り返しながらも、無事に動いたときというのは何とも言えない喜びもありました。諸々、何とかこなし、いよいよ個人アプリ開発となりました。
正直、現在も試行錯誤の連続でありますが、やはり自分のやっていることというのは、時間がたつと忘れてしまうこともありますので、自分が苦労して解決したことなどは記録していった方がよいと考え、投稿させていただきました。
まだまだ不慣れであり、かつ知識も未熟なため、書いてあることが間違っていることもあるかもしれませんが、はっきりいって投稿することに意味があるのだと思いましたので、間違っているところははっきり指摘していただけたら嬉しいです。
個人アプリの作成時、viewを表示させようとしたのですが
まずは以下のエラーがありました。原因はhamlの導入ができていませんでした。
なのでGemfileに『gem 'haml-rails'』
を入力して
ターミナルにて『bundle install』
を実行、その後すでにあるerbをhamlに変換
『rails haml:erb2haml』
すると「would you like〜」と出てくるので「y」を入力
これでエラーは解決しました。
- 投稿日:2020-07-08T22:30:53+09:00
Railsでdeviseを追加したのに反映されなかったときの対処法
dockerで環境構築を行いgemの追加をした際にエラーが発生しましたので
備忘録として情報共有しようと思います!開発環境
docker
rails (5.2.0)
ruby (2.7.1)
mysql (5.7)
nginx
puma※docker-compose buildでgemの更新をしますが時間がかかるので下記の記事を参考に
gemの更新を素早くする設定をしています。
https://qiita.com/neko-neko/items/abe912eba9c113fd527eエラー内容
Gemfileでdeviseのgemを追加し、docker-compose run --rm rails bundle installをしたところ以下のエラーが発生
~ RailsApp % docker-compose run --rm rails bundle install #bundle installを実行 Starting railsapp_db_1 ... done Fetching gem metadata from https://rubygems.org/............. Fetching gem metadata from https://rubygems.org/. Resolving dependencies.... Bundler could not find compatible versions for gem "railties": # エラー発生 In snapshot (Gemfile.lock): railties (= 5.2.4.3) In Gemfile: devise (= 4.1.0) was resolved to 4.1.0, which depends on railties (< 5.1, >= 4.1.0) rails (~> 5.2.2) was resolved to 5.2.4.3, which depends on railties (= 5.2.4.3) Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict.何が原因なのかわからなかったが、とりあえず「bundle updateしたら解決するかもよ」と書いてあったので
docker-compose run --rm rails bundle updateを実行↓↓↓↓↓↓↓↓
~ RailsApp % docker-compose run --rm rails bundle update #bundle updateを実行 Starting railsapp_db_1 ... done Fetching gem metadata from https://rubygems.org/............. Fetching gem metadata from https://rubygems.org/. Resolving dependencies........................... Bundler could not find compatible versions for gem "railties": # 同じエラーが発生 In snapshot (Gemfile.lock): railties (= 5.2.4.3) In Gemfile: devise (= 4.1.0) was resolved to 4.1.0, which depends on railties (< 5.1, >= 4.1.0) rails (~> 5.2.2) was resolved to 5.2.4.3, which depends on railties (= 5.2.4.3)ダメでしたね・・・
原因
結論から申し上げるとdeviseというgemがlistにないことがエラーの原因だったみたいです。
下記の記事を参考にさせていただきました。
https://qiita.com/hatorijobs/items/2928e152f22d009b07d0こちらの記事をそのまま実行すれば解決しますが、dockerで作業している方は若干、コマンドが
異なりますのでこちらで説明させていただこうと思います。対処方法
以下の順に操作をしていきます。
1. docker-compose exec [任意サービス名] gem list でGemの一覧を確認し、deviseのgemがないことを確認する。 2. docker-compose exec [任意サービス名] gem install devise でdeviseをインストールする。 3. もう一度docker-compose exec [任意のサービス名] gem list でGemの一覧を確認し、devise(4.7.2)があることを確認する。 4. Gemfileに**gem 'devise','4.7.2'を記述する 5. docker-compose run --rm rails bundle updateを実行順番に見ていきましょう。
↓↓↓↓↓↓↓↓1. docker-compose exec [任意サービス名] gem list でGemの一覧を確認し、deviseのgemがないことを確認する。
~ RailsApp % docker-compose exec app gem list *** LOCAL GEMS *** actioncable (5.2.4.3, 5.2.2) actionmailer (5.2.4.3, 5.2.2) actionpack (5.2.4.3, 5.2.2) actionview (5.2.4.3, 5.2.2) actionjob (5.2.4.3, 5.2.2) ~省略~ web-console (3.7.0)2. docker-compose exec [任意サービス名] gem install devise でdeviseをインストールする。
~ RailsApp % docker-compose exec app gem install devise Fetching warden-1.2.8.gem Fetching bcrypt-3.1.13.gem ~省略~ Successfully installed devise-4.7.2 5 gems installed3. もう一度docker-compose exec [任意のサービス名] gem list でGemの一覧を確認し、devise(4.7.2)があることを確認する。
~ RailsApp % docker-compose exec app gem list *** LOCAL GEMS *** actioncable (5.2.4.3, 5.2.2) actionmailer (5.2.4.3, 5.2.2) actionpack (5.2.4.3, 5.2.2) actionview (5.2.4.3, 5.2.2) actionjob (5.2.4.3, 5.2.2) ~省略~ devise (4.7.2) ~省略~ web-console (3.7.0)4. Gemfileにgem 'devise','4.7.2'を記述する
Gemfile.source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.0.0', '>= 5.0.2.1' # Use mysql as the database for Active Record gem 'mysql2', '>= 0.3.18', '< 0.5' # Use Puma as the app server ~省略~ #ログイン機能の追加 gem 'devise','4.7.2' ~省略~5. docker-compose run --rm rails bundle updateを実行
~ RailsApp % docker-compose run --rm rails bundle updateこれでエラーが出なければ我々の勝ちです
気づいたこと
エラー対応している際に気づいたことがいくつかあったので共有させていただきます
docker-compose exec app gem install deviseを実行すると最新版のgemがインストールされます。 最新版でも特に問題ないと判断したため、バージョン指定する方法の説明は省略させていただきました。 ※逆に方法がわかる方は教えてください!ちなみにdeviseの全バージョン履歴が掲載されたサイトを見つけましたのでURLを連携させていただきます。
https://rubygems.org/gems/devise/versions
※2020.06.10時点での最新バージョンは4.7.2
[任意のサービス名]とはdocker-compose.ymlで指定しているサービス名のことです。 任意で決めることができますが、どのサイトでもdbとかappとかwebて設定している方が多い印象です。※わからないて方は、「docker-compose.yml サービス名」で調べると記事がヒットすると思います。
終わりに
Railsやdockerについては今後もアウトプットのためにエラーに関する記事等を定期的に投稿していこうと思っております。
また、初めての投稿で至らない点があったかと思いますが、最後まで読んでいただきありがとうございました!!!以上
- 投稿日:2020-07-08T22:30:53+09:00
Railsでdeviseを追加したいのにbundle installができない
dockerで環境構築を行いgemの追加をした際にエラーが発生しましたので
備忘録として情報共有しようと思います!開発環境
docker
rails (5.2.0)
ruby (2.7.1)
mysql (5.7)
nginx
puma※docker-compose buildでgemの更新をしますが時間がかかるので下記の記事を参考に
gemの更新を素早くする設定をしています。
https://qiita.com/neko-neko/items/abe912eba9c113fd527eエラー内容
Gemfileでdeviseのgemを追加し、docker-compose run --rm rails bundle installをしたところ以下のエラーが発生
~ RailsApp % docker-compose run --rm rails bundle install Starting railsapp_db_1 ... done Fetching gem metadata from https://rubygems.org/............. Fetching gem metadata from https://rubygems.org/. Resolving dependencies.... Bundler could not find compatible versions for gem "railties": In snapshot (Gemfile.lock): railties (= 5.2.4.3) In Gemfile: devise (= 4.1.0) was resolved to 4.1.0, which depends on railties (< 5.1, >= 4.1.0) rails (~> 5.2.2) was resolved to 5.2.4.3, which depends on railties (= 5.2.4.3) Running `bundle update` will rebuild your snapshot from scratch, using only the gems in your Gemfile, which may resolve the conflict.何が原因なのかわからなかったが、とりあえず「bundle updateしたら解決するかもよ」と書いてあったので
docker-compose run --rm rails bundle updateを実行↓↓↓↓↓↓↓↓
~ RailsApp % docker-compose run --rm rails bundle update Starting railsapp_db_1 ... done Fetching gem metadata from https://rubygems.org/............. Fetching gem metadata from https://rubygems.org/. Resolving dependencies........................... Bundler could not find compatible versions for gem "railties": In snapshot (Gemfile.lock): railties (= 5.2.4.3) In Gemfile: devise (= 4.1.0) was resolved to 4.1.0, which depends on railties (< 5.1, >= 4.1.0) rails (~> 5.2.2) was resolved to 5.2.4.3, which depends on railties (= 5.2.4.3)ダメでしたね・・・
原因
結論から申し上げるとdeviseというgemがlistにないことがエラーの原因だったみたいです。
下記の記事を参考にさせていただきました。
https://qiita.com/hatorijobs/items/2928e152f22d009b07d0こちらの記事をそのまま実行すれば解決しますが、dockerで作業している方は若干、コマンドが
異なりますのでこちらで説明させていただこうと思います。対処方法
以下の順に操作をしていきます。
1. docker-compose exec [任意サービス名] gem list でGemの一覧を確認し、deviseのgemがないことを確認する。 2. docker-compose exec [任意サービス名] gem install devise でdeviseをインストールする。 3. もう一度docker-compose exec [任意のサービス名] gem list でGemの一覧を確認し、devise(4.7.2)があることを確認する。 4. Gemfileに**gem 'devise','4.7.2'を記述する 5. docker-compose run --rm rails bundle updateを実行順番に見ていきましょう。
↓↓↓↓↓↓↓↓1. docker-compose exec [任意サービス名] gem list でGemの一覧を確認し、deviseのgemがないことを確認する。
~ RailsApp % docker-compose exec app gem list *** LOCAL GEMS *** actioncable (5.2.4.3, 5.2.2) actionmailer (5.2.4.3, 5.2.2) actionpack (5.2.4.3, 5.2.2) actionview (5.2.4.3, 5.2.2) actionjob (5.2.4.3, 5.2.2) ~省略~ web-console (3.7.0)2. docker-compose exec [任意サービス名] gem install devise でdeviseをインストールする。
~ RailsApp % docker-compose exec app gem install devise Fetching warden-1.2.8.gem Fetching bcrypt-3.1.13.gem ~省略~ Successfully installed devise-4.7.2 5 gems installed3. もう一度docker-compose exec [任意のサービス名] gem list でGemの一覧を確認し、devise(4.7.2)があることを確認する。
~ RailsApp % docker-compose exec app gem list *** LOCAL GEMS *** actioncable (5.2.4.3, 5.2.2) actionmailer (5.2.4.3, 5.2.2) actionpack (5.2.4.3, 5.2.2) actionview (5.2.4.3, 5.2.2) actionjob (5.2.4.3, 5.2.2) ~省略~ devise (4.7.2) ~省略~ web-console (3.7.0)4. Gemfileにgem 'devise','4.7.2'を記述する
Gemfile.source 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.0.0', '>= 5.0.2.1' # Use mysql as the database for Active Record gem 'mysql2', '>= 0.3.18', '< 0.5' # Use Puma as the app server ~省略~ #ログイン機能の追加 gem 'devise','4.7.2' ~省略~5. docker-compose run --rm rails bundle updateを実行
~ RailsApp % docker-compose run --rm rails bundle updateこれでエラーが出なければ我々の勝ちです
気づいたこと
エラー対応している際に気づいたことがいくつかあったので共有させていただきます
docker-compose exec app gem install deviseを実行すると最新版のgemがインストールされます。 最新版でも特に問題ないと判断したため、バージョン指定する方法の説明は省略させていただきました。 ※逆に方法がわかる方は教えてください!ちなみにdeviseの全バージョン履歴が掲載されたサイトを見つけましたのでURLを連携させていただきます。
https://rubygems.org/gems/devise/versions
※2020.06.10時点での最新バージョンは4.7.2
[任意のサービス名]とはdocker-compose.ymlで指定しているサービス名のことです。 任意で決めることができますが、どのサイトでもdbとかappとかwebて設定している方が多い印象です。※わからないて方は、「docker-compose.yml サービス名」で調べると記事がヒットすると思います。
終わりに
Railsやdockerについては今後もアウトプットのためにエラーに関する記事等を定期的に投稿していこうと思っております。
また、初めての投稿で至らない点があったかと思いますが、最後まで読んでいただきありがとうございました!!!以上
- 投稿日:2020-07-08T22:30:28+09:00
dockerでrails+mySQLの環境構築したけどlocalhost:3000にアクセス出来なくて困ってたら驚愕の事実が発覚した話
こちらの記事を参考に、docker-composeでrails+mySQLの環境構築をしてみた。
DBの作成まですんなり行き「docker最高!!!!!!!!」みたいになってたが、localhost:3000にアクセスすると「このページは動作していません。」と返ってくる…
うーわ、最悪
環境構築には嫌な思い出しかない。
一度virtualboxとvagrantで環境構築したとき無限にエラーが出続けて「これはもう神がプログラミングをやめろと言っているのでは???」みたいになって以来、環境構築という単語を聞いただけで寒気がしてくる。まあプログラミングの学習をする上で避けては通れない道なのでやるしかない。
とりあえずコンテナ内のサーバーが起動しているか確認。
$ docker exec -it コンテナ名 bash **** # curl http://localhost:3000/コンテナの中に入ってlocalhost:3000にアクセスしてみると普通にHTMLが返ってくる。こっちは問題なさそう。
次にログを見てみる。
docker logs ***_***_web見たけど特にエラーは出ていない。
もう一度コンテナ内からアクセスしたのちログをみると、さっきと同様のログが一つ増えていたので多分このログはコンテナ内からアクセスしたものっぽい。ブラウザからアクセスしてもログが残っていないということは、そもそもブラウザからWebコンテナの3000ポートまでリクエストが到達してないってことらしい。
なるほどね……………ドユコト???クッソ、横文字ばっか並べやがって…
その後も格闘は続いた。ありとあらゆるサイトを読み漁り、英語を必死に読解し、嫌になってスプラトゥーンをやって、YouTubeを見て、twitterを見て…とかなんとかしているうちに夜になっていた。
もう諦めようか…そんな考えが頭をよぎり出したとき、ある記事が目に入る。
私と同じようなエラーが出て困っているようだったのだが、その人が「esetのファイアウォールを無効にしたらいけました〜」と言うているのだ。eset??まって私もセキュリティソフトesetだわ、ウイルスバスターって名前がダサくて嫌だったから特に何も考えずに購入したeset…まさかお前が…?いや、こんな悩みに悩んだ挙句セキュリティソフトに通信遮断されてました!は流石に酷いと思わない…???ねえ、違うよね??違うって言ってよ!!!
とか思いながら恐る恐るesetのファイアウォールを無効にしてlocalhost:3000にアクセスしてみたら
で、出た〜〜〜〜〜〜〜〜〜!!!!!!!!!!!!お前、あっさり出た〜〜〜〜〜〜〜〜!!!!!!!!
くそがよ
- 投稿日:2020-07-08T22:06:51+09:00
Railsでhtml.erbのclassを条件付きで追加する方法
概容
下記のクラスに「ある条件」の時だけクラスを追加したいとする。
<div class="container"> </div>例えば今回は
users_controller
の時だけクラスを追加するとする。
その場合の条件式は下記のようになる。<div class="container<%= ' user-container' if controller_name = 'users' %>"> </div>※注意
追加したいクラス名の先頭文字の前はスペースを開けること。
正しい: <%= ' user-container' if・・・ 間違い: <%= 'user-container' if・・・このような条件付きで
class
を追加することによってその条件下のみでCSSでスタイルの変更や上書きができるようになるのでぜひ覚えておきたい。
- 投稿日:2020-07-08T22:06:02+09:00
productionとdevelopmentをなぜ使い分けるのか
railsになんでわざわざdevelop(開発環境)とproduction(本番環境)を使い分けているのか判明したので記録
相変わらず表面的な知識なので、誰かツッコミを入れてくれると嬉しいです。
それではいってみよーーーーーー!!!!!development(開発環境)
- 長所
エラーが発生すれば、詳細を表示してくれたりする。
- 欠点
詳細を表示してまうと、ソースコード情報が外に漏れちゃうからセキュリティ的な脆弱性がある
production(本番環境)
- 長所
エラーが発生してもコードの詳細を表示しいひんから、安全性・安定性が高い
- 欠点
エラーが起こっても、「何か例外が発生したよ」ぐらいしか教えてくれない
(最初どうやってエラー見るねんって焦った)まとめ
セキュリティの観点から、本番デプロイ時にはproductionでrailsを動かす必要があるんやね!
- 投稿日:2020-07-08T21:53:18+09:00
[Rails]form_forを使ったselect-boxの注意点
form_forを使ったselect-boxにはrubyオプションとhtmlオプションが存在する。
双方のオプション記述場所を誤ると実装に反映されないため注意が必要!
オプションの種類に関しては公式ドキュメントを見ると簡単に理解できるであろう。Ruby公式▼
https://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/selecthtml公式▼
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select参考にしてください!
- 投稿日:2020-07-08T21:53:18+09:00
[Rails]form_forを使ったcelect-boxの注意点
form_forを使ったcelect-boxにはrubyオプションとhtmlオプションが存在する。
双方のオプション記述場所を誤ると実装に反映されないため注意が必要!
オプションの種類に関しては公式ドキュメントを見ると簡単に理解できるであろう。Ruby公式▼
https://apidock.com/rails/ActionView/Helpers/FormOptionsHelper/selecthtml公式▼
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select参考にしてください!
- 投稿日:2020-07-08T21:31:33+09:00
Docker&Rails環境で、gemを永続化させる
本記事について(お断り)
この記事は、しがないプログラミング初心者がお勉強よろしくプログラミング・技術に触れるべく、行ったことの備忘録として書き綴ったものです。ご了承ください。
ご指摘は甘んじて受け入れます?♂️起こったこと
とあるプロダクトでおもむろに
docker-compose up
してたら、... web_1 | Bundler::GemNotFound: Could not find gem 'rspec-rails' in any of the gem sources listed in your Gemfile. ... myapp_web_1 exited with code 1と怒って勝手に出て行ってしまった。
おかしい、確かにインストールしたはずのgemがないとは、、、一度落として、再度
docker-compose build
すると問題なく動いた。
まあこれでもいいんだけど、毎回ビルドしてコンテナ起動するのは面倒だと思う。調べてみると、volumeの永続化を行うことで、こうした問題が解消されるとのこと。
docker-compose.ymlの修正
現在のファイルの中身。
docker-compose.ymlersion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD=password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - dbこれを書き加える
docker-compose.ymlersion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD=password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - gem_data:/usr/local/bundle # <= ココと ports: - "3000:3000" depends_on: - db volumes: gem_data: # <= ココ。パスは、コンテナの中に入って確認できます。
$ docker-compose exec web bash $ gem environment RubyGems Environment: - RUBYGEMS VERSION: 3.0.3 - RUBY VERSION: 2.6.5 (2019-10-01 patchlevel 114) [x86_64-linux] - INSTALLATION DIRECTORY: /usr/local/bundle ...(略)これでgemを永続化する環境を構築できたことになります。
試しに、gemを追加、Gemfile+ gem 'rails-erd'その後、
bundle install
してみます。$ docker-compose exec web bundle installそして、一度コンテナを削除した後に再度起動してみます。
$ docker-compose down $ docker-compose up問題なくコンテナを起動できました!
コンテナ削除前にインストールしたgemもちゃんと入っています。
$ docker-compose exec web bundle list |grep 'rails-erd' * rails-erd (1.6.0)参考
今回の作業にあたり、以下のページを参考にさせていただきました。とても勉強になりました。ありがとうございます?♂️
- 投稿日:2020-07-08T19:42:38+09:00
form_with を使った検索フォームに初期値 value を設定する方法
筆者はRails6.0ですが、Rails5(Rails4も?)でも動くはずです。またSearchkick(Elasticsearchを簡単にするGem)を使っているので、もしかすると環境依存があるかもしれません。
最新のRailsでは
form_with
を使った記法が推奨されています。しかしこと検索フォームの作成においては、未だに情報が少なく苦戦したのでメモ。正解のコード
# index.html.erb <%= form_with model: @post, method: "get", local: true do %> <%= text_field_tag :search, params[:search], class: "form-control ds-input", placeholder: "検索‥" %> <% end %>初期値valueを入れる場合は
text_field
は使えないようです。form_withを使っているのでURLの指定は不要ですがmethod: "get", local: true
の記述は必要でした。
local: true
というのは form_with のデフォルト設定でAjaxによるHTTPリクエストを送ってしまうのをやめさせているようです(Ajaxだと画面遷移してくれないらしい)。
text_field_tag
の引数たちをかっこやハッシュ?({}
)で囲んでる記述も見かけましたが、よくわからんがこれでいいようです。post_controller.rbdef index @posts = self.query end private def query if params[:search] # ここの検索方法の記述はSearchkickを使っているためこうなので、標準の検索方法に置き換えるなどしてください Post.search params[:search], fields: [:name, :description, :category_name] else Post.all end end前提知識
筆者はProgateあがりですが、RubyやRailsの公式チュートリアルをやっていないせいかformまわりの基礎知識がバラバラです?もしかすると当たり前かもしれませんが、知らなかった知識を書いときます。
text_field は text_field_tag のヘルパー
フォームの中身に使う
text_field
はtext_field_tag
のヘルパーであり、ヘルパーとは簡単にいうと記述を省略して書けるようにしてくれるものらしい?フォームのコントロールの命名法や大量の属性を扱わなければならず、フォームのマークアップは作成もメンテナンスも退屈な作業になりがちです。そこでRailsでは、フォームのマークアップを生成するビューヘルパーを提供し、こうした煩雑な作業を行わないで済むようにしました
https://railsguides.jp/form_helpers.html最初の引数はインスタンス変数名、2番目の引数はオブジェクトを呼び出すためのメソッド名
たとえば、コントローラで
@person
が定義されており、その人物の名前がHenryの場合<%= text_field(:person, :name) %> # こうなる? <input id="person_name" name="person[name]" type="text" value="Henry"/>なので
<%= text_field :search, params[:search] %> # と書いてしまうと、こうなってしまう? <input id="search_" name="search[]" type="text">Railsの検索に search.html.erb はいらない
検索をする場合は search.html.erb を作成して、ルーティングにGETで追加して、index.html.erb と同じコードを毎回貼り付けてました(笑)コントローラー側でキーワードがあるかないかでif分岐してやればスッキリ書けます。
参考
- 投稿日:2020-07-08T18:54:52+09:00
Rails5でECサイトを作る⑤ ~Customerモデル編~
はじめに
架空のベーカリーで買い物できるECサイトを作るシリーズ、Rails5でECサイトを作る④の続きです。
ようやく機能の実装に入ります。まずは顧客サイトの親モデル周辺機能から順番に……ということで、今回はCustomerモデルのCRUDを中心に作ります。ソースコード
https://github.com/Sn16799/bakeryFUMIZUKI
deviseのコントローラ
deviseで新規登録できず、何でだろうと思ったらコントローラ側にパラメータの許可を与えていなかったためと判明しました。
app/controllers/customers/registrations_controller.rbclass Customers::RegistrationsController < Devise::RegistrationsController before_action :authenticate_customer! before_action :configure_permitted_parameters, if: :devise_controller? protected def after_sign_up_path_for(resource) customer_top_path end def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:is_active, :first_name, :first_name_kana, :family_name, :family_name_kana, :post_code, :address, :tel]) end protected def update_resouce(resource, parans) resource.update_without_password(params) end endついでにsessions_controllerもログイン、ログアウト後の遷移先を指定しておきました。
app/controllers/customers/sessions_controller.rbclass Customers::SessionsController < Devise::SessionsController protected def after_sign_in_path_for(resource) customer_top_path end def after_sign_out_path_for(resource) customer_top_path end endBootstrapのflashを使えるようにする
Bootstrapでは、class指定だけできれいに整ったflashメッセージを使うことができます。
部分テンプレートを作っておくとrenderで呼び出せて便利です。app/controllers/application_controller.rbadd_flash_types :success, :danger, :infoapp/helpers/application_helper.rbdef flash_class_for flash_type case flash_type when 'success' then 'alert-success' when 'danger' then 'alert-danger' when 'info' then 'alert-info' end endapp/views/layouts/_flash.html.erb<% flash.each do |type, msg| %> <div class="alert <%= flash_class_for(type) %> row" role="alert"> <a class="close" data-dismiss="alert">×</a> <%= msg %> </div> <% end %>app/views/layouts/application.html.erb<body> <%= render 'layouts/header' %> <div class="container-fluid"> <%= render 'layouts/flash', flash: flash %> <div class="row"> <%= yield %> </div> </div> <%= render 'layouts/footer' %> </body>Controller
多くの機能は基本的なCRUDですが、退会処理のみ異なります。
編集画面にて「退会する」ボタンを押下(withdrawアクション呼出) ↓ 退会確認画面に遷移 ↓ 「退会する」ボタンを押下(alert出現) ↓ 「はい」を押すと退会処理が行われる(withdraw_doneアクション呼出)以上のような流れになっています。
また、退会処理はCustomerデータの削除ではなく、is_activeカラムをtrueからfalseに変更する処理としています。app/controllers/customers_controller.rbclass CustomersController < ApplicationController before_action :authenticate_customer! before_action :set_customer before_action :baria_customer def edit end def show end def update @customer = current_customer if @customer.update(customer_params) redirect_to customer_path(@customer), success: 'お客様情報が更新されました!' else flash[:danger] = 'お客様の情報を更新出来ませんでした。空欄の箇所はありませんか?' render :edit end end def withdraw end def withdraw_done @customer = current_customer @customer.update(is_active: false) reset_session redirect_to customer_top_path, info: 'ありがとうございました。またのご利用を心よりお待ちしております。' end private def customer_params params.require(:customer).permit(:is_active, :first_name, :first_name_kana, :family_name, :family_name_kana, :post_code, :address, :email, :tel, cart_items_attributes: [:_destroy]) end def set_customer @customer = current_customer end # 他者のページにアクセスしようとすると直前のページに戻る def baria_customer if params[:id].to_i != current_customer.id redirect_back(fallback_location: root_path) end end endView
Sign_up
app/views/customers/registrations/new.html.erb<div class='col-lg-6 offset-lg-3 offset-2 space'> <div class='row'> <h2>新規会員登録</h2> </div> <div class='row'> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render 'devise/shared/error_messages', resource: resource %> <div class='form-group row space'> <div class='col-lg-4'> <h5><%= f.label :名前 %></h5> </div> <div class='col-lg-8'> <div class="row"> <div class="col-lg-6"> (姓)<%= f.text_field :family_name, autofocus: true, autocomplete: 'family_name', class: 'form-control' %> </div> <div class="col-lg-6"> (名)<%= f.text_field :first_name, autofocus: true, autocomplete: 'first_name', class: 'form-control' %> </div> </div> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :フリガナ %></h5> </div> <div class='col-lg-8'> <div class="row"> <div class="col-lg-6"> (セイ)<%= f.text_field :family_name_kana, autofocus: true, autocomplete: 'family_name_kana', class: 'form-control' %> </div> <div class="col-lg-6"> (メイ)<%= f.text_field :first_name_kana, autofocus: true, autocomplete: 'first_name_kana', class: 'form-control' %> </div> </div> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :Eメール %></h5> </div> <div class='col-lg-8'> <%= f.text_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :郵便番号(ハイフンなし) %></h5> </div> <div class='col-lg-8'> <%= f.text_field :post_code, autofocus: true, autocomplete: 'post_code', class: 'form-control' %> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :住所 %></h5> </div> <div class='col-lg-8'> <%= f.text_field :address, autofocus: true, autocomplete: 'address', class: 'form-control' %> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :電話番号(ハイフンなし) %></h5> </div> <div class='col-lg-8'> <%= f.text_field :tel, autofocus: true, autocomplete: 'tel', class: 'form-control' %> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :パスワード(6文字以上) %></h5> </div> <div class='col-lg-8'> <%= f.password_field :password, autocomplete: 'new-password', class: 'form-control' %> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :パスワード確認用 %></h5> </div> <div class='col-lg-8'> <%= f.password_field :password_confirmation, autocomplete: 'new-password', class: 'form-control' %> </div> </div> <div class='row'> <div class='col-lg-2 offset-lg-5 actions'> <%= f.submit '新規登録', class:'btn btn-danger' %> </div> </div> <% end %> </div> <div class="space"> <h4>すでに登録済みの方</h4> <h5><%= link_to 'こちら', new_customer_session_path %>からログインしてください</h5> </div> </div>Sign_in
html/app/views/customers/sessions/new.html.erb<div class='col-lg-6 offset-lg-4 offset-1 space'> <div class="row"> <h3> <span style="display: inline-block;">会員の方は</span> <span style="display: inline-block;">こちらからログイン</span> </h3> </div> <div class="row"> <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <%= render 'devise/shared/error_messages', resource: resource %> <div class="form-group row space"> <div class="col-lg-4"> <h5><%= f.label :Eメール %></h5> </div> <div class="col-lg-8"> <%= f.text_field :email, autofocus: true, autocomplete: "email", class: 'form-control' %> </div> </div> <div class="form-group row"> <div class="col-lg-4"> <h5><%= f.label :パスワード %></h5> </div> <div class="col-lg-8"> <%= f.password_field :password, autocomplete: "current-password", class: 'form-control' %> </div> </div> <div> <%= link_to "=>パスワードを忘れた方はこちら", new_password_path(resource_name) %> </div> <div class="form-group row space"> <div class="col-lg-2 offset-lg-5"> <%= f.submit "ログイン", class:"btn btn-danger" %> </div> </div> <% end %> </div> <div class="space"> <h4>登録がお済みでない方</h4> <h5><%= link_to "こちら", new_customer_registration_path %>から新規登録してください。</h5> </div> </div>編集
app/views/customers/edit.html.erb<div class='col-lg-10 offset-lg-1 space'> <div class='row'> <h2>登録情報編集</h2> </div> <div class='row'> <%= form_with(model: @customer, local: true, class: 'container') do |f| %> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :名前 %></h5> </div> <div class='col-lg-8'> <span style='display: inline-block;'> (姓)<%= f.text_field :family_name, autofocus: true, autocomplete: 'family_name', class: 'form-control' %> </span> <span style='display: inline-block;'> (名)<%= f.text_field :first_name, autofocus: true, autocomplete: 'first_name', class: 'form-control' %> </span> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :フリガナ %></h5> </div> <div class='col-lg-8'> <span style='display: inline-block;'> (セイ)<%= f.text_field :family_name_kana, autofocus: true, autocomplete: 'family_name_kana', class: 'form-control' %> </span> <span style='display: inline-block;'> (メイ)<%= f.text_field :first_name_kana, autofocus: true, autocomplete: 'first_name_kana', class: 'form-control' %> </span> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :Eメール %></h5> </div> <div class='col-lg-8'> <%= f.text_field :email, autofocus: true, autocomplete: 'email', class: 'form-control' %> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :郵便番号(ハイフンなし) %></h5> </div> <div class='col-lg-8'> <%= f.text_field :post_code, autofocus: true, autocomplete: 'post_code', class: 'form-control' %> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :住所 %></h5> </div> <div class='col-lg-8'> <%= f.text_field :address, autofocus: true, autocomplete: 'address', class: 'form-control' %> </div> </div> <div class='form-group row'> <div class='col-lg-4'> <h5><%= f.label :電話番号(ハイフンなし) %></h5> </div> <div class='col-lg-8'> <%= f.text_field :tel, autofocus: true, autocomplete: 'tel', class: 'form-control' %> </div> </div> <div class="form-group row"> <div class="col-lg-4 offset-lg-8"> <%= f.submit '編集内容を保存する', class:'btn btn-danger' %> <%= link_to '退会する', customer_withdraw_path(@customer), class:'btn btn-danger' %> </div> </div> <% end %> </div> </div>詳細
app/views/customers/show.html.erb<div class="col-lg-6 offset-lg-3 space"> <div class="container"> <h2>マイページ</h2> </div> <!-- お客様情報 --> <div class="container space"> <div class="row"> <div class="col-lg-5"><h3>お客様情報</h3></div> <div class="col-lg-2"> <%= link_to '編集', edit_customer_path, class: 'btn-sm btn-danger' %> </div> <div class="col-lg-4"> <%= link_to 'パスワード変更', new_customer_password_path, class: 'btn-sm btn-danger' %> </div> </div> <div class="row"> <div class="col-lg-3"> <h4>氏名</h4> </div> <div class="col-lg-9"> <%= @customer.full_name %> </div> </div> <div class="row"> <div class="col-lg-3"> <h4>カナ</h4> </div> <div class="col-lg-9"> <%= @customer.family_name_kana %> <%= @customer.first_name_kana %> </div> </div> <div class="row"> <div class="col-lg-3"> <h4>郵便番号</h4> </div> <div class="col-lg-9"> 〒 <%= @customer.post_code %> </div> </div> <div class="row"> <div class="col-lg-3"> <h4>住所</h4> </div> <div class="col-lg-9"> <%= @customer.address %> </div> </div> <div class="row"> <div class="col-lg-3"> <h4>電話番号</h4> </div> <div class="col-lg-9"> <%= @customer.tel %> </div> </div> <div class="row"> <div class="col-lg-3"> <h4>Eメール</h4> </div> <div class="col-lg-9"> <%= @customer.email %> </div> </div> </div> <!-- その他のリンク --> <div class="container space"> <div class="row"> <div class="col-lg-3"> <h4>配送先</h4> </div> <div class="col-lg-9"> <%= link_to '一覧を見る', addresses_path, class: 'btn btn-danger' %> </div> </div> <div class="row"> <div class="col-lg-3"> <h4>注文履歴</h4> </div> <div class="col-lg-9"> <%= link_to '一覧を見る', orders_path, class: 'btn btn-danger' %> </div> </div> </div> </div>退会確認
app/views/customers/withdraw.html.erb<div class="col-lg-10 offset-lg-1 space"> <div class="row space"> <h2>本当に退会しますか?</h2> </div> <div class="row space"> <h4>退会すると、会員登録情報や <br>これまでの購入履歴が閲覧できなくなります。 <br>退会する場合は「退会する」をクリックしてください。 </h4> </div> <div class="row space"> <%= link_to "退会しない",customer_path(current_customer),class: "btn btn-danger" %> <%= link_to "退会する", customer_withdraw_done_path(current_customer), method: :put, "data-confirm" => "本当に退会しますか?", class: "btn btn-danger" %> </div> </div>SCSS
app/assets/stylesheets/application.scss// initialize *{ margin:0; padding:0; box-sizing:border-box; color: #120136; } a{ text-decoration: none; } .space { padding: 50px 0 30px; }後記
今回、機能そのものは難しくないのですが、画面をレスポンシブ対応にしようとするとやはり手間取りました。ボタンは後ほどテーマカラーの色に変更する予定で、今はとりあえず全て赤にしてあります。目立つので。
また、Viewファイルに書いたリンクも一応は機能しますが、飛んだ先のページにまだ何も書いていないので、例の「Find me in ...」という文言が出るだけです。メインの画面を作ったことで、ようやくサイトらしい見た目になってきましたね。機能はまだまだですが、これから実装していきます。気になっていたIK●Aカラーも、進めるうちに「これはこれでアリなんじゃないか?」と思えるようになってきました。単に見慣れただけかも知れませんが。
写真を載せればきっとパン屋さんのサイトになると信じています。次回へ続く!
参考
RailsでTwitterBootstrapを使ったflashメッセージ
データ登録の際、適当な名前や住所が思い付かない時に便利。「文月太郎」とかでは流石に味気ないので……。
日本人名前自動生成機
ランダム日本地名ジェネレータ
- 投稿日:2020-07-08T17:46:45+09:00
rails の API doc作成ツール apipie-rails
rails で RESTful APIを作るに当たって、ドキュメンテーションツールを導入してみました
https://github.com/Apipie/apipie-rails
背景
rails でフロントとバックエンドと別々の担当者が作業しようとしたときに、フロントから受け渡されるべきパラメーターはなんで、その結果どんな結果がわたるのかどうやってドキュメントしようかなと。。
javadocみたいに定番の書式があるわけではなさそうなので、gem周りで検索してみました。
さらに、世の中の定番としては こんなかんじなのかな
https://qiita.com/jian-feng/items/c79955713e6dd8b5244a結果としてたどり着いたのが、 apipie-rails です
何が出来るのか
- ソースコード上にDSLを使ってAPIインターフェースを定義できる
- Webサーバー経由でHtmlドキュメントとして可視化された情報にアクセス出来る
- 引数のバリデーションをする → ArgumentError exception が上がる
導入
- "gem 'apipie-rails'" を追加(一旦、開発環境のみに導入されるように設定する)
- bundle install 後、rails g apipie:install で以下が追加されました a. routes.rb に apipie ルートが追加 b. config/initializers/apipie.rb が追加
- 投稿日:2020-07-08T17:01:11+09:00
[Docker][Rails] 新しくGemを導入した後の3コマンド
これはなに
Railsでアプリ開発中新しくGemを導入した後、若干躓いたので備忘録として残します。
ご指摘等あれば頂けると幸いです。3コマンド
gem bundle installdocker-compose run web bundle install参考記事
Docker Compose + Railsでイメージ内でbundle installしているはずなのにgemが無いとエラーがでる。docker-compose restart参考記事
dockerの起動、停止、再起動以上です!ありがとうございました!
- 投稿日:2020-07-08T16:58:43+09:00
【Rails】editアクションでのエラー解決
本記事を書いた理由
- 今回引っかかったエラーに対してどのように解決をしたのかを記録として残すことで同じミスをしないようにするため
目次
- どんなエラーが出たのか
- 考え方の道筋(実際にやってみたこと)
- 反省点
参考記事
どんなエラーが出たのか
todoアプリで編集機能をつけようとしたがうまく動かない。
具体的にはeditボタンという編集ページに飛ぶボタンを押すとエラー画面が出てくる状態。考え方の道筋(実際にやってみたこと)
結論
edit.html.erbのform_forの記述にパスを指定してあげる必要があった
実際にやってみたこと
エラー画面をみてeidt.html.erbに問題があると考える
コントローラの記述に誤りはないのか(編集したい情報をきちんと持ってきてこれているのか)
改めてedit.html.erbを見てみる(記述に変更を加えてみた)
エラー画面をみてeidt.html.erbに問題があると考える
- エラー画面からパスに問題があると推定した
- しかしこの時点でパスの何を直せばいいのかよくわからないので他のサイトからform_forの記述方法を調べて誤りがないか調べてみた。結果として特に誤りがないと判断した
コントローラの記述に誤りはないのか(編集したい情報をきちんと持ってきてこれているのか)
- コントローラの記述に誤りがないかどうか確認をした(特に問題なし)
- 確認のために
binding.pry
を使って編集したい情報が持ってこれているか確認をした(特に問題なし)- この時点でルーティングに問題がないと判断した(コントローラがしっかりと動いていることが確認できたため)
改めてedit.html.erbを見てみる(記述に変更を加えてみた)
- ルーティングとコントローラに問題がない時点でビュー(edit.html.erb)に問題があることを再確認した
- そうなるとエラー画面の通りform_forの記述方法に問題がある可能性が高いので改めて確認をした
- 結果としてform_forは自動的にcreateアクションやeditアクションを分けてくれる。しかしうまくいかない時もあるのでその時は直接アクション名(パス)を書いてあげることで解決できるらしい。(以下のように編集)
- 上記のようにパスを新しく指定することでエラーが解決できました。 *
action="index"
になっていますが実際はpatchを入れて編集内容を保存します。反省点
- エラー画面の詳細をもう少しみてそこから解決できる方法を考えるべきだった(最初のエラー画面の時点で意味がよくわからないのでコントローラの方などに原因があると考えてしまった)
- 上記のようなことをしなかったので解決までに時間がかかってしまった
- 投稿日:2020-07-08T16:17:00+09:00
Elasticsearchで多対多&階層化されたカテゴリーを検索対象に含める方法
前提
以下の前提で、ひとまずローカル環境(
Rails s
)での検索を実現する
- Ruby on Rails で多対多のモデル(例としてPostとCategoryとします)を使っている
- Ancestryを使ってモデルを多階層にしている(Categoryが多階層です)
- Elasticsearch と Kibana がインストール済み
- Searchkick(Elasticsearchを簡単にするGem)を使って検索をしたい
- Postを検索するときに検索対象にCategory.nameを含めたい
Post
とCategory
が中間テーブルを使って多対多の関係であり、Categoryだけ多階層になっている。構築がまだの方へのおすすめ記事
中間テーブル実装後にカテゴリ別ページを表示
https://qiita.com/otterminal/items/a8859a1fad0027a9bc5b多階層カテゴリでancestryを使ったら便利すぎた
https://qiita.com/Sotq_17/items/120256209993fb05ebacElasticsearchとKibanaのインストール(HomebrewでOSS版をインストール)
https://qiita.com/maztak/items/0f722ad01c982a96de59速攻で検索機能を実装できるsearchkickを調べた(Ruby)
https://qiita.com/kentosasa/items/f0af67f62f4692d68370ただしElasticsearch、Ancestry、Searchkickなどは更新により変更が大きいので色んな方法がネットに転がっているので注意。
公式リポジトリ
Searchkick
https://github.com/ankane/searchkickAncestry
https://github.com/stefankroes/ancestryコード
普通に
search_data
の中でcategories.map(&:name)
としてやれば良い。post.rbclass Post < ApplicationRecord searchkick has_many :post_category_relations has_many :categories, through: :post_category_relations def search_data { name: name, description: description, category_name: categories.map(&:name) } end endCategoryのインデックスはまだしてないのだが、今のところ以下のようになっている。
after_commit
以降のコードはカテゴリーが更新された時にPostをReindexしてね、というコードなので、今回の件とは直接は関係しない。category.rbclass Category < ApplicationRecord has_many :post_category_relations has_many :posts, through: :post_category_relations has_ancestry after_commit :reindex_post def reindex_post post.reindex end endSearchkick開発者の回答?
Rails: Elasticsearch :through association mapping
https://stackoverflow.com/questions/19721200/rails-elasticsearch-through-association-mapping/19751244Reindex は Rails Console の再起動の必要あり
あくまで開発環境(ローカル・developmentインデックス?)での話だが、Modelで
search_data
メソッドやMappingを変更した後に Rails Console を再起動せずにPost.reindex
しても、変更前の情報でインデックスしてしまうので注意。Kibanaでの検証Tips
http://localhost:5601/app/kibana#/dev_tools/console
Kibanaでのインデックス一覧やマッピング、検索結果の確認・検証において、上記コンソールを使うが、ここは(SwiftのPlaygroundのように)記述したブロックごとの実行が可能。なのでいちいちHistroyからメソッドを全部書き換えないでも、よく使うメソッドを全部貼り付けて適宜ブロックごとに実行すればいい。
# kiban_console # インデックスの削除(エイリアスでの指定は不可) DELETE /posts_development_20200708003504586 # インデックス一覧の確認 GET /_aliases?pretty # マッピングの確認 GET /posts_development/_mapping?pretty # すべてのドキュメントを返す(上限100件) GET /posts_development/_search { "query": { "match_all": {} }, "size": 100 } # 複数のフィールドを検索対象にして検索 GET /posts_development/_search { "query": { "multi_match": { "fields": [ "name", "description", "category_name"], "query": "美容" } } } # GET posts_development/_analyze { "text" : "美容" }その他のメソッドはこちらを参照
- 初心者のためのElasticsearchその1
- 初心者のためのElasticsearchその1
- Analyze API 公式ドキュメントその他
as_json
を使った関連付けされたデータの追加というQiitaもあったが、これは1対多で中間テーブルがない場合の記法のようである。
- 投稿日:2020-07-08T15:55:21+09:00
[Rails]gem 'ancestry'による多階層カテゴリーの導入[備忘録]
はじめに
初めて記事を投稿します。
某プログラミングスクールの課題でフリマアプリを作っていて、
gem 'ancestry'を導入してカテゴリー機能を実装したのでメモしておきます。
以下のようなものを目指します
なお、この記事ではデータベースにカテゴリーを入れて表示するところまでを記述します。
前提
Productモデル(カテゴリーを追加したいモデル)とCategoryモデルは実装してある。
大丈夫だとは思いますが、'Category'の複数形は'Categories'なので、
使うときは注意しましょう。(周りにそれが原因でエラーを出していた人がいたため。)導入
初めて使うgemのときは公式ドキュメントをまず参照。
https://github.com/stefankroes/ancestrygem 'ancestry'bundle installを実行
カテゴリーテーブルにデータを入れる
さて、早速 rails db:seed を実行してデータを入れようと思ったところ、上手いかない・・・
原因
1.DB設計の際、先にancestryカラムを追加していたが、データ型がintegerになっていた。
ancestryカラムはstring型です
2.null: falseが設定されており、下記のエラーが出た。Mysql2::Error: Field 'ancestry' doesn't have a default value解消
1.データ型を変更
rails g migration ChangeDatatypeAncestryOfCategoriesdef change change_column :categories, :ancestry, :string, index: true end2.null:false を外す
rails g migration ChangeColumnToNotNulldef up change_column :categories, :ancestry, :string, null: true end def down change_column :categories, :ancestry, :string, null: false endこれでエラーが出ずに通るようになったので、
データを記述してテーブルに入力db/seeds.rblady = Category.create(name: "レディース") lady_1 = lady.children.create(name: "トップス") lady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}]) #以下省略詳しくは公式ドキュメントに書いてありますが、入力されたCategoriesテーブルの中身を説明すると、
- 親要素(id: 1)のancestryは、nullが入る
- 子要素(id: 2)ののancestryは、親要素のid(1)が入る
- 孫要素(id: 3~20)のancestryは、親と子のidが入る(1/2)
これにより、多階層のカテゴリーを表現できる。一つのテーブル、一つのカラムで実装出来るのは管理もしやすくて使い勝手がいいですね。
ビュー
考え方を記載します。
products_controller.rbdef index # ancestry:nil つまり、親要素だけを取得 @parents = Category.where(ancestry: nil) end_category.html.haml- @parents.each do |parent| = parent.name - parent.children.each do |child| = child.name - child.children.each do |grandchild| = grandchild.name #細かい部分は省略しています #インデント等が正しくないですが、親に子を、子に孫を入れ子にしていくと上手く表示できます。category.scss// 親、子、孫に対して初期状態では display: none; // カテゴリーから探すにホバーされたときに、親要素を display: block; //親要素にホバーされたときには子要素、子要素にホバーされたときは孫要素に display: block; //ホバーされた要素自体に対しては、背景色や文字色を変更するとわかりやすくなります。このように連続してタブを出現させるには、
CSSを使う方法とJavaScriptを使う方法があると思いますが、
ホバーされたときに出現したり消えたりするだけならSCSSにまとめて書いた方が、
他の人が見たときに分かりやすいと思いこのような実装をしました。学んだこと
・必要なときに必要なテーブルを作り、必要なカラムを追加する。
・gemの使い方はまず公式ドキュメントを読む。何かの参考になれば幸いです。
ここまで記事を読んでくださりありがとうございました。
- 投稿日:2020-07-08T14:56:57+09:00
Mobile Safari は再起動時に POST リクエストする場合がある
Rails アプリケーションで、iPhone の Safari からのリクエストでだけ
ActionController::InvalidAuthenticityToken
例外が発生するので調査していたところ、Mobile Safari は再起動後、開いていたタブをロードする際に、POST リクエストで得たページであっても再度リクエストを送信しているらしいことがわかった。※ iOS 13.5.1 の iPhone 11 で検証
検証方法
下記の Ruby スクリプトを作成して
ruby server.rb -o 0.0.0.0
で起動# gem install sinatra が必要 require "sinatra" html = <<~HTML <!doctype html> <form action="/" method="post"> <input type="text" name="email"> <button type="submit">Submit</button> </form> HTML get "/" do html end post "/" do p params html end
- Mobile Safari で
http://[上記スクリプト実行したPCのアドレス]:4567
にアクセス- 適当に入力して submit
- Mobile Safari を終了
- Mobile Safari を起動 (確認などなしでページが表示される)
この手順でログを見ると、4 で 2 と同じリクエストが送られていることが確認できた。
x.x.x.x - - [08/Jul/2020:14:24:37 +0900] "GET / HTTP/1.1" 200 131 0.0072 {"email"=>"foo"} x.x.x.x - - [08/Jul/2020:14:24:41 +0900] "POST / HTTP/1.1" 200 131 0.0022 {"email"=>"foo"} x.x.x.x - - [08/Jul/2020:14:24:48 +0900] "POST / HTTP/1.1" 200 131 0.0007Rails で InvalidAuthenticityToken 例外が起こる理由と対策
Rails で...
- Cookie をセッションストアとして使っていて、かつ有効期限が Session (デフォルト)
protect_from_forgery with: :exception
を指定している場合に、上記のように 2 度 POST リクエストが送られると、2 度めのリクエストでは Cookie がクリアされているので
session[:_csrf_token]
が nil になり、params[:authenticity_token]
の検証が失敗し、InvalidAuthenticityToken 例外が発生してしまう。解決策としては下記のものが考えられる。
- POST でページを render せず、redirect してかならず GET させるようにする
- protect_from_forgery で null_session などにする
- Cookie の有効期限を伸ばす
3 の場合は config/initializers 下に適当なファイルを作って下記の内容を記述すればよい。
# key の部分はなんでもいいがデフォルトは "_#{アプリケーション名}_session" # ここでは有効期限を 2 週間にしている Rails.application.config.session_store :cookie_store, key: "_foo_session", expire_after: 2.weeks
- 投稿日:2020-07-08T11:17:19+09:00
エラー:Trying to get property 'id' of non-object
- 投稿日:2020-07-08T11:04:20+09:00
form_withのform.〇〇について
form_withのform.〇〇について
form.withのform.〇〇について、わからないところがあったので、備忘録としてまとめていく。
form.text_field
<%= form.text_field :title %>幅20文字の入力欄が設定される
form.text_area
<%= form.text_area :content %>40×20の入力欄が設定される
form.email_filed
<%= form.email.field :email %>投稿内容に@が含まれていないとメールアドレスとみなされず、エラーになる。
テスト時に注意!他にもいろいろあるが今回はメールの入力欄にてテスト時にエラーが出たので、備忘録としてまとめた。
- 投稿日:2020-07-08T09:09:21+09:00
i18nのロケールをJavaScriptにわたす
多言語対応のアプリを作成中に詰まった
jQueryとAjaxを使って、親カテゴリを選択すると子カテゴリの候補がプルダウンで表示されるようにしました。
ところが、子カテゴリだけ、デフォルト表記のベトナム語でしか翻訳されてませんでした。例:親カテゴリ => 質問
子カテゴリ => 1,Ngôn ngữ và văn hóa
2,Tập huấn kỹ thuật
3,Ứng dụng và thủ tục原因
Ajaxで子カテゴリを取得する際、コントローラにロケールを渡してないことが原因でした。
category_pulldown.js$("#parent").on("change", function () { // 選択した親カテゴリからIDを取得 var id = document.getElementById("parent").value; // 中略 $.ajax({ type: 'GET', data: { parent_id: id }, // IDをparams[:parent_id]に入れて送信 url: '/categories/pulldown', // categoriesコントローラにて、受け取ったIDで子カテゴリを取得 dataType: 'json', }).done(function (children) { // 取得した子カテゴリを表示させる処理...一部抜粋ですがロケールの取得は以下の方法でやってました。
application_controller.rbbefore_action :set_locale private def set_locale I18n.locale = locale end def locale @locale ||= params[:locale] ||= I18n.default_locale end def default_url_options(options = {}) options.merge(locale: locale) end現在のロケールをJava Scriptへわたす
まずは、inputタグを使って現在のロケールを保持させます。
以下のようにtype="hidden"
と書くと、ユーザーから見ても画面には表示されません。hoge.html.erb<input type="hidden" class="current_locale" value="<%= I18n.locale %>">次に
locale: $('.current_locale').val()
を追加すると
親子カテゴリの翻訳言語が統一されました。category_pulldown.js$("#parent").on("change", function () { // 選択した親カテゴリからIDを取得 var id = document.getElementById("parent").value; // 中略 $.ajax({ type: 'GET', data: { parent_id: id, locale: $('.current_locale').val() }, // IDをparams[:parent_id]に入れて送信 url: '/categories/pulldown', // categoriesコントローラにて、受け取ったIDで子カテゴリを取得 dataType: 'json', }).done(function (children) { // 取得した子カテゴリを表示させる処理...HTMLはRubyからもJavaScriptからも情報の受け渡しができるので便利ですね
- 投稿日:2020-07-08T09:01:12+09:00
【Ruby on Rails】Chartkickでカラム別集計の円グラフを作成
はじめに
railsでグラフを挿入したかったのでchartkickを使用しました。
導入自体も容易なので、集計結果の円グラフ化の方法を記載します。参考にしたページ
-chartkick公式ドキュメント
-Railsでシンプルなグラフを扱うならchart-js-rails よりchartkickを使うべし
-爆速で円グラフを実装する[5分]環境
ruby 2.5.1
Rails 5.2.4.3前提と目標
ユーザの意見をまとめたvotesテーブルのopinionカラム(integer)から円グラフを作成する。
opinionカラムの値が1の場合は賛成、0の場合は反対で集計しますが、他の値やNULL値が混ざっている状態で作成します。votesテーブル
id opinion 1 1 2 1 3 0 4 1 5 0 6 2 7 NULL 8 3 9 NULL 10 0 完成図
※実機では円グラフの各要素にマウスカーソルを当てると要素数が表示されます。Chartkickの導入
参考ページに記載されていますが改めて記載します。
Gemfilegem "chartkick" #追記するGemfile$ bundle installapp/javascript/packs/application.js//= require chartkick //= require Chart.bundle #2行追記するviewページへの反映
表示させるページはどこでもいいですが今回はpostsコントローラーのindex.html.erbにグラフを表示させたいと思います。
app/views/posts/index.html.erb<%= pie_chart Vote.group(:opinion).count%>これだけでは何を表しているのかよくわからないグラフになっています。。
そこでコントローラーファイルに変数とグラフ用のメソッドを定義します。
app/controllers/posts_controller.rbdef index @opinion = Vote.pluck(:opinion) @aggregate = aggregateOpinion(@opinion) @sum = sumOpinion(@opinion) end def aggregateOpinion(array) result = [["賛成",0],["反対",0],["どちらでもない",0],["無回答",0]] array.each do |i| if i == 1 result[0][1] += 1 elsif i == 0 result[1][1] += 1 elsif i == nil result[3][1] += 1 else result[2][1] += 1 end end return result end def sumOpinion(array) result = [["総投票数",0],["有効投票数",0],["無効投票数",0]] array.each do |i| if i == nil result[2][1] += 1 else result[1][1] += 1 end end result[0][1] = array.length return result end解説
pluckメソッドで対象のカラムだけを配列で取得しています。
その後、aggregateOpinionメソッドで円グラフ用のデータに整形しています。result配列のインデックスがそのまま円グラフの表示順になるのでパラメータが
NULL値のものを最後にしてその次に所謂"その他"(今回の場合は[どちらでもない]という名前)が来るようにしています。sumOpinionメソッドは直接グラフには関係ありませんが総投票数の情報をグラフに載せることが難しいので別途作成しています。
以上の内容を踏まえ、改めてビューページに反映させたいと思います。
app/views/posts/index.html.erb<%= pie_chart @aggregate, width: "500px"%>これでも問題ないですが最後にレイアウトとsumOpinionメソッドで取得した内容をtable要素で表示させて完成です。
app/views/posts/index.html.erb<%= pie_chart @aggregate,colors: ["#3333cc","#cc3333","#339966","333"], donut: true , width: "500px"%> <table border="1" width="500"> <tr> <th>総投票数</th> <th>有効投票数</th> <th>無効投票数</th> </tr> <tr> <th><%=@sum[0][1]%></th> <th><%=@sum[1][1]%></th> <th><%=@sum[2][1]%></th> </tr> </table>終わりに
いかがでしたでしょうか。もう少しメソッドの部分に応用を効かせられたなぁと自分でも思いますね。。
もちろんchartkickが対応しているのは円グラフだけではないので是非調べてみてください。
見て頂いてありがとうございました!
- 投稿日:2020-07-08T01:29:04+09:00
Arel::Nodesを最低限読めるようになりたい
Arel::Nodes を使って書かれたコードを最低限読んで理解できるようになりたい方のために、例文を紹介しながら、どのような処理なのか読み解いていきます。
※ 自分が実際に使用されているのを見たことがあるもののみを紹介しています。
ちなみに、arel_table から作れる条件式はこちらが参考になりました。
ActiveRecordのarel_tableから作れる条件式まとめArel::Nodes#build_quoted
build_quoted メソッドは文字列などの値をラップして、Arel::Nodes::NamedFunction などの引数に渡せるようにします。
Arel::Nodes.build_quoted(" ")上記例文は
" "
をラップしているだけですね。具体的な使用例は Arel::Nodes::NamedFunction を参照ください。Arel::Nodes::NamedFunction
Arel::Nodes::NamedFunction は SQL の任意の関数を呼び出すことができます。
第1引数に関数名、第2引数に関数に渡す引数(配列)、第3引数にエイリアス名(任意)を渡します。例文1users_table = User.arel_table separator = Arel::Nodes.build_quoted(" ") User.select( Arel::Nodes::NamedFunction.new( "CONCAT", [users_table[:last_name], separator, users_table[:first_name]], "name" ) ) # => SELECT CONCAT(`users`.`last_name`, ' ', `users`.`first_name`) AS name FROM `users`例文では、第1引数に
CONCAT
が指定されています。
CONCAT 関数は複数の文字列を結合してくれるもので、ここでは User テーブルのlast_name
と" "
、first_name
を繋ぎます。
例えば、last_name
が「Yamada」でfirst_name
が「Taro」だとすると「Yamada Taro」となります。
苗字と名前を分けてDBに保存しているが、つなげて取り出したい時の使い方になります。Arel::Nodes::NamedFunction に関してはもう一つ例文を紹介します。
例文2users_table = User.arel_table format = Arel::Nodes.build_quoted("%Y-%m-%d") User.select( Arel::Nodes::NamedFunction.new( "DATE_FORMAT", [users_table[:created_at], format], "registration_date" ) ) # => SELECT DATE_FORMAT(`users`.`created_at`, '%Y-%m-%d') AS registration_date FROM `users`DATE_FORMAT 関数は名前の通り、日時を指定のフォーマットに整形してくれる関数です。
ここでは User テーブルのcreated_at
を%Y-%m-%d
というフォーマットに変換します。
「2020-07-01」みたいな形です。
"日時"ではなく"日付"として取り出したい時の使用例になります。Arel::Nodes::SqlLiteral
生の SQL 文字列を作ることができます。SQL の構文中に必要な文字列をラップするのに使われることが多いです。
ちなみに、Arel で唯一パブリックな関数であるArel.sql()
は、内部で Arel::Nodes::SqlLiteral を使用しています。Arel::Nodes::SqlLiteral.new("utf8_general_ci")例文は
utf8_general_ci
をラップしているだけですね。具体的な使用例は Arel::Nodes:InfixOperation を参照ください。Arel::Nodes::InfixOperation
Arel::Nodes::InfixOperation の「infix operation」は二項演算という意味で、2つの値・変数の計算を行うことができます。
第1引数に演算子、第2引数に演算子の左側の値、第3引数に演算子の右側の値が入ります。User.where( Arel::Nodes::InfixOperation.new( "COLLATE", User.arel_table[:nickname], Arel::Nodes::SqlLiteral.new("utf8_general_ci") ) .matches(Arel::Nodes.build_quoted("トム%")) ) # => SELECT `users`.* FROM `users` WHERE `users`.`nickname` COLLATE utf8_general_ci LIKE 'トム%'例文では演算子として
COLLATE
を指定しています。COLLATE 関数は検索条件で照合順序を指定する時に使用されるものです。そして演算子の左にくるのが User テーブルのnickname
、右に来るのがutf8_genera_ci
です。ちなみに、utf8_genera_ci は UTF-8 文字コードで半角や全角、濁点を区別し、英語の大文字小文字を区別しない指定です。
つまり、User の nickname の「AAA」と「aaa」は一緒とみなして、「あああ」と「アアア」や「ぁぁぁ」は区別するということになります。
そして、最後の
matches()
でトム%
を指定しているため、この例文は nickname が「トム」から始まる User を取得するというクエリになります。このとき、"とむ" や "ドム" などから始まる User はヒットしません。Arel::Nodes::OuterJoin
外部結合(OUTER JOIN)する際に使用します。使い方としては、JOIN句の第2引数に
Arel::Nodes::OuterJoin
を指定し、最後にjoin_sources
を呼び出すことで結合できます。messages_table = Message.arel_table users_table = User.arel_table Message.joins( messages_table .join(users_table, Arel::Nodes::OuterJoin) .on( messages_table[:user_id].eq(users_table[:id]) .and(users_table[:is_active].eq(true)) ) .join_sources ) # => SELECT `messages`.* FROM `messages` LEFT OUTER JOIN `users` ON `messages`.`user_id` = `users`.`id` AND `users`.`is_active` = TRUE例文では、Message テーブルに User テーブルを外部結合しています。
結合の条件としては、Message テーブルのuser_id
と User テーブルのid
が等しい、かつ User テーブルのis_active
がtrue
のものという条件になります。
つまり、active な状態の User のみ外部結合するという条件になっているわけです。Arel::Nodes::Descending, Arel::Nodes::Ascending
Arel::Nodes::Descending は ORDER 句の DESC (降順)の部分を担当します。Arel::Nodes::Ascending は ASC (昇順)です。引数には、ORDER BY の右側の値が入ります。
User.order( Arel::Nodes::Descending.new( User.arel_table[:user_type].in([10,11,12]) ) ) # => SELECT `users`.* FROM `users` ORDER BY `users`.`user_type` IN (10, 11, 12) DESC例文では、Arel::Nodes::Descending の引数に
User.arel_table[:user_type].in([10,11,12])
が指定されています。
in 関数がついていなければ、この例文はUser.order(user_type: :desc)
と同じクエリになりuser_type
の降順に並ぶわけですが、.in([10,11,12])
と絞り込むことで、これらを一つのグループとして並び替えています。
この場合はuser_type
が 10, 11, 12 のユーザー群が前方に、それ以外のユーザー群が後方に来るように並びます。
前方と後方に分かれた後の並びは特に指定していないので、id の昇順になります。
特定の条件に合致するユーザーを前方に持ってきたいときなどに使用されますね。Arel::Nodes::Case
SQL の CASE 構文を表現することができます。使い方は直感的ですので、例文を参考にしてください。
products_table = Product.arel_table new_cond = products_table[:created_at].gt(Time.zone.now - 3.days) Product.order( Arel::Nodes::Ascending.new( Arel::Nodes::Case.new .when(new_cond).then(1) .else(9) ) ) # => SELECT `products`.* FROM `products` ORDER BY CASE WHEN `products`.`created_at` > '2020-07-01 12:34:56' THEN 1 ELSE 9 END ASC
Arel::Nodes::Case.new
の箇所では、Product が作成されて3日以内であれば1
、そうでなければ9
という値をとります。これと Arel::Nodes::Ascending を組み合わせることで、3日以内に作成された Product を前方にそれ以外を後方に並び替えています。「Arel::Nodes::Descending, Arel::Nodes::Ascending」 の例文と同じ感じですね。
最後に
例文で見たように Arel::Nodes を使うことで表現力はかなりアップします。しかし、慣れていない方にとっては何を行っているのかわかりづらいという欠点もあります。また、Arelでクエリを書くのはやめた方が良い5つの理由 という記事もあり、Arel を使うのには賛否両論があるようです。
ただ、環境によってはすでに Arel が頻繁に使用されていて、書かないまでも読む必要はあるような場面は結構多いように思います。こちらの記事がそのような環境に出くわした方の参考になれば幸いです。
参考