- 投稿日:2020-02-09T23:53:46+09:00
Rails6アップデートをproduction環境へリリースしたらインスタンスごと殺してしまった話
あまり目立っていない、地味に追加された機能によって殺されてしまいました
![]()
よし!Rails6アップデートリリースするぞ!
![]()
bundle update rails
を実行し、テストが全て通るよう修正し、Warningを潰し、stagingで入念に動作確認しました。そして、もう大丈夫!productionリリースしてもいいよね! と、リリースへの機運が高まりきったところで事件は起きました。
productionだけど社内用のクローズドな環境へリリース
![]()
全体のproductionへリリースする前に、社内用の隔離されたproduction環境へリリースしてみました。
無事リリースが成功し、「ふんふん、まあ大丈夫そうかな??」と触っていたら、急に503エラーが発生してうんともすんともいわなくなりました。
エラー監視(Sentry)を見ても、特にアプリケーションエラーが発生したような痕跡はなさそう。
何が起きたのかと思って急いでアプリケーションサーバへsshしてアプリケーションログを見てみました。
が、特にめぼしいエラーは見当たらず。。![]()
原因を探っていると、急にsshが遮断されました。
どうやらインスタンスがterminateされた模様。。え?なんかヤバイことしちゃった?
![]()
さすがに焦りました。
社内用のクローズドな環境なので影響範囲は社内に限られるのが不幸中の幸いでした。
社員のみなさんにごめんなさいして、調査を継続しました。
AWSコンソールからterminateされたインスタンスの情報を見てみても、CPU使用率やメモリ使用率などが異常に上がっているということはなさそうでした。
そして、「おっ復旧したみたい」という声が社内から上がってきました。
えっ?何もしてないけど???
![]()
原因も分かってないし、何もしてないのに、確かに復旧していました。
どうやらterminateされたインスタンスの代わりに新たなインスタンスが立ち上がったようです。
新たなインスタンスが元気にリクエストを捌いていました。
新しいインスタンスはRais6アップデート前の状態でデプロイされています。Auto Scaling?
勝手に立ち上がるってことは、Auto Scalingかな?
と思い、Auto Scalingを見てみました。確かにアクティビティ履歴にはTerminatingの履歴がありました。
原因を見ると、
system health check failure.
どうやら、Auto Scaling(ELB)からのヘルスチェックに応答しなかったせいで、unhealty扱いされてインスタンスterminate、という流れだったようです。
ふーん、なるほど。。でもなんで??
![]()
nginxのアクセスログを見る
アプリケーションエラーが原因ではなさそう、ということは分かっていました。
では、何が原因か?ヘルスチェックに応答できないって、そもそもヘルスチェックのリクエストに何返してるの??と思い、nginxのアクセスログを見てみました。
新たに立ち上がった元気なインスタンスへsshしてnginxのアクセスログを見てみました。
time:<アクセス時間> host:192.168.0.54 uri:/health status:200 size:33 reqsize:- method:GET referer:- ua:ELB-HealthChecker/2.0 reqtime:0.008 apptime:0.008 upstream:127.0.0.1:8080 vhost:192.168.8.12 time:<アクセス時間> host:192.168.2.116 uri:/health status:200 size:33 reqsize:- method:GET referer:- ua:ELB-HealthChecker/2.0 reqtime:0.013 apptime:0.012 upstream:127.0.0.1:8080 vhost:192.168.8.12 time:<アクセス時間> host:192.168.1.89 uri:/health status:200 size:33 reqsize:- method:GET referer:- ua:ELB-HealthChecker/2.0 reqtime:0.007 apptime:0.000 upstream:127.0.0.1:8080 vhost:192.168.8.12こんな感じでヘルスチェックのリクエストが来ていました。
status:200
を返していますね。ちなみに、Railsアプリケーションの動作確認を目的にヘルスチェックのリクエストを受けるエンドポイントを用意しています。
config/routes.rbget '/health' => 'health_statuses#show'この状態で、Rails6アップデートを再度リリースしてみました
![]()
おっ、やっぱりまた落ちた
無事リリースが成功すると、nginxのアクセスログが、
time:<アクセス時間> host:192.168.0.54 uri:/health status:403 size:1187 reqsize:- method:GET referer:- ua:ELB-HealthChecker/2.0 reqtime:0.024 apptime:0.024 upstream:127.0.0.1:8080 vhost:192.168.8.12 time:<アクセス時間> host:192.168.2.116 uri:/health status:403 size:1187 reqsize:- method:GET referer:- ua:ELB-HealthChecker/2.0 reqtime:0.005 apptime:0.008 upstream:127.0.0.1:8080 vhost:192.168.8.12 time:<アクセス時間> host:192.168.1.89 uri:/health status:403 size:1187 reqsize:- method:GET referer:- ua:ELB-HealthChecker/2.0 reqtime:0.002 apptime:0.000 upstream:127.0.0.1:8080 vhost:192.168.8.12おっと、
status:403
に変わりました。
この状態で数十秒経過したら、先ほどと同じくインスタンスterminateされてsshが切断されました。terminateされてhealthyなインスタンスが無くなってしまうのでELB側で503を返していた、ということだったんですね。
Rails6で403エラーを返す変更といえば・・
そう、 ActionDispatch::HostAuthorization ですね。
production環境には、以下のようにconfig.hostsを設定していました。
config.hosts << '.example.com'ELBからのヘルスチェックは、
host:192.168.0.54
のようにIPアドレスを指定した形式なのでActionDispatch::HostAuthorization
のチェックに引っかかって403エラーを返してしまっていたようです。どう回避したか
以下の通りconfig.hostsの設定をクリアしました。
config.hosts.clearこれで、
ActionDispatch::HostAuthorization
のチェックを無効にできます。(せっかく追加された機能を無効化するのは心苦しいですが・・)
あとで調べたら、そもそもconfig.hostsを設定しなければ、デフォルトでdevelopment環境以外ではチェックされないようですね。
https://github.com/rails/rails/blob/v6.0.2.1/railties/CHANGELOG.md#rails-600beta1-january-18-2019
In other environments
Rails.application.config.hosts
is empty and noHost
header checks will be done.ひとまず、これでヘルスチェックに応答するようになり、社内用production環境へリリースできました。
その後、無事全体のproductionへもリリースできました![]()
まとめ
ELBからのヘルスチェックを使用している場合は
ActionDispatch::HostAuthorization
に注意してください。今回、ELBがあるのはproductiondだけというインフラ構成の違いによって、stagingでは気づくことができませんでした。
環境ごとの違いをしっかり考慮しておく必要がありますね。
本来は、production と staging はインフラ構成も完全に一致させておくべきです。
しかし、様々な事情によってできないケースもあると思うので難しいところですね。あと、stagingとproductionの間にもう1クッションとして、社内用のクローズドなproduction環境があるのは本当に助かりました。
もし、いきなり全体productionへリリースしていたら全インスタンス死亡でサービス停止という最悪の事態になるところでした先人の知恵、努力に感謝です
![]()
- 投稿日:2020-02-09T21:26:22+09:00
Ruby on Rails 環境構築チートシート
アプリ開発をスタートする際の環境構築について
1からアプリ開発をスタートさせる場合(git cloneからスタートしない場合)
前段のPCの環境構築はこちら
→https://qiita.com/kenkentarou/items/e592a10a54c9c7e110b9
上記が終わっている前提で進めます!
- 新アプリ名のディレクトリを作成
$ cd ~/<ディレクトリ名>
コマンドで、アプリディレクトリに移動$bundle init
→Gemfileを作成$ ruby -v
で現状のrubyのバージョンを確認- Gemfileでrailsのバージョンを確認
rbenvでrubyのバージョン設定
- ターミナルで
$rbenv versions
を実行し、rbenvで管理されているrubyのバージョンを確認→開発で使用したい該当のバージョンがなければインストールする必要あり。
$ rbenv install 2.6.5
(バージョンは一例)※rubyのインストール時にエラーが出た場合
If you don't have the version you need, try upgrading ruby-build: brew update && brew upgrade ruby-buildこのエラーが出れば、指摘通り以下のコマンド実行
$brew update
$brew upgrade ruby-build
その後、もう一度トライすると上手くいく
$rbenv install 2.6.5
今回の開発で使うrubyのバージョンを指定
$ rbenv global 2.6.5
以下のコマンドで反映
$ rbenv rehash
gemのインストール
$ bundle install --path vendor/bundle
※以下のようなbundlerのバージョンエラーが出たら
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→
$ gem install bundler
で最新のbundlerをインストール
→$ rbenv rehash
→$ bundle -v
でbundlerのバージョン確認
→その後もう一度
$ bundle install --path vendor/bundle
rails new
$ cd ~/<ディレクトリ名>
で、アプリのディレクトリに移動
$ bundle exec rails new . -d mysql --skip-turbolinks --skip-test --skip-coffee
→.はカレントディレクトリを意味する
→gemfileの上書きを聞かれた場合は、yes入力でgemfileを上書き
→うまく行かなければ
$ bundle update
※ mysqlエラー出た場合
以下をやってみる
$ bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl/lib"
$ bundle install
nodeを使う場合(nodeを使わないのであれば飛ばしてください)
nodeのバージョンなどをいじる場合は以下の記事を参照
→https://qiita.com/kenkentarou/items/f27bd4e49af6fe429429※nodeとは
関係性
ruby <-> node
rbenv <-> nodebrewやnvmnodeは一つの言語(node.jsと同義)で、nodeは、言語としても扱うし、サーバーサイドで動くためにコンパイルするシステムとしても使われる
データベースの設定
データベースの設定ファイルを作成
通常の現場では、datebase.yml.defaultというファイルを作成して、設定情報はrootなどセキュリティー上影響ない初期値を入れておき、こちらをgit管理します。
ローカルで作業する際には、datebase.yml.defaultをコピーして、database.ymlというファイルを作成し、自身のPC上の設定に合わせ修正したものを使います。自身の設定に合わせたdatabase.ymlを作成しないと、ローカルでデータベースを走らせることができません。
以下のコマンドを実行し、database.yml.defaultをコピーして、自身の環境に合わせたdatabase.ymlを作成しましょう。
$ cp config/database.yml.default config/database.ymlローカル上にデータベーステーブル作成
$ bundle exec rails db:create
→テーブル作成
$bundle exec rails db:migrate
→migrationが必要なファイルがあれば実行
$ bundle exec rails s
git flowでの管理の場合
git flowがPCに入っている前提で、
$git flow init
→アプリのディレクトリ全体がgit管理対象になる
→→ローカルにmasterとdevelopがbranchが出来るプッシュする前に確認すること
.gitignoreに入れておかなければいけないファイル、ディレクトリが無いかどうか
ex)
/vender等リモートリポジトリにプッシュする
$ git add .
$ git commit -m "inital commit"
$ git remote add origin https://github.com/hoge/hoge.git
$git push --all
→ローカルにあるdevelopとmasterの両方をリモートにあげるレビューをしやすくするためにrails newの段階でgit flow initをしてmasterとdevelopブランチを自身のパブリックリポジトリに対してプッシュしています!
(PCにgit flow入れてなければ
$ brew install git-flow
)
- 投稿日:2020-02-09T20:37:15+09:00
【Rails+Redis+Docker】RailsアプリにRedis導入&ランキングの実装
Redisの導入
今回使うRedisはブログサイトでよくあるランキングを表示するために使います。
まずRailsとRedisの接続からしないといけないのですが、ここで2日くらい躓きました。色んなサイトを見まくって試したけど全然繋がらない状態が続き地獄でした。
解決方法
基本的にredisはlocalhost:6379に繋ぐのが普通なのですが、開発段階だとRails自体をlocalhostに繋いでるので混同しちゃって上手く繋がらなくなってしまっていたということでした。
なのでredisの設定をlocalhost→redisといった感じに名前を変更して行えばすんなり上手くいった感じです。
出来てしまえば簡単なことだったと思うのですが、やはり初心者には結構辛いところでした。コード打っててエラーならまだしも繋がらなくてエラーは精神的にかなりきます。
以下は変更点と参考サイトです。
ちなみにdocker-compose.ymlはこんな感じです。
docker-compose.ymlversion: '3' services: db: image: mysql:8.0.17 command: mysqld --default-authentication-plugin=mysql_native_password volumes: - ./db/mysql_data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: root ports: - "4306:3306" web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/app_name ports: - "3000:3000" depends_on: - db - redis tty: true stdin_open: true links: - db environment: REDIS_HOST: redis REDIS_PORT: 6379 redis: image: redis:5.0.5 ports: - 6379:6379 volumes: - ./redis:/data command: redis-server --appendonly yesRedisでランキング機能の実装
無事redisは導入出来ましたが、「導入出来てしまえばこっちのもん!」というわけではありません。
ランキングを表示しないといけないのでこれまたredisの基礎とcontroller、viewを見直さないといけません。
これも「Rails redis ランキング」と調べたら結構参考サイトは出てくるのですが、仕組みの理解が乏しいので基礎の見直しが必要でした。
redisの特徴としては以下のような感じです。
- インメモリアルデータベース(すごく早い!)…ランキングなどに向いてる
- 永続化(定期的にディスクに書き出す)
- データ構造サーバー
そんなredisをRailsで使うには、gemでredisを導入してREDISメソッドを利用する必要があります。
gemの導入が完了したらconfig/initialize以下にredis.rbを作成して以下を記入。
config/initialize/redis.rbrequire 'redis' uri = URI.parse(ENV["REDIS"]) REDIS = Redis.new(host: uri.host, port: uri.port)post_controller.rbdef index @posts = Post.all ids = REDIS.zrevrangebyscore "posts/daily/#{Date.today.to_s}", "+inf", 0,limit:[0,3] @ranking_posts = ids.map{ |id| Post.find(id) } end # GET /posts/1 # GET /posts/1.json def show REDIS.zincrby "posts/daily/#{Date.today.to_s}", 1, @post.id end今回はredisのソート済みセットを使ってpv数ランキングを実装していきました。
pv数の表示はviewに直接以下のように書けば表示されます。index.rb<ul> <% @ranking_posts.each do |ranking_post| %> <li> <%= link_to(ranking_post.title,"/posts/#{ranking_post.id}") %> (<%= REDIS.zscore("posts/daily/#{Date.today.to_s}", ranking_post.id).to_i %>PV) </li> <% end %> </ul>アクションに設置する方法がないか考えたのですが、これしか方法がわからなかったです。ちょっと見苦しいですがとりあえずこれでPV数が表示されます。
まとめ
Redisの理解はまだまだ乏しいのですが、触ってると結構楽しかったのでまた利用してみたいと思います。
- 投稿日:2020-02-09T19:00:46+09:00
Rails 6 + RSpec 3.7~でスクリーンショットが真っ白になる問題の要点とその解決法
要点
- RSpec3.7で追加されたSystemSpecはスクショを自動で保存してくれる機能がある
- Rails6ではRSpec4系の使用が推奨されている
- 解決方法は
rails_helper.rb
パッチをあてる- なぜ?を追ってみたけど、わからなかった
- 他の理由で失敗するケースもある模様
System Specのスクリーンショット保存機能
RSpec 3.7から追加された System Specでは、テスト失敗時に自動でスクリーンショットを保存してくれます。
この機能はRailsのシステムテストに組み込まれているので、feature specのようにcapybara-screenshotなどによるサポートを手動で設定する必要がありません。
Rails 5.1以降のシステムテストをRSpecで実行する(翻訳)|TechRachoRails6ではRSpec4の使用が推奨されている
rspec-railsのイシューにもおなじ問題が上がっていました。このスレッドで解決できる
?
とおもったのですが、「rspec-rails4.0で試してみてほしい」「完璧に動いたよ、サンキュー!」でおわっていました。Rails6にはRSpec4という組み合わせが推奨のようですね。
解決方法
以下の記事で、原因と解決するためのパッチが説明されていました。
rails 6.0.0 と rspec-rails 3.8.2 の組み合わせだと Capybara.reset_sessions! を呼んだ後に save_screenshot が呼ばれるので、スクリーンショットの内容が真っ白になります。
Rails アプリケーションの不安定なテストを撲滅したい 〜system spec のデバッグ方法とテストを不安定にさせる要因〜 - あらびき日記rails_helper.rbにパッチをあてる
さきほどの記事にはパッチコードはあったのですが、「どこに書けばいいか」は明記されておらず、迷いました
![]()
rails_helper.rb
に書いてあげると動作することがわかりました。rails_helper.rbmodule SystemHelper extend ActiveSupport::Concern included do |example_group| # Screenshots are not taken correctly # because RSpec::Rails::SystemExampleGroup calls after_teardown before before_teardown example_group.after do take_failed_screenshot end end def take_failed_screenshot return if @is_failed_screenshot_taken super @is_failed_screenshot_taken = true end end RSpec.configure do |config| config.include SystemHelper, type: :system endなぜ?を追ってみたが…
パッチコードをみたときに、「うーん
?」何をしているのか、なぜ直るのかがわかりませんでした。「Capybara.reset_sessions! を呼んだ後に save_screenshot が呼ばれる」というキーワードでしらべてみました。
…
順序はただしいように見えるdef before_teardown take_failed_screenshot ensure super end def after_teardown Capybara.reset_sessions! ensure super endTestHelpers::SetupAndTeardownモジュールにたどりついて、それでも
?で力尽きました(笑)
理由が気になる…。
まとめ
- RSpec4はbetaだし、あげると既存のテストが壊れる可能性もある。
- スクショが保存できればそれでいい。
- RSpec4が来るまで動いてくれたらOK!
- 上記にあてはまるようであれば、今回の対応でよいのではとおもいました。
他の理由で失敗するケースもある
遭遇はしたことないのですが、パスの長さがエラーの問題になるケースもあるようでした。(詳細未調査)
- 投稿日:2020-02-09T16:54:15+09:00
コード書いたことないPdMやPOに捧ぐ、Rails on Dockerハンズオン vol.6 - Model validation -
この記事はなにか?
この記事は私が社内のプログラミング未経験者、ビギナー向けに開催しているRuby on Rails on Dockerハンズオンの内容をまとめたものです。ていうかこの記事を基にそのままハンズオンします。ハンズオンは
1回の内容は喋りながらやると大体40~50分くらいになっています。お昼休みに有志でやっているからです。
現在進行形なので週1ペースで記事投稿していけるように頑張ります。
ビギナーの方のお役にたったり、同じように有志のハンズオンをしようとしている人の参考になれば幸いです。
他のハンズオンへのリンク
・ Vol.1 - Introduction -
・ Vol.2 - Hello, Rails on Docker -
・ Vol.3 - Scaffold, RESTful, MVC -
・ Vol.4 - Static pages -
・ Vol.5 - Model and CRUD -
・ Vol.6 - Model validation -
$
,#
,>
について
$
: ローカルでコマンドを実行するときは、頭に$
をつけています。
#
: コンテナの中でコマンドを実行するときは、頭に#
をつけています。
>
: Rails console内でコマンド(Rubyプログラム)を実行するときは、頭に>
をつけています。
はじめに
第6回目となる今回は、前回作成したUserモデルにバリデーションを付けていきます。
バリデーションとは、日本語では『検証』と訳されますが、モデルの保存条件のようなもので、例えばname
はnil
じゃダメ、とか@
が含まれていないとダメ、とかそういうやつです。Validationをつけてみる
早速Validationを付けてみます。いよいよModelファイルをいじる時がきました。
app/models/user.rbclass User < ApplicationRecord validates :name, presence: true, length: { maximum: 50 } VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } endValidationは
validates [attribute_name], [validations]
の形式で定義することができます。
一つの属性に対して複数のValidationを一気に定義していますね。
どんなValidationが定義されているのか紹介していきます!presence
presence
は『存在性』のValidationです。presence: true
なので『存在しなければならない』ことを検証します。
ユーザー情報としてname
や
なのでname
と動作を確認してみましょう。Rails consoleで確認していくのでまずはコンテナ&Rails consoleの起動から。
$ docker-compose up -d $ docker-compose exec web ash# rails c> user = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> > user.save (0.3ms) BEGIN User Exists? (6.7ms) SELECT 1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT $1 [["LIMIT", 1]] (0.5ms) ROLLBACK => falseおっと、
save
メソッドでfalse
が返却されていますね。前回も少しお話しましたが、save
メソッドはデータのDB保存に失敗する場合(validationで引っかかる場合)、false
を返却するようになっています。エラーの内容は
user.errors
に格納されます。中でもuser.errors.full_messages
を見れば、ユーザー向けのエラーメッセージが格納されているのでエラー理由が一目瞭然です。> user.errors.full_messages => ["Nameを入力してください", "Emailを入力してください", "Emailは不正な値です"]『を入力してください』がPresence validationに違反した場合のエラーメッセージです。
属性の日本語化
...
validationは日本語化されていますが属性は英語になっていますね...
それもそのはず。属性の表現の仕方を定義していないのですから!ということで初期設定の時にi18n化対応したのと同じように、localesファイルを編集して『Name』を『お名前』、『Email』を『メールアドレス』と日本語化してあげましょう。
config/locales/ja.ymlja: activerecord: attributes: user: name: "お名前" email: "メールアドレス" errors: ...属性の名称は
activerecord.attributes.[model_name].[attribute_name]
で定義します。
一度Rails consoleをリロードして、もう一度エラーを起こして確認してみましょう!> reload! Reloading... => true > user = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> > user.save (0.3ms) BEGIN User Exists? (3.0ms) SELECT 1 AS one FROM "users" WHERE "users"."email" IS NULL LIMIT $1 [["LIMIT", 1]] (1.6ms) ROLLBACK => false > user.errors.full_messages => ["お名前を入力してください", "メールアドレスを入力してください", "メールアドレスは不正な値です"]属性も日本語化されたエラーメッセージに変わりました!
length
length
は属性値の長さを検証するvalidationです。
maximum
で最大文字数(最大桁数)、minimum
で最小文字数(最小桁数)、in
で最小と最大の範囲、is
で特定の文字数(桁数)を検証します。
今回は、name
には最大50文字、> user = User.new => #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil> > user.name = "a" * 51 => "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" > user.email = "b" * 245 + "@sample.com" => "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb@sample.com" > user.save (2.8ms) BEGIN User Exists? (11.7ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2 [["email", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb@sample.com"], ["LIMIT", 1]] (0.4ms) ROLLBACK => false > user.errors.full_messages => ["お名前は50文字以内で入力してください", "メールアドレスは255文字以内で入力してください"]文字数について検証してくれていることが確認できました!
format
format
は正規表現とマッチするかを検証するvalidationです。
今回のケースでは、正規表現として/\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
を与えています。これはメールアドレスの正規表現として一般的なもので、簡単に言えば「【何か文字列】@【何か文字列】.【何か文字列】」を表しています。
正規表現の表現方法については今回は端折ります。例えば「Ruby 正規表現の使い方 - Qiita」などを参考に勉強してみてください。常に知っておく必要はあんまりないと思いますが、必要になったときに調べながらでも書けるようになっているのが望ましいでしょう。では、早速このフォーマットバリデーションが正しく動作するかを確認します。例えば、「@」を抜いた「taro.com」なんていかがでしょうか?絶対メールアドレスじゃないのでちゃんとエラーになってほしいですよね。
> user = User.new(name: "taro", email: "taro.com") => #<User id: nil, name: "taro", email: "taro.com", created_at: nil, updated_at: nil> > user.save (0.4ms) BEGIN User Exists? (2.1ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2 [["email", "taro.com"], ["LIMIT", 1]] (0.3ms) ROLLBACK => false > user.errors.full_messages => ["メールアドレスは不正な値です"]メールアドレスのフォーマットエラーになりました。
uniqueness
uniqueness
は一意性を検証するvalidationです。
case_sensitive
をfalse
に設定すると大文字小文字の区別をしないで一意性を検証するようになります。taro@sample.com
」と「TARO@sample.com
」は同じメールアドレスですのでこのオプションを付与してます。では検証してみましょう。
> User.create(name: "taro", email: "taro@sample.com") (0.5ms) BEGIN User Exists? (3.7ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2 [["email", "taro@sample.com"], ["LIMIT", 1]] User Create (2.5ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "taro"], ["email", "taro@sample.com"], ["created_at", "2020-01-30 13:58:08.104803"], ["updated_at", "2020-01-30 13:58:08.104803"]] (2.0ms) COMMIT => #<User id: 2, name: "taro", email: "taro@sample.com", created_at: "2020-01-30 04:58:08", updated_at: "2020-01-30 04:58:08"> > user = User.create(name: "taro", email: "TARO@sample.com") (0.5ms) BEGIN User Exists? (3.0ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2 [["email", "TARO@sample.com"], ["LIMIT", 1]] (1.1ms) ROLLBACK => #<User id: nil, name: "taro", email: "TARO@sample.com", created_at: nil, updated_at: nil> > user.errors.full_messages => ["メールアドレスはすでに存在します"]emailの一意性チェックでエラーになったことがわかります。また、大文字小文字を区別せずに一意性チェックをしてくれていることもわかりました。
DBの制約
ここまでモデル側、つまりアプリ側にvalidationをかけてきました。
このような制約はDB側でもかけることができますし、その方が安全だ!という考え方もあります。Railsでは、デザインパターンとしてActiveRecordを採用しています。ActiveRecordでは「制約をかけるのはモデルの仕事」とされているため、上のようにモデル側で制約をかけています。
こうすることで、制約の内容が変わったとしてもDB側の設定を変えることなくアプリ側だけの改修で柔軟に対応をすることができます。一方で一意性に関しては、複数のアプリが並列で処理を行う構成を考えるとDB側で制御されている方がよいとされています。
今回は一意性(uniqueness)に関して、DB側にも制約をかけます。
まず、マイグレーションファイルを生成します。
# rails g migration add_index_to_user Running via Spring preloader in process 194 invoke active_record create db/migrate/YYYYMMDDhhmmss_add_index_to_user.rbこれでほとんど空のマイグレーションファイルが生成されますので、中身を書いていきます。
db/migrate/YYYYMMDDhhmmss_add_index_to_user.rbclass AddIndexToUser < ActiveRecord::Migration[6.0] def change add_index :users, :email, unique: true end end
uniqueness
の制約はadd_index [table (model)], [column (attribute)], unique: true
で設定します。
ではマイグレーションファイルを適用していきましょー。# rails db:migrate == 20200130053807 AddIndexToUser: migrating =================================== -- add_index(:users, :email, {:unique=>true}) -> 0.0942s == 20200130053807 AddIndexToUser: migrated (0.0947s) ==========================DBの一意性チェックは利用するDBによりますが大文字小文字を区別してしまう可能性があります。そのため、モデル側でDBに保存する前に強制的にemailを小文字化する処理を入れます。
app/models/user.rbclass User < ApplicationRecord before_save { self.email = email.downcase } ... end
user.save
をするときに、RailsではCallbacksといいますがシーケンシャルな処理が存在します。まずvalidation
が実行されsave
が実行されcommit
が実行されるといった流れです。
before_save
はその名前から分かるとおり、validation
が通った後、save
が始まる前に処理を挟み込むことを意味しています。before_save
の後の{}の中身が処理になりますが、email.downscale
で現在のself.email
、つまり自分の属性値に代入しています。さて、ここまでやるとDBでも一意制約を入れることができている状態になります。
一度モデル側のuniqueness validationを外しておき、DBだけで一意制約を担保できるか確認してみましょう。
app/models/user.rbclass User < ApplicationRecord ... validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }#, # uniqueness: { case_sensitive: false } end> reload! Reloading... => true > user = User.create(name: "taro", email: "TARO@sample.com") (0.5ms) BEGIN User Create (13.3ms) INSERT INTO "users" ("name", "email", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["name", "taro"], ["email", "taro@sample.com"], ["created_at", "2020-01-30 16:12:37.414147"], ["updated_at", "2020-01-30 16:12:37.414147"]] (0.6ms) ROLLBACK Traceback (most recent call last): 1: from (irb):40 ActiveRecord::RecordNotUnique (PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_users_on_email") DETAIL: Key (email)=(taro@sample.com) already exists. > user.errors.full_messages => []今回もROLLBACKが走っているので失敗していることがわかります。
違う点としては、ROLLBACKの理由がモデルのvalidationではなくActiveRecord::RecordNotUnique
例外であるということがコンソールから読み取れると思います。モデルでのエラーではないので、user.errors.full_messages
には何も格納されていません。さて、DB側の制約でも一意性を担保できることを確認できたので、モデルのコメントアウトを元に戻しておきます。
app/models/user.rbclass User < ApplicationRecord ... validates :email, presence: true, length: { maximum: 255 }, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } end後片付け
じゃあ、またデータ消します。
$ docker-compose down $ docker-compose run --rm web rails db:migrate:resetまとめ
今回は、Modelにvalidationを付与してみました。
ふんふん。ちゃんとvalidationをつけれましたね。
validationは他にもいろいろあります。
Active Record バリデーション - Railsガイド
自分のアプリケーションに合致するバリデーションを見つけましょう!次回は、Userモデルにセキュアなパスワードを付与して行こうと思います。
パスワードを平文で持つのはやっぱりNG。Railsは簡単にセキュアなパスワードを実装できるんです!では、次回も乞うご期待!ここまでお読みいただきありがとうございました!
Reference
P.S. 間違っているところ、抜けているところ、説明の仕方を変えるとよりわかりやすくなるところなどありましたら、優しくアドバイスいただけると助かります。
- 投稿日:2020-02-09T16:49:18+09:00
$ is not defined rails
gemfilegem 'jquery-rails' gemを追加して読み込んでおくこと。 app>assets>javascripts>application.jsにて //= require jquery ←これを追加 //= require jquery_ujs ←これを追加 //= require_tree . ←必ずこれより先に記載。
- 投稿日:2020-02-09T16:25:14+09:00
【rails tutorial 13.4.4】「ArgumentError: Missing required arguments: aws_access_key_id, aws_secret_access_key」エラーの対処法
経緯
railsチュートリアル「13.4.4 本番環境での画像アップロード」において、
AWSの「Simple Storage Service (S3)」との接続がうまくできませんでした。本来スキップしても良い項目でしたが、
カリキュラム通り進めて
IAMの設定やS3との接続もできた(と思っていた)のですが
$ git push heroku
をすると、
「ArgumentError: Missing required arguments: aws_access_key_id, aws_secret_access_key」
及び
「remote:! Precompiling assets failed.」
というエラーが出てしまいましたので、その原因と対処法を記載します。結論
heroku config:set
がされていなかったAWSのS3を使う際には、登録したデータの紐付けが必要になります。
その際のコマンドに""(ダブルクォーテーション)を不必要に付けていたことが原因のようでした。$ heroku config:set S3_ACCESS_KEY="ココに先ほどメモしたAccessキーを入力" $ heroku config:set S3_SECRET_KEY="同様に、Secretキーを入力" $ heroku config:set S3_BUCKET="Bucketの名前を入力" $ heroku config:set S3_REGION="Regionの名前を入力"↑こちらはチュートリアルから引用してきたコマンドです。
""は含まずに代入してください。代入できたか確認するコマンドが以下になります
$ heroku config:get S3_ACCESS_KEY $ heroku config:get S3_SECRET_KEY $ heroku config:get S3_BUCKET $ heroku config:get S3_REGIONsetがgetになっただけです。
入力値が不安であれば確認の時に使ってください。これで問題なく
$ git push heroku
はできるようになりました。別件の問題が
git push herokuをやりすぎてlimitいっぱいになってしまいました。
herokuはgit push回数に制限があるんですね。知りませんでした。そこで、別のherokuをcreateしました。
git push herokuは無事できるようになりました。
しかし、
今度はアプリのsigninができなくなりました。signinできない原因
認証に使っている
edit_account_activation_url
というリンクに入っている名前の中に古い方のheroku.app名が入っていました。対策
production環境のメール設定のhostを新しい方のherokuへ切り替える
config/environments/production.rbhost = 'https://自分のherokuアプリ名.herokuapp.com/'これで本番環境でのsigninができました。
ちょっと脱線してしまい、すみませんでした。まとめ
・$ heroku config:setコマンドには""は必要ない
・$ heroku config:getで反映できているか確認できる
・production.rbにherokuのhost情報を入れている参考
・https://qiita.com/fuku_tech/items/b21e49ff49cb19f04838
・https://310nae.com/es6-compile/
・https://pg-happy.jp/rails-aws-s3-upload.html
・https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39
- 投稿日:2020-02-09T15:31:11+09:00
【Ruby on Rails チュートリアル】一度作成したherokuアプリを作り直すときの復旧手順
はじめに
Ruby on Rails チュートリアル を進めている最中、何らかの理由でherokuアプリを再作成したくなったとき(謎のエラーに苦しんだ時とか)の手順をまとめました
以下、筆者はリモートリポジトリとしてBitbucketではなくGithubを使用していますが、それ以外の開発環境はRails Tutorialに準拠しています。
既存のherokuアプリ名を確認
リモート先に設定しているURLから、アプリ名を確認します。
Console-input$ git remote -vConsole-resultheroku ssh://git@heroku.com/<アプリ名>.git (fetch) heroku ssh://git@heroku.com/<アプリ名>.git (push) origin git@github.com:<ユーザー名>/<リポジトリ名>.git (fetch) origin git@github.com:<ユーザー名>/<リポジトリ名>.git (push)リモートリポジトリとしてBitbucketを使用している人は、ここの表記が少し異なります。また、リモートの設定次第では、各URLの先頭が「https:~」で始まる人もいると思います。
いずれにせよ、heroku のリモート先に設定されているURL(上2行)のうち、「.git」の直前の文字列が<アプリ名>になりますので、これをコピーして控えておきましょう
既存のherokuアプリを削除
herokuの無料枠では、5つまでアプリをデプロイすることができます。
しかし、デプロイ枠が余っている場合でも、デプロイ先が複数存在してしまうことは初学者にとって思わぬ形での混乱を招きかねません。
そのため、既存のアプリは一旦消してしまうことをオススメします(余りの無料枠で新規作成する場合は、本章はスキップして頂いて構いません)
Console-input$ heroku apps:destroy --app <アプリ名>Console-result▸ WARNING: This will delete ⬢ <アプリ名> including all add-ons. ▸ To proceed, type <アプリ名> or re-run this command with --confirm <アプリ名><アプリ名>の箇所を、先ほどコピーしたアプリ名をに書き換えてください。
その後、上記のように「本当に消して良いですか?本当に消すなら、アプリ名をもう一回入力してね」という旨のメッセージがきますので、Consoleに<アプリ名>を再入力します
Console-input> <アプリ名>これで、一度デプロイしたherokuアプリを消せました。
新規herokuアプリの作成
Console-input$ heroku createここでアプリ名を指定することもできますが、デフォルトだとheroku側で空いているアプリ名を自動で生成してくれます。
console画面から、「https://git.heroku.com/<新規アプリ名>.git」をと書かれている箇所を見つけ、コピーして控えておきましょう
リモート先の再設定
旧アプリ自体は既に削除済みですが、gitのリモート先が旧アプリのURLのままなので、新アプリのURLに変更します。
Console-input$ git remote remove heroku $ git remote set-url heroku https://git.heroku.com/<新規アプリ名>.gitこれで、リモート先の再設定ができました。念のため確認しておきましょう
Console-input$ git remote -vConsole-outputheroku https://git.heroku.com/<新規アプリ名>.git (fetch) heroku https://git.heroku.com/<新規アプリ名>.git (push) origin git@github.com:<ユーザー名>/<リポジトリ名>.git (fetch) origin git@github.com:<ユーザー名>/<リポジトリ名>.git (push)herokuのリモート先のURLが、新規アプリ名のものになっていれば、OKです。
※任意ですが、リモートリポジトリにgithubを選択肢しており、herokuへのアクセスをHTTPS経由ではなくSSH経由にしたい場合は、リモート先の設定を下記のように描いてください
Console-input$ git remote set-url heroku ssh://git@heroku.com/<新規アプリ名>.git確認すると、
Console-input$ git remote -vConsole-outputheroku https://git.heroku.com/<新規アプリ名>.git (fetch) heroku https://git.heroku.com/<新規アプリ名>.git (push) origin git@github.com:<ユーザー名>/<リポジトリ名>.git (fetch) origin git@github.com:<ユーザー名>/<リポジトリ名>.git (push)これでSSH経由のURLになりました
本番環境でのメール設定
「第11章 アカウントの有効化」で「11.4 本番環境でのメール送信 」まで進んでいる人は、SendGridの再設定が必要です(第11章まで進んでいない人はスキップ)
まずは、SendGridのアドオンを再インストールします。
Console-input$ heroku addons:create sendgrid:starterその後、本番環境の設定ファイル内の「host = ~」の箇所が旧アプリのホストのままになっているので、修正します。
config/environments/production.rbRails.application.configure do . . . config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp host = '<新規アプリ名>.herokuapp.com' config.action_mailer.default_url_options = { host: host } ActionMailer::Base.smtp_settings = { :address => 'smtp.sendgrid.net', :port => '587', :authentication => :plain, :user_name => ENV['SENDGRID_USERNAME'], :password => ENV['SENDGRID_PASSWORD'], :domain => 'heroku.com', :enable_starttls_auto => true } . . . end最後に、heroku config内部における、SendGridのユーザー名とパスワードを更新して完了です。
Console-input$ heroku config:get SENDGRID_USERNAME $ heroku config:get SENDGRID_PASSWORD本番環境での画像アップロード
「第13章 ユーザーのマイクロポスト」内の、「13.4.4 本番環境での画像アップロード
」まで進めていた人は、heroku configにおけるS3に関する情報の再設定が必要です(第13章まで進んでいない人はスキップ)Console-input$ heroku config:set S3_ACCESS_KEY="Accessキーを入力" $ heroku config:set S3_SECRET_KEY="Secretキーを入力" $ heroku config:set S3_BUCKET="Bucketの名前を入力" $ heroku config:set S3_REGION="Regionの名前を入力"各パラメータ値を忘れてしまった場合は、【Railsチュートリアル】S3に画像をアップロードする設定【13章課題】を参考に、AWSコンソールより取得してください。
デプロイ
最後、新規URLへデプロイをして完了です。
Console-input$ rails test $ git add -A $ git commit -m "Recreate heroku app"まだ第11章まで進んでいなかった人は、今回の一連の作業の中でファイルを一切編集していないはずです。
そのため下記のように「ファイルの変更がないため、commitしません」と言う表示が出るかもしれません。
Console-resultOn branch master Your branch is up-to-date with 'origin/master'. nothing to commit, working tree cleanこの場合は、そのまま下へ進んでください。
Console-input$ git push $ git push heroku「第6章 ユーザーのモデルを作成する」まで進めている人は、heroku上でのmigrationも走らせておきましょう
Console-input$ heroku run rails db:migrate加えて、「第10章 ユーザーの更新・表示・削除」まで進めている人は、忘れずにseedファイルを本番環境に流し込んでおきましょう
Console-input$ heroku run rails db:seed $ heroku restartおわりに
以上で、完了です!予期せぬエラーが発生して抜け出せなくなった時は、アプリごと作り直すことでエラーが解消されるケースもあります。よければ参考にしてください。
- 投稿日:2020-02-09T15:11:35+09:00
RailsAdminをインストールして簡単に管理画面を作成する方法
Ruby on Rails には管理画面をテンプレート的に作成できるgemがいくつかあります。
今回は、カスタマイズ性はそこまで高くはないもののセットアップが簡単な「RailsAdmin」を使って管理画面を作成します。管理画面を作成する
最初にgemをインストールします。
Gemfileに記述し、bundel installを実行します。gem 'rails_admin', '~> 2.0.0.beta'$ bundle install $ rails g rails_admin:install管理画面へアクセス
まずはサーバを起動させます。
$ rails sブラウザから、railsの基本画面に "/admin" を後付けしてアクセスします。
初期設定であれば "http://localhost:3000/admin" をアドレスバーに入力します。インストールに成功していれば、次のような画面が表示されます。
(初回は少し時間がかかります)すでに作成したテーブルを作成している場合にはそれが表示されます。
私の環境の場合、Questions というテーブルを作成しているので表示されていますね。テーブル名を選択すると、編集画面に飛べます。
"+Add new" からレコードを追加したり、外部へ出力したりできます。BASIC認証をするよう制限をかける
デフォルトだとアドレスバーから誰でも管理画面にアクセスできてしまうので、セキュリティ的に問題があります。
BASIC認証(ユーザー名、パスワード認証)をかけましょう。config/initializers/rails_admin.rb# 一番上に追加 config.authenticate_with do authenticate_or_request_with_http_basic('Site Message') do |username, password| username == 'admin' && password == 'admin' end endサーバを起動しなおして、"http://localhost:3000/admin" にアクセスすると認証ダイアログが表示されます。
上記ダイアログが表示されれば成功です。
- 投稿日:2020-02-09T15:01:28+09:00
Railsが動いてくれない。
rails newはなんとか動く。モデル作成その他が動かない。なんでや!
で、rails newした時によくよく見てみるとこんなんがある。
- bin/rake: Spring inserted
- bin/rails: Spring inserted
なんぞこれ。
調べてみると以前サーバーが起動しなかった時と同じ感じらしい。
$ ps ax | grep spring 11588 ? Sl 0:00 spring server | anilova | started 1 hour ago 12708 pts/1 S+ 0:00 grep --color=auto spring11588が何故か動いてるのでこいつを消す。
一旦以下のコードで確認。$spring status Spring is running: 11588 spring server | anilova | started 1 hour ago 12283 [ruby] <defunct>$spring status Spring stopped.これで11588は止まったはず。
もう一度確認。$ spring status Spring is not running.$ ps -ef | grep spring vagrant 12728 12631 0 05:53 pts/1 00:00:00 grep --color=auto springこれで正常に戻った!
- 投稿日:2020-02-09T14:21:09+09:00
Railsで新しくテーブルを作成するときの基本的な流れ
Railsで新しくモデルを作成するときの備忘メモ
マイグレーションファイルを作成
$ rails g model Testマイグレーションファイルの編集
マイグレーションファイルを「rails g model Test」で作成すると、次のようなファイルが作成される。
yyyymmddHHMMSS_create_tests.rbclass CreateQuestions < ActiveRecord::Migration[5.2] def change create_table :tests do |t| t.timestamps end end end基本としては、create_table :tests do |t| から end の間に追加したいカラムの情報をつけていく。
ここでは、string型のtitleカラムと、inter型の No というカラムを追加させる。
なお、255文字以下の文字列ならば string 型、それ以上であれば text 型を使います。yyyymmddHHMMSS_create_tests.rbclass CreateQuestions < ActiveRecord::Migration[5.2] def change create_table :tests do |t| t.string :title t.integer :No t.timestamps end end endこれであとは「rails db:migrate」:を実行すれば、titleカラムとNoカラムとtimestampsカラムを持つ「Test」というテーブル(表のようなもの)が作成される。
マイグレーションファイルをデータベースに反映
$ rails db:migrateモデルに制約をつけていく
通常、作成したモデルに入れる情報(ここではテーブルに入るデータのようなイメージ)には、好き勝手な情報を入れられないように制約(validates)をつけてあげます。
ちなみにモデルファイルを編集したあとは、特にコマンドを実行する必要はありません。次の例は、空のタイトルが入力されないように Test モデルに制約(バリデーションといいます)をつける例です。
¥app¥models¥test.rbclass Question < ApplicationRecord validates :title, presence: true endなお、複数のバリデーションをもたせることもできます。
例えばタイトルの長さを100文字以下にしてほしい場合は次のように記述します。¥app¥models¥test.rbclass Question < ApplicationRecord validates :title, presence: true, length: { maximum: 100 } end以下ではよく使うバリデーションを整理
空の値ではないこと
validates :title, presence: true一意性(かぶらないようにしたいとき)
validates :title, uniqueness: true長さ
validates :title, length: { minimum: 10 } # 「10文字以上」 validates :title, length: { maximum: 100 } # 「100文字以下」 validates :title, length: { in: 10..100 } # 「10文字以上100文字以下」 validates :title, length: { is: 8 } # 「8文字のみ」数値であること
例えばinteger型で書いてほしいとき
validates :No, numericality: trueメールアドレス形式
正規表現を駆使していて少し難しいと思うので、コピペが楽
VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i validates :email, presence: true, uniqueness: true, format: { with: VALID_EMAIL_REGEX }これで「@が入っているか」「一意性があるか」「空ではないか」といった、メールアドレスのデータとして必要な要件を包含できているので便利です。
- 投稿日:2020-02-09T14:14:02+09:00
refileで投稿した画像が表示されない
初投稿です。至らない点が多いとは思いますがよろしくお願いします!
refileで実装した画像投稿機能。投稿はできるものの投稿した画像の表示がどうもおかしい。
投稿画像はどこへやら、表示されるのは穏やかな山と雲のアイコン。困ったのでその時の解決方法をまとめます。結論から言うと自分の計画性のなさからマイグレーションファイルにimage_idカラムを2つ記述していました。
その結果、image_idカラムのデータ型がstringではなくinteger型になっていたのが原因でした。現状の問題点...refileで画像投稿後、表示されない
新規投稿、一覧表示はどちらも機能しているが、画像の表示だけどうもおかしい。
原因の特定に色々やってみる
そもそも投稿した時に画像がきちんと保存できているのか?
byebug
でデバックして投稿した画像がどうなっているのかを確認してみる。posts_controller.rbdef create @post = Post.new(post_params) @post.user_id = current_user.id if @post.save byebug redirect_to posts_path else redirect_to new_post_path end endvagrant環境で動かし新規投稿、その後ターミナルで確認↓
image_id:
に一応値は入ってるみたい。
.....あれ、image_id: 599?image_idってstring型じゃなかったっけ。画像のurlが入るはずなんだけどな。
とりあえず、schemaでpostsのカラムを確認してみる。schema.rbcreate_table "posts", force: :cascade do |t| t.text "comment" t.integer "image_id" t.integer "rating" t.integer "user_id" t.integer "shop_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false endinteger形になってるこれはおかしい。次にpostsのマイグレーションファイルを確認してみる。
..._create_posts.rbclass CreatePosts < ActiveRecord::Migration[5.2] def change create_table :posts do |t| t.text :comment t.string :image_id t.integer :rating t.integer :user_id t.integer :shop_id t.integer :image_id t.timestamps end end end
:image_id
が2つ!?
どうやら設計の段階で複数画像を投稿できるようにpost_imagesテーブルで保存しようとしていたのを途中でpostテーブルで保存しようとカラムを追加したのでimage_idが2つという現象になってた。これが原因で画像の保存が上手くいってなかった。
2つある場合はエラーとかではなく、最後の記述が反映されるんですね。
というかめんどくさがって、addやchangeを使わず直接createファイルに記述を追加してrails db:migrate:reset
で作り直すというやり方自体に問題ありなんでしょうね。カラムの記述被りを解消
原因がわかったので後はマイグレーションファイルの操作で
t.integer :image_id
を消して記述の被りを解消。:image_id
のデータ型をstring型に変更。被りを解消してpostsのカラムをstring型に変更したらちゃんと表示されました。
計画性のない予定変更ダメ絶対。
- 投稿日:2020-02-09T13:50:02+09:00
Railsの環境構築時にLibrary not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError)で苦しんだ
Railsの環境構築時に表題のエラーで苦しんだ
おなじみのopensslのエラー。どうせすぐ解決できるやろって思ってたら意外とハマったので備忘録として残しておきます。
bundleを叩くとコケる
諸々の環境構築終了後によしローカルサーバー立ち上げるぞ!と意気揚々と
$ bundle exec rails sを叩くと
Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError) Referenced from: /usr/local/opt/mysql@5.6/lib/libmysqlclient.18.dylib Reason: image not found - /Users/~~/~~/~~/vendor/bundle/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/mysql2.bundleあれ、opensslで怒られてるぞ…
https://qiita.com/YoshiyukiKato/items/e4f67c588d2943c1253d
この方の記事の通り、$ brew install openssl $ brew link openssl --force $ rbenv uninstall 2.5.5 $ rbenv install 2.5.5とかでいけるやろ… いけー!!
Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError) Referenced from: /usr/local/opt/mysql@5.6/lib/libmysqlclient.18.dylib Reason: image not found - /Users/~~/~~/~~/vendor/bundle/ruby/2.5.0/gems/mysql2-0.4.10/lib/mysql2/mysql2.bundleえ…同じじゃん…
というわけで少しハマってしまいました…
解決法
https://qiita.com/utsu_jimmy/items/a35437faea1c0d2f357d
この方の記事も参考にしながら、rbenvでrubyごと再インストールしたり、brew upgrade試したり一通りはやったのですが、エラー内容は変わらず…もしかしてopensslのversionがおかしい?と思って、
$ brew switch openssl ~~みたいにopensslのversionを切り替えようとすると
Error: openssl does not have a version ~~ in the Cellar. openssl's installed versions: 1.0.2sあ、~~なんてversionなかった。。。じゃあ1.0.2s指定したったらどうなるんやろ
$ brew switch openssl 1.0.2sを実行すると、、
Cleaning /usr/local/Cellar/openssl/1.0.2s Opt link created for /usr/local/Cellar/openssl/1.0.2sん?もしかしてうまくいった?
じゃあこれで$ bundle exec rails s実行すると
=> Booting WEBrick => Rails 5.1.7 application starting in development on http://localhost:3000 => Run `rails server -h` for more startup optionsふつうに立ち上がったあああ!
結局opensslのversionが問題だったようです…
記事にするとしょうもないけど、ハマるときはハマりますよね…
この記事が誰かの役に立てば幸いです
- 投稿日:2020-02-09T13:17:11+09:00
(メモ)【Rails勉強会 第7回】アソシエーンション Part1(2020/2/9)
※こちらの記事は,「人生逆転サロン共同開発参加者限定の勉強会」で使用したコードのメモ書きです。
人生逆転サロンの共同開発について知りたい方はこちらをご覧下さい。
http://yanbaru-spike.com/l/c/yE5eqgMh/NuSs3Jlb0. 内容
【開催日】 2/9(日) 19:30〜20:30
「メッセージ投稿アプリ」を作成する場合,「どのユーザーのメッセージであるか」を判別できる形でデータベースに保存しなければなりません。
Railsアプリを開発する上で,このような関連付け(アソシエーション)は避けて通れないのですが,最初はなかなか分かりづらい内容です。
そのアソシエーションを基礎から順番に解説していきます。
1. 基礎の復習
1.1 アプリの作成
- アプリの作成
ターミナルrails new association_tutorial_appGemfilegroup :development do gem 'web-console', '>= 3.3.0' gem 'listen', '>= 3.0.5', '< 3.2' gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' # ********** 以下を追加 ********** # デバッグ用 gem 'pry-byebug' # ********** 以上を追加 ********** end
rails s
でサーバーを起動し,http://localhost:3000
にアクセスしたときに,Yay! You’re on Rails!
が表示されることを確認して下さい。1.2 ユーザー一覧の表示
ターミナルrails g model User name:string age:integer rails db:migrate
- 初期データ その1
seeds.rbUser.create!(name: '大地', age: 53) User.create!(name: '前野', age: 30) User.create!(name: '原田', age: 54)ターミナルrails db:seed
- コンソールの起動
ターミナルrails c
control + D
でコンソールから抜けられる(1文字でも入力していたら使えない)usersテーブル
id name age created_at updated_at 1 大地 53 2 前野 30 3 原田 54
- ユーザー一覧を表示しよう
ターミナルrails g controller users index
app/controllers/users_controller.rbclass UsersController < ApplicationController def index @users = User.all end endapp/views/users/index.html.erb<% @users.each do |user| %> 【名前】 <%= user.name %> 【年齢】 <%= user.age %> <hr> <% end %>
http://localhost:3000/users/index
にアクセス2. アソシエーション(1:多)
どのユーザーが投稿したメッセージであるかを判断するためには通常「ログイン機能」を利用しますが,複数の内容をまとめて話すと話がややこしくなりますので,今回はアソシエーションのみに焦点を当てることにします。
2.1 データベースの正規化
投稿したユーザーが分かる形でメッセージを保存する場合,どのようにデータベースを設計すればよいでしょうか。
- 悪い例1
usersテーブル
id name age messages 1 大地 53 ["おはよう", "こんばんは", "おやすみ"] 2 前野 30 ["こんにちは"] 3 原田 54 ["テスト"]
- 悪い例2
usersテーブル
id name age message 1 大地 53 おはよう 2 前野 30 こんにちは 3 大地 53 こんばんは 4 原田 54 テスト 5 大地 53 おやすみ
- 正しい例
usersテーブル
id name age 1 大地 53 2 前野 30 3 原田 54 messagesテーブル
id user_id message 1 1 おはよう 2 2 こんにちは 3 1 こんばんは 4 3 テスト 5 1 おやすみ ターミナルrails g model Message content:string user:references rails db:migrate
- 初期データ その2
seeds.rb# 元の内容は全て削除 Message.create!(user_id: 1, content: 'おはよう') Message.create!(user_id: 2, content: 'こんにちは') Message.create!(user_id: 1, content: 'こんばんは') Message.create!(user_id: 3, content: 'テスト') Message.create!(user_id: 1, content: 'おやすみ')2.2 メッセージの一覧表示,新規投稿機能
ターミナルrails g controller messages index new createconfig/routes.rbRails.application.routes.draw do root to: "messages#index" resources :messages get 'users/index' endapp/controllers/messages_controller.rbclass MessagesController < ApplicationController def index @messages = Message.all end def new @message = Message.new @users = User.all end def create Message.create!(message_params) redirect_to root_path end private def message_params params.require(:message).permit(:user_id, :content) end endapp/views/messages/index.html.erb<%= link_to '新規投稿', new_message_path %> <hr> <% @messages.each do |message| %> 【投稿者名】 <%= message.user.name %> 【内容】 <%= message.content %> <hr> <% end %>app/views/messages/new.html.erb<%= form_with model: @message, local: true do |form| %> 【ユーザー】 <%= form.collection_select :user_id, @users, :id, :name %> 【内容】 <%= form.text_field :content %> <%= form.submit '投稿' %> <% end %>【問題】 現状では「一覧表示ページ」でとある問題が起こります。それは何でしょうか?
- 投稿日:2020-02-09T13:11:02+09:00
Railsアプリをherokuにデプロイするまでの大まかな流れとハマったところ
Railsで簡単なセリフ検索アプリを作りました。
昔から『おおきく振りかぶって』という漫画が大好きで考察もしていたのでその効率化のためにも作ったアプリです。
https://oofurisearch.herokuapp.com/初めての公開なのでherokuを使ったのですが、結構時間がかかってしまったのでデプロイまでの流れと初心者がハマりやすいところをメモしときたいと思います。
前提条件
- railsアプリがすでにできている
- herokuの登録は完了している
- herokuコマンドが使える
環境
- Mac
- docker
- MySQL
herokuにファイルをアップロードする
herokuにアプリをアップロードするためにはgitを使います。
まずはターミナルでherokuにログイン。
heroku loginEnterを押すと勝手にブラウザが開くのでloginボタンを押します。
ログインできたら次はアプリを作ります。
heroku create アプリ名アプリ名を入力しなかったら勝手に名前がつきます。
また、同じ名前のアプリ名があるとエラーが出ます。アプリを作ったらgitでファイルをアップしましょう。
git init git add . git commit -am "first commit" git push heroku masterこれでファイルのアップロードはおkです。
DB周辺の設定
DB設定が結構ハマるポイントだと思いますので丁寧に進めていくことをおすすめします。
MySQLを利用したいので、まずはherokuでクレジットカード情報を登録しときます。お金はかかりません。登録ができたら以下を実行。
$ heroku addons:create cleardb:igniteこれでMySQLの設定はできましたが、railsはmysql2を利用していると思うので(gemファイルで確認できます)mysql2にも繋ぎます。
heroku config CREATE_DATABASE_URL: mysql://~~~~~~~「heroku config」で出たURL(上みたいなやつ)をmysql2に設定します。
heroku config:add DATABASE_URL=mysql2://~~~~~~~~ heroku config:set DATABASE_URL='mysql2://~~~~~~~~'これでmysql2にも設定できました。
最後に、
heroku run rake db:migrateしたらおk。
これであとは「OPENAPP」押したら(コマンドならheroku open)アプリは立ち上がります。ここまでは特に問題ないのですが、めんどくさいのが初期データを投入する場合です。
初期データを投入するときは、まずアプリに以下のファイルを作り記述を行います。
lib/tasks/seed.rbDir.glob(File.join(Rails.root, 'db', 'seeds', '*.rb')).each do |file| desc "Load the seed data from db/seeds/#{File.basename(file)}." task "db:seed:#{File.basename(file).gsub(/\..+$/, '')}" => :environment do load(file) end enddb/seeds.rbUser.create!([ {name: "test", email: "sample@email.com",password:"testtesttest"} ])初期データを記入したらherokuにpushしときます。そのあとに以下のコマンドを打つと初期データが投入されます。
heroku run rake db:seedただここで気をつけないといけないのは、例えばログイン機能などをControllerを経由して入るように設定していると入れません。
どうやら初期データはControllerを経由しないらしく、ただデータベースに放り込まれるだけらしいのでそれでログインはできません。なので、ユーザー情報とかはアプリで新規登録するのがいいと思います。また、複数の同じモデルのデータを投入したい場合はvalidatesをかけてると一番上のデータ以降読み込んでくれなくなってしまうので外しときましょう。
ちなみに、複数の別のモデルを投入したい場合は以下のようにファイルを分けたらいいと思います。
db/seeds/users.rbUser.create!([ {name: "test", email: "sample@email.com",password:"testtesttest"} ])db/seeds/items.rbItem.create!([ {item:"*********"} ])db/seeds.rbrequire './db/seeds/users.rb' require './db/seeds/items.rb'これで複数モデルを読み込むことができます。
まとめ
これでherokuへのデプロイができます。私みたいな初心者が躓きやすそうなところなので参考になれば嬉しいです。
- 投稿日:2020-02-09T11:23:05+09:00
CircleCI 2.1 設定サンプル(Rails + Rspec + Rubocop + Jest + ESLint + Yarn + Postgres)
環境
- Circle CI 2.1
- Rails 5 + PostgresSQL 9.6 + Yarn
- 主にCircle CIの設定のため、上記環境には大きく依存していません。
やっていること
- テストの実行(
rspec
、jest
)- Linterの実行(
rubocop
、eslint
)
RAILS_ENV: test
で実行しているため、Gemfile
内でrubocop
がgroup :development, :test
に定義されている必要があります。- デプロイ
develop
やmaster
ブランチへマージされた時に、capistrano
を使って各staging、production環境にデプロイ。- 結果を
slack
に通知。capistrano
の設定ファイル(config/deploy/staging.rb
等)で、set :branch, ENV['BRANCH'] || 'develop'
等として、ブランチの設定がされている必要があります。- 上記に付随する処理(ソースコード、ライブラリのインストール及びキャッシング)
出来ていないこと
fingerprint
を環境変数で設定すべき設定例
.circleci/config.ymlversion: 2.1 orbs: slack: circleci/slack@3.3.0 executors: default: working_directory: ~/repo docker: - image: circleci/ruby:2.6.3-node environment: BUNDLE_PATH: vendor/bundle RAILS_ENV: test extended: working_directory: ~/repo docker: - image: circleci/ruby:2.6.3-node environment: BUNDLE_PATH: vendor/bundle RAILS_ENV: test PGHOST: 127.0.0.1 PGUSER: root - image: circleci/postgres:9.6.2-alpine environment: POSTGRES_USER: root POSTGRES_DB: circle-test commands: restore_source_code: steps: - restore_cache: name: Restore source code cache keys: - v1-source-code-{{ .Environment.CIRCLE_SHA1 }} - v1-source-code- save_source_code: steps: - save_cache: key: v1-source-code-{{ .Environment.CIRCLE_SHA1 }} paths: - ~/repo restore_node_dependencies: steps: - restore_cache: name: Restore node dependencies cache keys: - v1-node-dependencies-{{ checksum "yarn.lock" }} - v1-node-dependencies- install_node_dependencies: steps: - run: name: Install node dependencies command: yarn install --frozen-lockfile save_node_dependencies: steps: - save_cache: name: Save node dependencies cache key: v1-node-dependencies-{{ checksum "yarn.lock" }} paths: - ~/node_modules restore_bundle_dependencies: steps: - restore_cache: name: Restore bundle dependencies cache keys: - v1-bundle-dependencies-{{ checksum "Gemfile.lock" }} - v1-bundle-dependencies- install_bundler: steps: - run: name: Install bundler # Without the following two lines, `echo ... source $BASH_ENV`, you see the following error. # Could not find rake-12.3.2 in any of the sources # see: https://discuss.circleci.com/t/using-bundler-2-0-during-ci-fails/27411 command: | echo 'export BUNDLER_VERSION=$(cat Gemfile.lock | tail -1 | tr -d " ")' >> $BASH_ENV source $BASH_ENV gem install bundler install_bundle_dependencies: steps: - run: name: Install bundle dependencies command: bundle install --jobs=4 --retry=3 --path vendor/bundle save_bundle_dependencies: steps: - save_cache: key: v1-bundle-dependencies-{{ checksum "Gemfile.lock" }} paths: - ~/vendor/bundle wait_for_db_start_up: steps: - run: name: Wait for db start up command: dockerize -wait tcp://127.0.0.1:5432 -timeout 1m set_up_database: steps: - run: name: Set up database command: | bin/rails db:create --trace bin/rails db:migrate --trace run_rubocop: steps: - run: name: Run rubocop command: bundle exec rubocop run_rspec: steps: - run: name: Run rspec command: bundle exec rspec --format progress --format RspecJunitFormatter -o test_results/rspec.xml run_eslint: steps: - run: name: Run eslint command: yarn lint --format junit -o test_results/eslint.xml run_jest: steps: - run: name: Run jest command: yarn test --maxWorkers=2 --testResultsProcessor="jest-junit" environment: JEST_JUNIT_OUTPUT: ~/test_results/jest.xml jobs: checkout_code: executor: default steps: - checkout - save_source_code node_dependencies: executor: default steps: - restore_source_code - restore_node_dependencies - install_node_dependencies - save_node_dependencies bundle_dependencies: executor: default steps: - restore_source_code - restore_bundle_dependencies - install_bundler - install_bundle_dependencies - save_bundle_dependencies rubocop_job: executor: default steps: - restore_source_code - restore_bundle_dependencies - install_bundler - run_rubocop eslint_job: executor: default steps: - restore_source_code - restore_node_dependencies - run_eslint jest_job: executor: default steps: - restore_source_code - restore_node_dependencies - run_jest rspec_job: executor: extended steps: - restore_source_code - restore_bundle_dependencies - install_bundler - wait_for_db_start_up - set_up_database - run: name: Copy application.yml.sample command: cp -p config/application.yml.sample config/application.yml - run_rspec deploy: executor: default parameters: env: type: enum enum: ['staging', 'production'] steps: - restore_source_code - restore_bundle_dependencies - install_bundler - add_ssh_keys: fingerprints: <your_fingerprint_comes_here> - run: name: Deploy to << parameters.env >> command: bundle exec cap << parameters.env >> deploy - slack/status: mentions: channel workflows: build_test_and_deploy: jobs: - checkout_code - node_dependencies: requires: - checkout_code - bundle_dependencies: requires: - checkout_code - rubocop_job: requires: - bundle_dependencies - eslint_job: requires: - node_dependencies - jest_job: requires: - node_dependencies - rspec_job: requires: - bundle_dependencies - deploy: name: Deploy to staging env: staging requires: - rubocop_job - eslint_job - jest_job - rspec_job filters: branches: only: develop - deploy: name: Deploy to production env: production requires: - rubocop_job - eslint_job - jest_job - rspec_job filters: branches: only: masterポイント
参照先
- 投稿日:2020-02-09T10:36:10+09:00
Rails アプリを作成する際に出たエラー
目的
- アプリ作成コマンド
$ rails new アプリ名
を実行した際に出力されたエラーを解決したときの話をまとめるエラー
- 下記にエラー内容を記載する。
[10:51:19]MacBook-miriwo~/workspace/study/rails$ rails new tropical_fish_sns --database=mysql create create README.md create Rakefile create .ruby-version create config.ru create .gitignore create Gemfile run git init from "." Initialized empty Git repository in /Users/admin/workspace/study/rails/tropical_fish_sns/.git/ create package.json create app create app/assets/config/manifest.js create app/assets/stylesheets/application.css create app/channels/application_cable/channel.rb create app/channels/application_cable/connection.rb create app/controllers/application_controller.rb create app/helpers/application_helper.rb create app/javascript/channels/consumer.js create app/javascript/channels/index.js create app/javascript/packs/application.js create app/jobs/application_job.rb create app/mailers/application_mailer.rb create app/models/application_record.rb create app/views/layouts/application.html.erb create app/views/layouts/mailer.html.erb create app/views/layouts/mailer.text.erb create app/assets/images/.keep create app/controllers/concerns/.keep create app/models/concerns/.keep create bin create bin/rails create bin/rake create bin/setup create bin/yarn create config create config/routes.rb create config/application.rb create config/environment.rb create config/cable.yml create config/puma.rb create config/spring.rb create config/storage.yml create config/environments create config/environments/development.rb create config/environments/production.rb Traceback (most recent call last): 47: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/bin/rails:23:in `<main>' 46: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/bin/rails:23:in `load' 45: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/exe/rails:10:in `<top (required)>' 44: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require' 43: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/2.5.0/rubygems/core_ext/kernel_require.rb:59:in `require' 42: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/cli.rb:18:in `<top (required)>' 41: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/command.rb:46:in `invoke' 40: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/command/base.rb:69:in `perform' 39: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor.rb:392:in `dispatch' 38: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command' 37: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/command.rb:27:in `run' 36: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/commands/application/application_command.rb:26:in `perform' 35: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/base.rb:485:in `start' 34: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/group.rb:232:in `dispatch' 33: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/invocation.rb:134:in `invoke_all' 32: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/invocation.rb:134:in `map' 31: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/invocation.rb:134:in `each' 30: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/invocation.rb:134:in `block in invoke_all' 29: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/invocation.rb:127:in `invoke_command' 28: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/command.rb:27:in `run' 27: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/generators/rails/app/app_generator.rb:319:in `create_config_files' 26: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/generators/app_base.rb:156:in `build' 25: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/generators/rails/app/app_generator.rb:106:in `config' 24: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/generators/rails/app/app_generator.rb:19:in `inside' 23: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions.rb:187:in `inside' 22: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/2.5.0/fileutils.rb:122:in `cd' 21: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/2.5.0/fileutils.rb:122:in `chdir' 20: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions.rb:187:in `block in inside' 19: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/generators/rails/app/app_generator.rb:115:in `block in config' 18: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/generators/rails/app/app_generator.rb:19:in `directory' 17: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/directory.rb:52:in `directory' 16: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions.rb:93:in `action' 15: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/directory.rb:66:in `invoke!' 14: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/directory.rb:80:in `execute!' 13: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/directory.rb:80:in `each' 12: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/directory.rb:92:in `block in execute!' 11: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/file_manipulation.rb:122:in `template' 10: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/create_file.rb:25:in `create_file' 9: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions.rb:93:in `action' 8: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/create_file.rb:60:in `invoke!' 7: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/empty_directory.rb:117:in `invoke_with_conflict_check' 6: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/create_file.rb:63:in `block in invoke!' 5: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/create_file.rb:63:in `open' 4: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/create_file.rb:63:in `block (2 levels) in invoke!' 3: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/create_file.rb:53:in `render' 2: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/thor-1.0.1/lib/thor/actions/file_manipulation.rb:131:in `block in template' 1: from /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/2.5.0/erb.rb:885:in `result' /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/2.5.0/erb.rb:885:in `eval': /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/railties-6.0.2.1/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt:10: syntax error, unexpected ')', expecting end-of-input (SyntaxError) ); if spring_install?原因
- Railsのバージョンを指定せず現時点で最新のものを使用し作成したため発生したエラーである。
解決方法
$ rails new アプリ名
の実行時にrailsのバージョンを指定することで解決した。- 下記に問題を解決したときのコマンドを記載する。
$ rails _6.0.0_ new tropical_fish_sns --database=mysql付録
- 下記に正常に
$ rails new アプリ名
が実行できたときのターミナル出力を記載する。[10:54:05]MacBook-miriwo~/workspace/study/rails$ rails _6.0.0_ new tropical_fish_sns --database=mysql create create README.md create Rakefile create .ruby-version create config.ru create .gitignore create Gemfile run git init from "." Initialized empty Git repository in /Users/admin/workspace/study/rails/tropical_fish_sns/.git/ create package.json create app create app/assets/config/manifest.js create app/assets/stylesheets/application.css create app/channels/application_cable/channel.rb create app/channels/application_cable/connection.rb create app/controllers/application_controller.rb create app/helpers/application_helper.rb create app/javascript/channels/consumer.js create app/javascript/channels/index.js create app/javascript/packs/application.js create app/jobs/application_job.rb create app/mailers/application_mailer.rb create app/models/application_record.rb create app/views/layouts/application.html.erb create app/views/layouts/mailer.html.erb create app/views/layouts/mailer.text.erb create app/assets/images/.keep create app/controllers/concerns/.keep create app/models/concerns/.keep create bin create bin/rails create bin/rake create bin/setup create bin/yarn create config create config/routes.rb create config/application.rb create config/environment.rb create config/cable.yml create config/puma.rb create config/spring.rb create config/storage.yml create config/environments create config/environments/development.rb create config/environments/production.rb create config/environments/test.rb create config/initializers create config/initializers/application_controller_renderer.rb create config/initializers/assets.rb create config/initializers/backtrace_silencers.rb create config/initializers/content_security_policy.rb create config/initializers/cookies_serializer.rb create config/initializers/cors.rb create config/initializers/filter_parameter_logging.rb create config/initializers/inflections.rb create config/initializers/mime_types.rb create config/initializers/new_framework_defaults_6_0.rb create config/initializers/wrap_parameters.rb create config/locales create config/locales/en.yml create config/master.key append .gitignore create config/boot.rb create config/database.yml create db create db/seeds.rb create lib create lib/tasks create lib/tasks/.keep create lib/assets create lib/assets/.keep create log create log/.keep create public create public/404.html create public/422.html create public/500.html create public/apple-touch-icon-precomposed.png create public/apple-touch-icon.png create public/favicon.ico create public/robots.txt create tmp create tmp/.keep create tmp/cache create tmp/cache/assets create vendor create vendor/.keep create test/fixtures create test/fixtures/.keep create test/fixtures/files create test/fixtures/files/.keep create test/controllers create test/controllers/.keep create test/mailers create test/mailers/.keep create test/models create test/models/.keep create test/helpers create test/helpers/.keep create test/integration create test/integration/.keep create test/channels/application_cable/connection_test.rb create test/test_helper.rb create test/system create test/system/.keep create test/application_system_test_case.rb create storage create storage/.keep create tmp/storage create tmp/storage/.keep remove config/initializers/cors.rb remove config/initializers/new_framework_defaults_6_0.rb run bundle install The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies...... Using rake 13.0.1 Using concurrent-ruby 1.1.5 Using i18n 1.8.2 Using minitest 5.14.0 Using thread_safe 0.3.6 Using tzinfo 1.2.6 Using zeitwerk 2.2.2 Using activesupport 6.0.2.1 Using builder 3.2.4 Using erubi 1.9.0 Using mini_portile2 2.4.0 Using nokogiri 1.10.7 Using rails-dom-testing 2.0.3 Using crass 1.0.6 Using loofah 2.4.0 Using rails-html-sanitizer 1.3.0 Using actionview 6.0.2.1 Using rack 2.1.2 Using rack-test 1.1.0 Using actionpack 6.0.2.1 Using nio4r 2.5.2 Using websocket-extensions 0.1.4 Using websocket-driver 0.7.1 Using actioncable 6.0.2.1 Using globalid 0.4.2 Using activejob 6.0.2.1 Using activemodel 6.0.2.1 Using activerecord 6.0.2.1 Using mimemagic 0.3.4 Using marcel 0.3.3 Using activestorage 6.0.2.1 Using mini_mime 1.0.2 Using mail 2.7.1 Using actionmailbox 6.0.2.1 Using actionmailer 6.0.2.1 Using actiontext 6.0.2.1 Using public_suffix 4.0.3 Using addressable 2.7.0 Using bindex 0.8.1 Using msgpack 1.3.3 Using bootsnap 1.4.5 Using bundler 2.0.2 Using byebug 11.1.1 Using regexp_parser 1.6.0 Using xpath 3.2.0 Using capybara 3.31.0 Using childprocess 3.0.0 Using ffi 1.12.2 Using jbuilder 2.9.1 Using rb-fsevent 0.10.3 Using rb-inotify 0.10.1 Using ruby_dep 1.5.0 Using listen 3.1.5 Using method_source 0.9.2 Using mysql2 0.5.3 Using puma 3.12.2 Using rack-proxy 0.6.5 Using thor 1.0.1 Using railties 6.0.2.1 Using sprockets 3.7.2 Using sprockets-rails 3.2.1 Using rails 6.0.2.1 Using rubyzip 2.2.0 Using sass-listen 4.0.0 Using sass 3.7.4 Using tilt 2.0.10 Using sass-rails 5.1.0 Using selenium-webdriver 3.142.7 Using spring 2.1.0 Using spring-watcher-listen 2.0.1 Using turbolinks-source 5.2.0 Using turbolinks 5.2.1 Using web-console 4.0.1 Using webdrivers 4.2.0 Using webpacker 4.2.2 Bundle complete! 17 Gemfile dependencies, 75 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. run bundle binstubs bundler The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. run bundle exec spring binstub --all * bin/rake: Spring inserted * bin/rails: Spring inserted rails webpacker:install create config/webpacker.yml Copying webpack core config create config/webpack create config/webpack/development.js create config/webpack/environment.js create config/webpack/production.js create config/webpack/test.js Copying postcss.config.js to app root directory create postcss.config.js Copying babel.config.js to app root directory create babel.config.js Copying .browserslistrc to app root directory create .browserslistrc The JavaScript app source directory already exists apply /Users/admin/.anyenv/envs/rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/webpacker-4.2.2/lib/install/binstubs.rb Copying binstubs exist bin create bin/webpack create bin/webpack-dev-server append .gitignore Installing all JavaScript dependencies [4.2.2] run yarn add @rails/webpacker@4.2.2 from "." yarn add v1.19.1 info No lockfile found. [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... [4/4] ? Building fresh packages... success Saved lockfile. warning Your current version of Yarn is out of date. The latest version is "1.22.0", while you're on "1.19.1". info To upgrade, run the following command: $ brew upgrade yarn success Saved 610 new dependencies. info Direct dependencies ├─ @rails/actioncable@6.0.2 ├─ @rails/activestorage@6.0.2 ├─ @rails/ujs@6.0.2 ├─ @rails/webpacker@4.2.2 └─ turbolinks@5.2.0 info All dependencies ├─ @babel/code-frame@7.8.3 ├─ @babel/core@7.8.4 ├─ @babel/helper-builder-binary-assignment-operator-visitor@7.8.3 ├─ @babel/helper-call-delegate@7.8.3 ├─ @babel/helper-compilation-targets@7.8.4 ├─ @babel/helper-create-class-features-plugin@7.8.3 ├─ @babel/helper-define-map@7.8.3 ├─ @babel/helper-explode-assignable-expression@7.8.3 ├─ @babel/helper-wrap-function@7.8.3 ├─ @babel/helpers@7.8.4 ├─ @babel/highlight@7.8.3 ├─ @babel/plugin-proposal-async-generator-functions@7.8.3 ├─ @babel/plugin-proposal-class-properties@7.8.3 ├─ @babel/plugin-proposal-dynamic-import@7.8.3 ├─ @babel/plugin-proposal-json-strings@7.8.3 ├─ @babel/plugin-proposal-nullish-coalescing-operator@7.8.3 ├─ @babel/plugin-proposal-object-rest-spread@7.8.3 ├─ @babel/plugin-proposal-optional-catch-binding@7.8.3 ├─ @babel/plugin-proposal-optional-chaining@7.8.3 ├─ @babel/plugin-proposal-unicode-property-regex@7.8.3 ├─ @babel/plugin-syntax-top-level-await@7.8.3 ├─ @babel/plugin-transform-arrow-functions@7.8.3 ├─ @babel/plugin-transform-async-to-generator@7.8.3 ├─ @babel/plugin-transform-block-scoped-functions@7.8.3 ├─ @babel/plugin-transform-block-scoping@7.8.3 ├─ @babel/plugin-transform-classes@7.8.3 ├─ @babel/plugin-transform-computed-properties@7.8.3 ├─ @babel/plugin-transform-destructuring@7.8.3 ├─ @babel/plugin-transform-dotall-regex@7.8.3 ├─ @babel/plugin-transform-duplicate-keys@7.8.3 ├─ @babel/plugin-transform-exponentiation-operator@7.8.3 ├─ @babel/plugin-transform-for-of@7.8.4 ├─ @babel/plugin-transform-function-name@7.8.3 ├─ @babel/plugin-transform-literals@7.8.3 ├─ @babel/plugin-transform-member-expression-literals@7.8.3 ├─ @babel/plugin-transform-modules-amd@7.8.3 ├─ @babel/plugin-transform-modules-commonjs@7.8.3 ├─ @babel/plugin-transform-modules-systemjs@7.8.3 ├─ @babel/plugin-transform-modules-umd@7.8.3 ├─ @babel/plugin-transform-named-capturing-groups-regex@7.8.3 ├─ @babel/plugin-transform-new-target@7.8.3 ├─ @babel/plugin-transform-object-super@7.8.3 ├─ @babel/plugin-transform-parameters@7.8.4 ├─ @babel/plugin-transform-property-literals@7.8.3 ├─ @babel/plugin-transform-regenerator@7.8.3 ├─ @babel/plugin-transform-reserved-words@7.8.3 ├─ @babel/plugin-transform-runtime@7.8.3 ├─ @babel/plugin-transform-shorthand-properties@7.8.3 ├─ @babel/plugin-transform-spread@7.8.3 ├─ @babel/plugin-transform-sticky-regex@7.8.3 ├─ @babel/plugin-transform-template-literals@7.8.3 ├─ @babel/plugin-transform-typeof-symbol@7.8.4 ├─ @babel/plugin-transform-unicode-regex@7.8.3 ├─ @babel/preset-env@7.8.4 ├─ @babel/runtime@7.8.4 ├─ @rails/actioncable@6.0.2 ├─ @rails/activestorage@6.0.2 ├─ @rails/ujs@6.0.2 ├─ @rails/webpacker@4.2.2 ├─ @types/parse-json@4.0.0 ├─ @types/q@1.5.2 ├─ @webassemblyjs/floating-point-hex-parser@1.8.5 ├─ @webassemblyjs/helper-code-frame@1.8.5 ├─ @webassemblyjs/helper-fsm@1.8.5 ├─ @webassemblyjs/helper-wasm-section@1.8.5 ├─ @webassemblyjs/wasm-edit@1.8.5 ├─ @webassemblyjs/wasm-opt@1.8.5 ├─ @xtuc/ieee754@1.2.0 ├─ abbrev@1.1.1 ├─ acorn@6.4.0 ├─ aggregate-error@3.0.1 ├─ ajv-errors@1.0.1 ├─ ajv-keywords@3.4.1 ├─ ajv@6.11.0 ├─ amdefine@1.0.1 ├─ ansi-styles@3.2.1 ├─ anymatch@2.0.0 ├─ are-we-there-yet@1.1.5 ├─ argparse@1.0.10 ├─ arr-flatten@1.1.0 ├─ array-find-index@1.0.2 ├─ asn1.js@4.10.1 ├─ asn1@0.2.4 ├─ assert@1.5.0 ├─ assign-symbols@1.0.0 ├─ async-each@1.0.3 ├─ async-foreach@0.1.3 ├─ asynckit@0.4.0 ├─ atob@2.1.2 ├─ autoprefixer@9.7.4 ├─ aws-sign2@0.7.0 ├─ aws4@1.9.1 ├─ babel-loader@8.0.6 ├─ babel-plugin-macros@2.8.0 ├─ base@0.11.2 ├─ base64-js@1.3.1 ├─ bcrypt-pbkdf@1.0.2 ├─ big.js@5.2.2 ├─ binary-extensions@1.13.1 ├─ bindings@1.5.0 ├─ block-stream@0.0.9 ├─ bluebird@3.7.2 ├─ boolbase@1.0.0 ├─ brace-expansion@1.1.11 ├─ braces@2.3.2 ├─ browserify-aes@1.2.0 ├─ browserify-cipher@1.0.1 ├─ browserify-des@1.0.2 ├─ browserify-sign@4.0.4 ├─ browserify-zlib@0.2.0 ├─ buffer-xor@1.0.3 ├─ buffer@4.9.2 ├─ builtin-status-codes@3.0.0 ├─ cache-base@1.0.1 ├─ caller-callsite@2.0.0 ├─ caller-path@2.0.0 ├─ callsites@2.0.0 ├─ camelcase-keys@2.1.0 ├─ caniuse-lite@1.0.30001025 ├─ case-sensitive-paths-webpack-plugin@2.3.0 ├─ caseless@0.12.0 ├─ chokidar@2.1.8 ├─ chownr@1.1.3 ├─ chrome-trace-event@1.0.2 ├─ class-utils@0.3.6 ├─ clean-stack@2.2.0 ├─ cliui@5.0.0 ├─ clone-deep@4.0.1 ├─ coa@2.0.2 ├─ code-point-at@1.1.0 ├─ collection-visit@1.0.0 ├─ color-convert@1.9.3 ├─ color-name@1.1.3 ├─ color-string@1.5.3 ├─ color@3.1.2 ├─ combined-stream@1.0.8 ├─ commander@2.20.3 ├─ compression-webpack-plugin@3.1.0 ├─ concat-map@0.0.1 ├─ concat-stream@1.6.2 ├─ console-browserify@1.2.0 ├─ console-control-strings@1.1.0 ├─ constants-browserify@1.0.0 ├─ convert-source-map@1.7.0 ├─ copy-concurrently@1.0.5 ├─ copy-descriptor@0.1.1 ├─ core-js-compat@3.6.4 ├─ core-js@3.6.4 ├─ core-util-is@1.0.2 ├─ create-ecdh@4.0.3 ├─ create-hmac@1.1.7 ├─ cross-spawn@6.0.5 ├─ crypto-browserify@3.12.0 ├─ css-blank-pseudo@0.1.4 ├─ css-color-names@0.0.4 ├─ css-declaration-sorter@4.0.1 ├─ css-has-pseudo@0.10.0 ├─ css-loader@3.4.2 ├─ css-prefers-color-scheme@3.1.1 ├─ css-select-base-adapter@0.1.1 ├─ css-select@2.1.0 ├─ css-unit-converter@1.1.1 ├─ css-what@3.2.1 ├─ cssdb@4.4.0 ├─ cssnano-preset-default@4.0.7 ├─ cssnano-util-raw-cache@4.0.1 ├─ cssnano-util-same-parent@4.0.1 ├─ cssnano@4.1.10 ├─ csso@4.0.2 ├─ currently-unhandled@0.4.1 ├─ cyclist@1.0.1 ├─ dashdash@1.14.1 ├─ debug@2.6.9 ├─ decamelize@1.2.0 ├─ decode-uri-component@0.2.0 ├─ deep-extend@0.6.0 ├─ delayed-stream@1.0.0 ├─ delegates@1.0.0 ├─ des.js@1.0.1 ├─ detect-file@1.0.0 ├─ detect-libc@1.0.3 ├─ diffie-hellman@5.0.3 ├─ dom-serializer@0.2.2 ├─ domain-browser@1.2.0 ├─ domelementtype@1.3.1 ├─ domutils@1.7.0 ├─ dot-prop@4.2.0 ├─ duplexify@3.7.1 ├─ ecc-jsbn@0.1.2 ├─ electron-to-chromium@1.3.345 ├─ emoji-regex@7.0.3 ├─ emojis-list@2.1.0 ├─ enhanced-resolve@4.1.0 ├─ entities@2.0.0 ├─ errno@0.1.7 ├─ error-ex@1.3.2 ├─ es-to-primitive@1.2.1 ├─ escape-string-regexp@1.0.5 ├─ eslint-scope@4.0.3 ├─ esprima@4.0.1 ├─ esrecurse@4.2.1 ├─ estraverse@4.3.0 ├─ events@3.1.0 ├─ execa@1.0.0 ├─ expand-brackets@2.1.4 ├─ expand-tilde@2.0.2 ├─ extend@3.0.2 ├─ extglob@2.0.4 ├─ extsprintf@1.3.0 ├─ fast-deep-equal@3.1.1 ├─ fast-json-stable-stringify@2.1.0 ├─ file-loader@4.3.0 ├─ file-uri-to-path@1.0.0 ├─ fill-range@4.0.0 ├─ find-cache-dir@2.1.0 ├─ findup-sync@3.0.0 ├─ flatted@2.0.1 ├─ flatten@1.0.3 ├─ flush-write-stream@1.1.1 ├─ for-in@1.0.2 ├─ forever-agent@0.6.1 ├─ form-data@2.3.3 ├─ from2@2.3.0 ├─ fs-minipass@2.1.0 ├─ fs.realpath@1.0.0 ├─ fsevents@1.2.11 ├─ fstream@1.0.12 ├─ gauge@2.7.4 ├─ gaze@1.1.3 ├─ gensync@1.0.0-beta.1 ├─ get-caller-file@2.0.5 ├─ get-stream@4.1.0 ├─ getpass@0.1.7 ├─ glob-parent@3.1.0 ├─ glob@7.1.6 ├─ global-modules@2.0.0 ├─ global-prefix@3.0.0 ├─ globule@1.3.0 ├─ har-schema@2.0.0 ├─ har-validator@5.1.3 ├─ has-ansi@2.0.0 ├─ has-unicode@2.0.1 ├─ has-value@1.0.0 ├─ hash.js@1.1.7 ├─ hex-color-regex@1.1.0 ├─ hmac-drbg@1.0.1 ├─ hosted-git-info@2.8.5 ├─ hsl-regex@1.0.0 ├─ hsla-regex@1.0.0 ├─ html-comment-regex@1.1.2 ├─ http-signature@1.2.0 ├─ https-browserify@1.0.0 ├─ iconv-lite@0.4.24 ├─ icss-utils@4.1.1 ├─ ieee754@1.1.13 ├─ ignore-walk@3.0.3 ├─ import-cwd@2.1.0 ├─ import-fresh@2.0.0 ├─ import-from@2.1.0 ├─ import-local@2.0.0 ├─ in-publish@2.0.0 ├─ indent-string@4.0.0 ├─ infer-owner@1.0.4 ├─ inflight@1.0.6 ├─ ini@1.3.5 ├─ interpret@1.2.0 ├─ invert-kv@2.0.0 ├─ is-absolute-url@2.1.0 ├─ is-accessor-descriptor@1.0.0 ├─ is-arrayish@0.2.1 ├─ is-binary-path@1.0.1 ├─ is-callable@1.1.5 ├─ is-color-stop@1.1.0 ├─ is-data-descriptor@1.0.0 ├─ is-date-object@1.0.2 ├─ is-descriptor@1.0.2 ├─ is-directory@0.3.1 ├─ is-extglob@2.1.1 ├─ is-finite@1.0.2 ├─ is-obj@1.0.1 ├─ is-plain-obj@1.1.0 ├─ is-plain-object@2.0.4 ├─ is-regex@1.0.5 ├─ is-resolvable@1.1.0 ├─ is-stream@1.1.0 ├─ is-svg@3.0.0 ├─ is-symbol@1.0.3 ├─ is-typedarray@1.0.0 ├─ is-utf8@0.2.1 ├─ is-windows@1.0.2 ├─ is-wsl@1.1.0 ├─ isarray@1.0.0 ├─ isexe@2.0.0 ├─ isstream@0.1.2 ├─ jest-worker@25.1.0 ├─ js-base64@2.5.1 ├─ js-tokens@4.0.0 ├─ jsesc@2.5.2 ├─ json-schema-traverse@0.4.1 ├─ json-schema@0.2.3 ├─ json-stringify-safe@5.0.1 ├─ json5@2.1.1 ├─ jsprim@1.4.1 ├─ last-call-webpack-plugin@3.0.0 ├─ lcid@2.0.0 ├─ leven@3.1.0 ├─ lines-and-columns@1.1.6 ├─ load-json-file@1.1.0 ├─ loader-runner@2.4.0 ├─ locate-path@3.0.0 ├─ lodash.get@4.4.2 ├─ lodash.has@4.5.2 ├─ lodash.memoize@4.1.2 ├─ lodash.template@4.5.0 ├─ lodash.templatesettings@4.2.0 ├─ lodash.uniq@4.5.0 ├─ lodash@4.17.15 ├─ loose-envify@1.4.0 ├─ loud-rejection@1.6.0 ├─ make-dir@2.1.0 ├─ mamacro@0.0.3 ├─ map-age-cleaner@0.1.3 ├─ map-obj@1.0.1 ├─ map-visit@1.0.0 ├─ mdn-data@2.0.4 ├─ mem@4.3.0 ├─ memory-fs@0.4.1 ├─ meow@3.7.0 ├─ merge-stream@2.0.0 ├─ miller-rabin@4.0.1 ├─ mime-db@1.43.0 ├─ mime-types@2.1.26 ├─ mimic-fn@2.1.0 ├─ mini-css-extract-plugin@0.8.2 ├─ minimalistic-assert@1.0.1 ├─ minimalistic-crypto-utils@1.0.1 ├─ minimatch@3.0.4 ├─ minimist@1.2.0 ├─ minipass-collect@1.0.2 ├─ minipass-flush@1.0.5 ├─ minipass-pipeline@1.2.2 ├─ minipass@3.1.1 ├─ minizlib@1.3.3 ├─ mississippi@3.0.0 ├─ mixin-deep@1.3.2 ├─ mkdirp@0.5.1 ├─ ms@2.1.2 ├─ nan@2.14.0 ├─ nanomatch@1.2.13 ├─ needle@2.3.2 ├─ nice-try@1.0.5 ├─ node-gyp@3.8.0 ├─ node-libs-browser@2.2.1 ├─ node-pre-gyp@0.14.0 ├─ node-releases@1.1.47 ├─ node-sass@4.13.1 ├─ nopt@3.0.6 ├─ normalize-package-data@2.5.0 ├─ normalize-range@0.1.2 ├─ normalize-url@1.9.1 ├─ npm-bundled@1.1.1 ├─ npm-packlist@1.4.8 ├─ npm-run-path@2.0.2 ├─ npmlog@4.1.2 ├─ nth-check@1.0.2 ├─ num2fraction@1.2.2 ├─ oauth-sign@0.9.0 ├─ object-assign@4.1.1 ├─ object-copy@0.1.0 ├─ object-inspect@1.7.0 ├─ object-keys@1.1.1 ├─ object.getownpropertydescriptors@2.1.0 ├─ object.values@1.1.1 ├─ optimize-css-assets-webpack-plugin@5.0.3 ├─ os-browserify@0.3.0 ├─ os-homedir@1.0.2 ├─ os-locale@3.1.0 ├─ os-tmpdir@1.0.2 ├─ osenv@0.1.5 ├─ p-defer@1.0.0 ├─ p-finally@1.0.0 ├─ p-is-promise@2.1.0 ├─ p-limit@2.2.2 ├─ p-locate@3.0.0 ├─ p-map@3.0.0 ├─ p-try@2.2.0 ├─ pako@1.0.11 ├─ parallel-transform@1.2.0 ├─ parent-module@1.0.1 ├─ parse-json@4.0.0 ├─ parse-passwd@1.0.0 ├─ pascalcase@0.1.1 ├─ path-browserify@0.0.1 ├─ path-complete-extname@1.0.0 ├─ path-dirname@1.0.2 ├─ path-exists@3.0.0 ├─ path-key@2.0.1 ├─ path-parse@1.0.6 ├─ path-type@4.0.0 ├─ performance-now@2.1.0 ├─ pinkie@2.0.4 ├─ pnp-webpack-plugin@1.6.0 ├─ posix-character-classes@0.1.1 ├─ postcss-attribute-case-insensitive@4.0.2 ├─ postcss-calc@7.0.1 ├─ postcss-color-functional-notation@2.0.1 ├─ postcss-color-gray@5.0.0 ├─ postcss-color-hex-alpha@5.0.3 ├─ postcss-color-mod-function@3.0.3 ├─ postcss-color-rebeccapurple@4.0.1 ├─ postcss-colormin@4.0.3 ├─ postcss-convert-values@4.0.1 ├─ postcss-custom-media@7.0.8 ├─ postcss-custom-properties@8.0.11 ├─ postcss-custom-selectors@5.1.2 ├─ postcss-dir-pseudo-class@5.0.0 ├─ postcss-discard-comments@4.0.2 ├─ postcss-discard-duplicates@4.0.2 ├─ postcss-discard-empty@4.0.1 ├─ postcss-discard-overridden@4.0.1 ├─ postcss-double-position-gradients@1.0.0 ├─ postcss-env-function@2.0.2 ├─ postcss-flexbugs-fixes@4.2.0 ├─ postcss-focus-visible@4.0.0 ├─ postcss-focus-within@3.0.0 ├─ postcss-font-variant@4.0.0 ├─ postcss-gap-properties@2.0.0 ├─ postcss-image-set-function@3.0.1 ├─ postcss-import@12.0.1 ├─ postcss-initial@3.0.2 ├─ postcss-lab-function@2.0.1 ├─ postcss-load-config@2.1.0 ├─ postcss-loader@3.0.0 ├─ postcss-logical@3.0.0 ├─ postcss-media-minmax@4.0.0 ├─ postcss-merge-longhand@4.0.11 ├─ postcss-merge-rules@4.0.3 ├─ postcss-minify-font-values@4.0.2 ├─ postcss-minify-gradients@4.0.2 ├─ postcss-minify-params@4.0.2 ├─ postcss-minify-selectors@4.0.2 ├─ postcss-modules-extract-imports@2.0.0 ├─ postcss-modules-local-by-default@3.0.2 ├─ postcss-modules-scope@2.1.1 ├─ postcss-modules-values@3.0.0 ├─ postcss-nesting@7.0.1 ├─ postcss-normalize-charset@4.0.1 ├─ postcss-normalize-display-values@4.0.2 ├─ postcss-normalize-positions@4.0.2 ├─ postcss-normalize-repeat-style@4.0.2 ├─ postcss-normalize-string@4.0.2 ├─ postcss-normalize-timing-functions@4.0.2 ├─ postcss-normalize-unicode@4.0.1 ├─ postcss-normalize-url@4.0.1 ├─ postcss-normalize-whitespace@4.0.2 ├─ postcss-ordered-values@4.1.2 ├─ postcss-overflow-shorthand@2.0.0 ├─ postcss-page-break@2.0.0 ├─ postcss-place@4.0.1 ├─ postcss-preset-env@6.7.0 ├─ postcss-pseudo-class-any-link@6.0.0 ├─ postcss-reduce-initial@4.0.3 ├─ postcss-reduce-transforms@4.0.2 ├─ postcss-replace-overflow-wrap@3.0.0 ├─ postcss-safe-parser@4.0.1 ├─ postcss-selector-matches@4.0.0 ├─ postcss-selector-not@4.0.0 ├─ postcss-svgo@4.0.2 ├─ postcss-unique-selectors@4.0.1 ├─ prepend-http@1.0.4 ├─ private@0.1.8 ├─ process-nextick-args@2.0.1 ├─ process@0.11.10 ├─ prr@1.0.1 ├─ pseudomap@1.0.2 ├─ psl@1.7.0 ├─ public-encrypt@4.0.3 ├─ pumpify@1.5.1 ├─ punycode@1.4.1 ├─ q@1.5.1 ├─ qs@6.5.2 ├─ query-string@4.3.4 ├─ querystring-es3@0.2.1 ├─ querystring@0.2.0 ├─ randomfill@1.0.4 ├─ rc@1.2.8 ├─ read-cache@1.0.0 ├─ read-pkg@1.1.0 ├─ readdirp@2.2.1 ├─ redent@1.0.0 ├─ regenerate-unicode-properties@8.1.0 ├─ regenerator-runtime@0.13.3 ├─ regenerator-transform@0.14.1 ├─ regexpu-core@4.6.0 ├─ regjsgen@0.5.1 ├─ regjsparser@0.6.2 ├─ remove-trailing-separator@1.1.0 ├─ repeat-element@1.1.3 ├─ repeating@2.0.1 ├─ request@2.88.0 ├─ require-main-filename@2.0.0 ├─ resolve-cwd@2.0.0 ├─ resolve-dir@1.0.1 ├─ resolve-url@0.2.1 ├─ resolve@1.15.1 ├─ ret@0.1.15 ├─ rgb-regex@1.0.1 ├─ rgba-regex@1.0.0 ├─ rimraf@2.7.1 ├─ run-queue@1.0.3 ├─ safer-buffer@2.1.2 ├─ sass-graph@2.2.4 ├─ sass-loader@7.3.1 ├─ sax@1.2.4 ├─ scss-tokenizer@0.2.3 ├─ semver@5.7.1 ├─ set-value@2.0.1 ├─ setimmediate@1.0.5 ├─ shallow-clone@3.0.1 ├─ shebang-command@1.2.0 ├─ shebang-regex@1.0.0 ├─ simple-swizzle@0.2.2 ├─ snapdragon-node@2.1.1 ├─ snapdragon-util@3.0.1 ├─ sort-keys@1.1.2 ├─ source-list-map@2.0.1 ├─ source-map-resolve@0.5.3 ├─ source-map-support@0.5.16 ├─ source-map-url@0.4.0 ├─ spark-md5@3.0.0 ├─ spdx-correct@3.1.0 ├─ spdx-exceptions@2.2.0 ├─ split-string@3.1.0 ├─ sprintf-js@1.0.3 ├─ sshpk@1.16.1 ├─ ssri@7.1.0 ├─ stable@0.1.8 ├─ static-extend@0.1.2 ├─ stdout-stream@1.4.1 ├─ stream-browserify@2.0.2 ├─ stream-each@1.2.3 ├─ stream-http@2.8.3 ├─ strict-uri-encode@1.1.0 ├─ string_decoder@1.1.1 ├─ string.prototype.trimleft@2.1.1 ├─ string.prototype.trimright@2.1.1 ├─ strip-bom@2.0.0 ├─ strip-eof@1.0.0 ├─ strip-indent@1.0.1 ├─ strip-json-comments@2.0.1 ├─ style-loader@1.1.3 ├─ stylehacks@4.0.3 ├─ supports-color@6.1.0 ├─ svgo@1.3.2 ├─ tar@2.2.2 ├─ terser-webpack-plugin@2.3.4 ├─ terser@4.6.3 ├─ through2@2.0.5 ├─ timers-browserify@2.0.11 ├─ timsort@0.3.0 ├─ to-arraybuffer@1.0.1 ├─ to-fast-properties@2.0.0 ├─ to-object-path@0.3.0 ├─ to-regex-range@2.1.1 ├─ tough-cookie@2.4.3 ├─ trim-newlines@1.0.0 ├─ true-case-path@1.0.3 ├─ ts-pnp@1.1.5 ├─ tslib@1.10.0 ├─ tty-browserify@0.0.0 ├─ tunnel-agent@0.6.0 ├─ turbolinks@5.2.0 ├─ tweetnacl@0.14.5 ├─ typedarray@0.0.6 ├─ unicode-canonical-property-names-ecmascript@1.0.4 ├─ unicode-match-property-ecmascript@1.0.4 ├─ unicode-match-property-value-ecmascript@1.1.0 ├─ unicode-property-aliases-ecmascript@1.0.5 ├─ union-value@1.0.1 ├─ unique-slug@2.0.2 ├─ unquote@1.1.1 ├─ unset-value@1.0.0 ├─ upath@1.2.0 ├─ uri-js@4.2.2 ├─ urix@0.1.0 ├─ url@0.11.0 ├─ use@3.1.1 ├─ util-deprecate@1.0.2 ├─ util.promisify@1.0.1 ├─ util@0.11.1 ├─ uuid@3.4.0 ├─ v8-compile-cache@2.0.3 ├─ validate-npm-package-license@3.0.4 ├─ vendors@1.0.4 ├─ verror@1.10.0 ├─ vm-browserify@1.1.2 ├─ watchpack@1.6.0 ├─ webpack-assets-manifest@3.1.1 ├─ webpack-cli@3.3.10 ├─ webpack@4.41.5 ├─ which-module@2.0.0 ├─ which@1.3.1 ├─ wide-align@1.1.3 ├─ worker-farm@1.7.0 ├─ wrap-ansi@5.1.0 ├─ xtend@4.0.2 ├─ yallist@3.1.1 ├─ yaml@1.7.2 ├─ yargs-parser@13.1.1 └─ yargs@13.2.4 ✨ Done in 49.66s. Installing dev server for live reloading run yarn add --dev webpack-dev-server from "." yarn add v1.19.1 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0". warning " > webpack-dev-server@3.10.3" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0". [4/4] ? Building fresh packages... success Saved lockfile. success Saved 100 new dependencies. info Direct dependencies └─ webpack-dev-server@3.10.3 info All dependencies ├─ @types/events@3.0.0 ├─ @types/glob@7.1.1 ├─ @types/minimatch@3.0.3 ├─ @types/node@13.7.0 ├─ accepts@1.3.7 ├─ ansi-colors@3.2.4 ├─ ansi-html@0.0.7 ├─ array-flatten@1.1.1 ├─ array-union@1.0.2 ├─ array-uniq@1.0.3 ├─ async-limiter@1.0.1 ├─ async@2.6.3 ├─ batch@0.6.1 ├─ body-parser@1.19.0 ├─ bonjour@3.5.0 ├─ buffer-indexof@1.1.1 ├─ cliui@4.1.0 ├─ compressible@2.0.18 ├─ compression@1.7.4 ├─ connect-history-api-fallback@1.6.0 ├─ content-disposition@0.5.3 ├─ cookie-signature@1.0.6 ├─ cookie@0.4.0 ├─ deep-equal@1.1.1 ├─ default-gateway@4.2.0 ├─ del@4.1.1 ├─ destroy@1.0.4 ├─ detect-node@2.0.4 ├─ dns-equal@1.0.0 ├─ dns-packet@1.3.1 ├─ dns-txt@2.0.2 ├─ ee-first@1.1.1 ├─ eventemitter3@4.0.0 ├─ eventsource@1.0.7 ├─ express@4.17.1 ├─ faye-websocket@0.10.0 ├─ finalhandler@1.1.2 ├─ follow-redirects@1.10.0 ├─ forwarded@0.1.2 ├─ globby@6.1.0 ├─ handle-thing@2.0.0 ├─ hpack.js@2.1.6 ├─ html-entities@1.2.1 ├─ http-deceiver@1.2.7 ├─ http-parser-js@0.4.10 ├─ http-proxy-middleware@0.19.1 ├─ http-proxy@1.18.0 ├─ internal-ip@4.3.0 ├─ ip-regex@2.1.0 ├─ ip@1.1.5 ├─ ipaddr.js@1.9.1 ├─ is-absolute-url@3.0.3 ├─ is-arguments@1.0.4 ├─ is-path-cwd@2.2.0 ├─ is-path-in-cwd@2.1.0 ├─ is-path-inside@2.1.0 ├─ json3@3.3.3 ├─ killable@1.0.1 ├─ loglevel@1.6.6 ├─ media-typer@0.3.0 ├─ merge-descriptors@1.0.1 ├─ methods@1.1.2 ├─ mime@2.4.4 ├─ multicast-dns-service-types@1.1.0 ├─ multicast-dns@6.2.3 ├─ negotiator@0.6.2 ├─ node-forge@0.9.0 ├─ object-is@1.0.2 ├─ obuf@1.1.2 ├─ on-headers@1.0.2 ├─ opn@5.5.0 ├─ original@1.0.2 ├─ p-retry@3.0.1 ├─ path-is-inside@1.0.2 ├─ path-to-regexp@0.1.7 ├─ portfinder@1.0.25 ├─ proxy-addr@2.0.5 ├─ querystringify@2.1.1 ├─ raw-body@2.4.0 ├─ regexp.prototype.flags@1.3.0 ├─ retry@0.12.0 ├─ select-hose@2.0.0 ├─ selfsigned@1.10.7 ├─ serve-index@1.9.1 ├─ serve-static@1.14.1 ├─ sockjs-client@1.4.0 ├─ sockjs@0.3.19 ├─ spdy-transport@3.0.0 ├─ spdy@4.0.1 ├─ thunky@1.1.0 ├─ type-is@1.6.18 ├─ unpipe@1.0.0 ├─ utils-merge@1.0.1 ├─ wbuf@1.7.3 ├─ webpack-dev-middleware@3.7.2 ├─ webpack-dev-server@3.10.3 ├─ websocket-extensions@0.1.3 ├─ ws@6.2.1 ├─ yargs-parser@11.1.1 └─ yargs@12.0.5 ✨ Done in 14.68s. Webpacker successfully installed ? ?
- 投稿日:2020-02-09T07:29:29+09:00
Choices.js + fetchAPIでフィルタ付き動的セレクトボックス [脱jQuery]
概要
件名のモノが必要になった時
ググって出てきたのはajaxやcoffee script、select2にchosenと
内容が古かったり環境制約で使えないものだったりで苦しめられたので
rails6とpure javascriptで動くサンプルを遺します↓
Sample app source code
- Ruby 2.7.0
- Rails 6.0.2.1
- Choices.js
- Fetch API
Choices.js
素のjavascriptで書かれた軽量custom select box
jQueryに依存せずにselect2やchosenのようなフィルタ機能を実現します
option設定やメソッドの詳細はGitHubで確認出来ます
公式動作デモ
Fetch API
Webブラウザの
新しめの標準APIで、非同期通信処理をカンタンに書けます
従来のjQuery.ajax、XHR(XMLHttpRequest)を代替するとかしないとか。
How does it work?
大まかな処理の流れは以下になります
- ユーザが親select boxからカテゴリを選択する
- 親select boxのchangeイベントが発火
- javascriptがfetchでserverのtile一覧apiを叩く
- serverがtile一覧をjsonで返す
- javascriptがjsonを受け取る
- javascriptがchoicesメソッドを経由して子select boxにjsonの内容をセットする
以下具体的なコードです
javascript
/javascript/tiles.js
document.addEventListener('DOMContentLoaded', function() { /* apply choices to select boxes */ const choicesOptions = { shouldSort: false, removeItemButton: true, searchResultLimit: 9, // default: 4 searchFields: ['label'] // default: ['label', 'value'] }; const selects = document.querySelectorAll('select'); selects.forEach(function(select) { select.choices = new Choices(select, choicesOptions); }); /* add event listener to first select */ const firstSelect = document.getElementById('first_select') if (firstSelect != null) { firstSelect.addEventListener('change', setSecondChoices); // type: 'input' is not effective. } /* get tiles json by fetch api */ async function getTiles(tile_category_id) { const params = new URLSearchParams(); params.set('tile_category_id', tile_category_id); const apiURL = `/api/tiles?${params}`; const response = await fetch(apiURL); const json = await response.json(); return json; } /* set values to second select */ function setSecondChoices() { const secondSelect = document.getElementById('second_select'); const choices = secondSelect.choices; /* clear current values & selected item */ choices.clearStore(); const tile_category_id = firstSelect.value; getTiles(tile_category_id).then(json => { // setChoices(choices, value, label, replaceChoices); choices.setChoices(json, 'index', 'display_name', true); }); } });
要点を抜粋解説します
序盤の以下の部分で素のselect要素に対してchoicesを適用しています
const selects = document.querySelectorAll('select'); selects.forEach(function(select) { select.choices = new Choices(select, choicesOptions); });
choices適用後はカスタムタグが挿入されて見た目が変わり、フィルタが使えるようになります
select boxに対する項目の追加や削除はchoicesメソッドを通す必要があるので注意して下さい
続くaddEventListenerで親select boxのchangeイベントを拾い、setSecondChoices()を呼びます
firstSelect.addEventListener('change', setSecondChoices);
setSecondChoices()内
choices適用時にselect.choices = new Choices...
という風に書いたので
以下のようにselect boxのchoicesにアクセス出来ますconst choices = secondSelect.choices;
親カテゴリが変更された時、変更前に選んだ子が残っていると宜しくないのでクリアします
choices.clearStore();
getTiles()は非同期処理で
/api/tiles?
を叩きます
async/await+fetchAPIを使用します/* get tiles json by fetch api */ async function getTiles(tile_category_id) { const params = new URLSearchParams(); params.set('tile_category_id', tile_category_id); const apiURL = `/api/tiles?${params}`; const response = await fetch(apiURL); const json = await response.json(); return json; }
ES6縛りでasync/await記法が使えない場合は以下をお使い下さい
/* set values to second select */ function setSecondChoices() { const secondSelect = document.getElementById('second_select'); const choices = secondSelect.choices; /* clear current values & selected item */ choices.clearStore(); const tile_category_id = firstSelect.value; const params = new URLSearchParams(); params.set('tile_category_id', tile_category_id); const url = `/api/tiles?${params}`; /* get tiles JSON by fetch api */ fetch(url).then(function(response) { return response.json(); }).then(function(json) { // setChoices(choices, value, label, replaceChoices); choices.setChoices(json, 'index', 'display_name', true); }); }
apiを叩いて返ってくるjsonの構造は以下のような形式です
[ {"display_name":"白","index":41}, {"display_name":"發","index":42}, {"display_name":"中","index":43} ]
setChoices()の第二, 第三引数で
choicesのvalueとlabelに対応するjson中のhashのkeyを指定します(value, labelの順)// setChoices(choices, value, label, replaceChoices); choices.setChoices(json, 'index', 'display_name', true);これで子select boxの選択肢が書き換わります
/javascript/packs/application.js
webpackerにpackしてもらう為、↑のtiles.jsをimportしてるだけimport '../tiles.js'
routes.rb
- select boxesを表示:
/
(root)- カテゴリ毎のtile一覧を取得するapi:
/api/tiles?tile_category_id=xxx
Rails.application.routes.draw do root controller: :top_page, action: :show namespace :api do resources :tiles, only: [:index] end end
model
Model定義と一覧取得は今回の本筋ではないのでhashでベタ書きしました (DB不要です)
RailsユーザならActiveRecordに置き換えるのは造作も無い事でしょう
indexは各牌識別用の通し番号です
rubyのsymbolって漢字使えたんですね…
/models/concerns/tiles_identifiable.rb
module TilesIdentifiable extend ActiveSupport::Concern included do def get_tiles(tile_category_id:) tile_category = tile_categories.key(tile_category_id) case tile_category when :萬子 then characters when :筒子 then dots when :索子 then bamboos when :風牌 then winds when :三元牌 then dragons else all_tiles end end def tile_categories { 萬子: 0, 筒子: 1, 索子: 2, 風牌: 3, 三元牌: 4, } end def numbers 1..9 end def chinese_numerals ['一', '二', '三', '四', '五', '六', '七', '八', '九'] end def to_chinese_numerals(number) chinese_numerals[number - 1] end # tile: { display_name:, index: } def characters(base_index: 10 * 0) numbers.map { | number | { display_name: "#{to_chinese_numerals(number)}萬", index: base_index + number } } end def dots(base_index: 10 * 1) numbers.map { | number | { display_name: "#{to_chinese_numerals(number)}筒", index: base_index + number } } end def bamboos(base_index: 10 * 2) numbers.map { | number | { display_name: "#{to_chinese_numerals(number)}索", index: base_index + number } } end def winds(base_index: 10 * 3) ['東', '南', '西', '北'].map.with_index(1) { | wind, index | { display_name: wind, index: base_index + index } } end def dragons(base_index: 10 * 4) ['白', '發', '中'].map.with_index(1) { | dragon, index | { display_name: dragon, index: base_index + index } } end def all_tiles characters | dots | bamboos | winds | dragons end end end
controller
controllers/top_page_controller.rb
class TopPageController < ApplicationController include TilesIdentifiable before_action :set_values private def set_values @first_choices = tile_categories.to_a end end
controllers/api/tiles_controller.rb
指定されたカテゴリに対応する牌一覧をjsonで返すapiclass Api::TilesController < ApplicationController include TilesIdentifiable def index tile_category_id = params[:tile_category_id] if tile_category_id.blank? render json: all_tiles else render json: get_tiles(tile_category_id: tile_category_id.to_i) end end end
view
素のerbです
views/top_page/show.html.erb
<div class='form_wrapper'> <%= form_with do | form | %> <%= form.label 'tile categories' %> <%= form.select :first_select, @first_choices, { include_blank: true }, {} %> <%= form.label 'tiles' %> <%= form.select :second_select, [], { include_blank: true }, {} %> <% end %> </div>
views/layouts/application.html.erb
choicesのjsとcss、whatwg-fetchのjsをCDNでお手軽導入<head> ... <!-- Include Choices CSS --> <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/choices.js/public/assets/styles/choices.min.css'/> <!-- Include Choices JavaScript (latest) --> <script src='https://cdn.jsdelivr.net/npm/choices.js/public/assets/scripts/choices.min.js'></script> <!-- Include whatwg-fetch --> <script src='https://cdn.jsdelivr.net/npm/whatwg-fetch@3.0.0/dist/fetch.umd.min.js'></script> </head>
最後に
誰かの何らかの助けになれば幸いです
フィルタの日本語動作の確認で漢字を使う麻雀牌(Tile)にしてみたけど
牌一覧用意するのが無駄に面倒でもうやらないと思います
- 投稿日:2020-02-09T01:20:52+09:00
世はまさに Web アプリケーションフレームワーク戦国時代?
Web アプリケーションフレームワークを選ぶ基準って、人それぞれ、色々とあるとは思いますが、やっぱり流行も気になってしまうのが Web エンジニア心というものではないでしょうか?
それに、よく使われているということは、
- 当然、フレームワークとしての成熟度は高いはずですし、
- サードパーティのライブラリも充実していて、
- 比較的、Web サイトや書籍などから得られる情報量も多い
と、考えられます。
初めて Web アプリを作成するという人はもちろん、
ある程度、知識のある方でも、軽い気持ちで新しいフレームワークを学んでみたいというのであれば、流行りに乗っておくのが無難かもしれません。
というわけで、ここでは、
- Ruby
- Python
- PHP
- JavaScript
で、実装されている「 人気の Web アプリケーションフレームワーク 」について、興味本位でまとめてみましたので、良かったら何かの参考にでもして頂ければ幸いです。
Ruby on Rails vs. Sinatra
Ruby の Web アプリケーションフレームワークと言えば、Ruby on Rails ですね。
GitHub のスター数は、現在、45k となっています。
https://github.com/rails/rails
異論は認めますが、比較するとしたら、Sinatra くらいしかないでしょうか?
GitHub のスター数は、現在、10.9k となっています。
https://github.com/sinatra/sinatra
Google トレンド のグラフを見ると、Ruby の Web フレームワーク自体、徐々に人気が下がってきているようでもありますが、あとで他の言語とも比較してみましょう。
Django vs. Flask vs. Tornado
Google トレンド によると、Python では、やっぱり Django が人気ですが、Flask の人気もかなり上がってきているように見えます。
単純に考えれば、Python 自体の人気が急上昇していることが要因でしょうか。
GitHub のスター数はと言うと、現在、Django が 47.1k で、Flask が 48.8k となっています。
https://github.com/django/django
https://github.com/pallets/flask比較対象として、もう一つ、Tornado という Web フレームワークを挙げておきましょう。
こちらは、GitHub のスター数が、現在、18.8k となっています。
https://github.com/tornadoweb/tornado
Laravel vs. Symfony vs. CodeIgniter
PHP では、Laravel が人気を博しているようですね。
GitHub のスター数は、現在、57.5k となっています。
https://github.com/laravel/laravel
何となくのイメージですが、昔から、Web アプリ作成のフレームワークと言えば PHP みたいなところもあるので、やはり定番なのでしょうか。
今回、Laravel と比較するのは、Symfony と CodeIgniter です。
GitHub のスター数は、現在、Symfony が 22.7k で、CodeIgniter が 17.9k となっています。
https://github.com/symfony/symfony
https://github.com/bcit-ci/CodeIgniterGoogle トレンド によると、他のフレームワークの人気がなくなってきていると言うよりは、純粋に Laravel が強すぎる感じですかね。
Vue.js vs. React vs. AngularJS
JavaScript に関しては、そもそも他の言語と同列に扱っていいのか、ちょっと微妙なところもあるかもしれませんが、
ともかく、Google トレンド のグラフを見ると、React の勢いが凄いです。
GitHub のスター数は、現在、143k となっています。
https://github.com/facebook/react
もちろん、GitHub のスター数が全てではありませんし、条件が同じとも言い難いですが、他の言語の Web フレームワークと、桁が違いますね。笑
しかし、スター数で言えば、実は、Vue.js の方が多く、現在、157k となっています。
また、AngularJS はと言うと、GitHub のスター数は、現在、59.6k となっていますが、それでも多いので、全く侮れません。
https://github.com/angular/angular.js
ただ、Google トレンド によると、Vue.js の人気も、上がってはいるのですが、他の二つに比べるとちょっと低すぎるように見えます。
一体、どういうことでしょうか?
ここへきて、この人気調査?の信憑性が危ぶまれる事態とも言えますが、これは「 一つの指標だけに囚われてはいけない 」ということの好例、ということにしておきましょう。
Ruby on Rails vs. Django vs. Laravel vs. React
まぁ、Google トレンド については、ものによって、キーワードの指定や検索の仕方で結果が大きく変わってしまう場合もあるので、自分でも試してみることをオススメします。
ちなみに、今回は、国やカテゴリなどは絞り込まず、同形異義語を排除するために、プログラミング言語名を一緒に検索してみました。
他にも、人気を判断するために、こんな指標があるよ!というような情報がありましたら、是非、教えて頂ければと思います。
話は逸れましたが、最後に、懲りもせず Google トレンド で、
- Ruby on Rails
- Django
- Laravel
- React
の、各言語で人気らしい Web フレームワークの比較もしてみましょうか。
結果は、こんな感じになりました。
URL は、https://trends.google.co.jp/trends/explore?date=2016-01-01%202019-12-31&q=rails%20ruby,django%20python,laravel%20php,react%20javascript です。このグラフによれば、Rails の人気が下がってきて、Django と Laravel は少しずつ上昇し、React は急上昇したことにより、
世はまさに「 Web アプリケーションフレームワーク戦国時代 」に突入した!と、言えるのかもしれません。笑
特にどの言語に媚びることもない、こんな平和な?結論が、果たしてあり得るのかどうかはともかく、たとえこれが事実だったとしても、ここを分岐点として、これから、どのフレームワークが生き残っていくのか、頭一つ抜けるのか、見ものではありますよね。
しばらくしたら、また調べてみたいと思います。
- 投稿日:2020-02-09T00:21:05+09:00
初めてのWebアプリ制作 - ruby on rail - 4日目
はじめに
せき風邪をひいて4日程寝込んでおり、更新が止まってしまってしまいました・・・(´・ω・`)
新型コロナが流行ってる最中なので、病院ヘはいかず自宅療養してました
皆様も今の風邪にはご注意ください!4日目
それでは、4日目についてまとめて行こうと思います。
まず最初に、風邪で寝込んでる最中もスマホを使って、
rails関連の記事などを読んでいました。
そんな中で下記記事に行きつき、衝撃を受けます・・・。Herokuで画像アップロード機能を備えたWebアプリをデプロイした場合、
画像の保存自体はできるが、一定時間で消えてしまうという内容の記事でした。Herokuで画像アップロード機能を利用したWebアプリをデプロイする場合は、
画像をクラウドストレージなどを利用する必要があるそうです。そのため、画像アップロード機能自体を見直すことになりました。
画像アップロード機能の見直し
まず、Herokuに画像アップロード機能を備えたWebアプリをデプロイする場合、
画像アップロードする方法については、以下の2つの選択肢があるようです。①クラウドストレージ(AWS S3 / Cloudinary など)にアップロードし、
データベース上には、アップロード先のURLを保存する方法②画像データをバイナリ化して、データベースに保存する方法
結論としては、今回は①の方法を選択。
クラウドストレージには、AWS S3を利用する事にしました。理由としては、以下の通り。
画像バイナリ化については、メリットが少なくデメリットが多い事
(下記リンクの回答がすごい参考になりました)
データベースに画像を保存するのはありでしょうか?クラウドストレージは今後も利用する場面が多くありそうと感じた
使用するGem
クラウドストレージを使うにあたっては、参考記事が多かった下記Gemを利用しようと思います。
・carrierwave
・Fogまだ、利用方法がよくわかっていないので、今日の残りの時間と、明日の時間を利用して、
実際にアップロード機能の実装を適当なプロジェクトを作成して試してみようと思います。風邪に続いてメインの実装部分が止まってしまいますが、何とか1日使って覚えようと思います!
5日目の目標
5日目の目標については、以下の通り!
-テストアプリを作成し、carrierwave + fog を利用したクラウドストレージへの画像アップロード機能の実装4日目の目標だった以下については、まんま6日目の目標へ
-ログイン機能の実装
-セッションコントローラの作成
-イメージポストモデルの作成
-画像投稿機能の実装正直、どんどん先延ばしになっていってる感じが否めないですが、
とにかく完成に向かって頑張ろうと思います・・・!