- 投稿日:2020-09-27T23:40:38+09:00
英語を習っていない小学1年生でも書ける、l(エル)メソッド
l(エル)メソッドとは
時刻や日時を表すときに使うメソッド。指定した現地時間に対応できる。SNSで「何月何日の何時に投稿した」なんてときに使う。テーブルのcreated_atカラムを表示したいときなどに使う。
現地時間を指定する方法
ステップ1
config/application.rbclass Application < Rails::Application config.i18n.default_locale = :ja #日本の場合 config.time_zone = 'Tokyo' #東京の場合 endRailsの全ての環境設定のファイルに、上記の記述をする。
ステップ2
ja.ymlファイルを作成する。
config/locales/ja.ymlja: time: formats: default: "%Y/%m/%d %H:%M:%S"lメソッドの使い方
上記の2つの設定をしたら、時刻の入ったカラムを表示させるメソッドの前に「l」と入れるだけ。
ex) 拡張子が.html.erbなら<%= l name.created_at %> #2020/9/27/ 23:32:09 と表示されるポイント
- configディレクトリのapplicationファイルに現地時間の設定をする。
- localesディレクトリにja.ymlファイルを作成し、時間の表示方法を指定する。
- 使う時は、メソッドの前に「l」を記述する。
最後に
投稿機能を実装するときには必須のメソッドとなりそう。
- 投稿日:2020-09-27T22:38:57+09:00
Rails: rake taskを良い感じに書く方法
良い感じ = 以下の二点
- テストしやすいこと
- 工数削減のため
- (ある程度は)エンジニア間での書き方が統一できること
- 例えば
dryrun
の指定方法が書き手によってまちまちだと、商用でのtask実行時に事故が起きる可能性もあるので統一可能であればした方が良い- ログのフォーマットが統一されていないと作業効率が悪い
などなど
コード
lib/tasks/issue_6885.rakerequire_relative 'helpers/all_user_name_update_helper.rb' namespace :issue_6885 do desc 'これはサンプルです' task all_user_name_update: :environment do helper = AllUserNameUpdateHelper.new helper.main end endlib/tasks/helpers/all_user_name_update_helper.rbrequire_relative 'rake_helper_template' class AllUserNameUpdateHelper < RakeHelperTemplate NEW_NAME = 'bar' def main template do |logger| all_users = get_all_user logger.info("対象ユーザー数: #{all_users.size}") if all_users.blank? logger.info('対象ユーザー数が0件だったため処理を終了します') return end all_users.each { |user| update_name(user, NEW_NAME) } end end private def get_all_user User.all end def update_name(user, new_name) user.update!(name: new_name) user end endlib/tasks/helpers/rake_helper_template.rbclass RakeHelperTemplate # 標準出力も行う def make_logger(log_file_path) logger = ActiveSupport::Logger.new(log_file_path) stdout_logger = ActiveSupport::Logger.new(STDOUT) broadcast_logger = ActiveSupport::Logger.broadcast(stdout_logger) logger.extend(broadcast_logger) logger.formatter = Logger::Formatter.new logger end # ログファイル名はtask名と同じにする(コロンは使わない方が良いので置換する) # rakeタスクの実行以外から呼び出されるケース(例: spec)を考慮しておく def make_log_file_path task_name = Rake.try(:application)&.top_level_tasks&.[](0)&.gsub(':', '_') || Rails.env log_file_name = "#{task_name}.log" Rails.root.join('log', log_file_name) end def template log_file_path = make_log_file_path # 明示的に文字列のfalseを渡さない限りは必ずdryrunにする is_dryrun = ENV['is_dryrun'] != 'false' logger = make_logger(log_file_path) logger.info("Start. is_dryrun: #{is_dryrun}") ActiveRecord::Base.transaction do yield(logger) raise ActiveRecord::Rollback if is_dryrun end logger.info("Finish. log_file_path: #{log_file_path}") end end実行例
dryrunにする場合$ is_dryrun=false bundle exec rake issue_6885:all_user_name_update I, [2020-09-27T13:33:27.229764 #13040] INFO -- : Start. is_dryrun: true I, [2020-09-27T13:33:27.556890 #13040] INFO -- : 対象ユーザー数: 1 I, [2020-09-27T13:33:27.755933 #13040] INFO -- : Finish. log_file_path: /app/log/issue_6885_all_user_name_update.logポイント
- .rakeファイルはhelperクラスを呼び出すだけにして、rakeのDSL?的なお作法を気にせずに普段書き慣れているclassのメソッドをテストするようにした
- yieldを使って
dryrun
,logger
(の一部)を共通化し、rake taskを作成するたびに毎回定義しなくても済むようにした
- mainメソッド(名前は何でも良い)には極力そのtaskの処理フローだけを定義するようにし、個別具体的な処理はテストしやすい粒度で別メソッドに切り出すようにする
テンプレートにしてしまえばall_user_name_update_helper.rbのようにやりたいことに集中できる!
(^○^)
所感
- rake taskの書き方は久々に書くと忘れてるのでこの記事は実は備忘録的な意味合いもあったり
- yieldは正直わかりにくいので使いたくない派だけど、テンプレートのように毎回書いたり読んだりしないものであれば良いかなという考え
- 他にも良い感じの書き方あるよという方いたら教えてください!
- 投稿日:2020-09-27T19:26:18+09:00
指定されたエラー場所にファイルがあるのに、rails sで、`rescue in block in modules_for_hoge': Missing hoge file hoge/hoge_hoge.rb (AbstractController::Helpers::MissingHogeError)と出た時の対処法。
エラー文
qiita.rb/home/ubuntu/.rvm/gems/ruby-2.6.3/gems/actionpack-6.0.3/lib/abstract_controller/helpers.rb:152:in `rescue in block in modules_for_helpers': Missing helper file helpers/microposts_helper.rb (AbstractController::Helpers::MissingHelperError) from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/actionpack-6.0.3/lib/abstract_controller/helpers.rb:149:in `block in modules_for_helpers' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/actionpack-6.0.3/lib/abstract_controller/helpers.rb:145:in `map!' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/actionpack-6.0.3/lib/abstract_controller/helpers.rb:145:in `modules_for_helpers' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/actionpack-6.0.3/lib/action_controller/metal/helpers.rb:94:in `modules_for_helpers' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/actionpack-6.0.3/lib/abstract_controller/helpers.rb:109:in `helper' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/actionpack-6.0.3/lib/action_controller/railties/helpers.rb:19:in `inherited' from /home/ubuntu/environment/sample_app/app/controllers/application_controller.rb:1:in `<main>' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `require' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:22:in `block in require_with_bootsnap_lfi' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/loaded_features_index.rb:92:in `register' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:21:in `require_with_bootsnap_lfi' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/bootsnap-1.4.5/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/zeitwerk-2.4.0/lib/zeitwerk/kernel.rb:27:in `require' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/actiontext-6.0.3/lib/action_text/engine.rb:47:in `block (2 levels) in <class:Engine>' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:428:in `instance_exec' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:428:in `block in make_lambda' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:200:in `block (2 levels) in halting' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:605:in `block (2 levels) in default_terminator' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:604:in `catch' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:604:in `block in default_terminator' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:201:in `block in halting' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:513:in `block in invoke_before' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:513:in `each' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:513:in `invoke_before' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/callbacks.rb:134:in `run_callbacks' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/execution_wrapper.rb:119:in `complete!' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/execution_wrapper.rb:90:in `wrap' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/activesupport-6.0.3/lib/active_support/reloader.rb:51:in `reload!' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/spring-2.1.0/lib/spring/application.rb:168:in `serve' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/spring-2.1.0/lib/spring/application.rb:145:in `block in run' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/spring-2.1.0/lib/spring/application.rb:139:in `loop' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/spring-2.1.0/lib/spring/application.rb:139:in `run' from /home/ubuntu/.rvm/gems/ruby-2.6.3/gems/spring-2.1.0/lib/spring/application/boot.rb:19:in `<top (required)>' from /home/ubuntu/.rvm/rubies/ruby-2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require' from /home/ubuntu/.rvm/rubies/ruby-2.6.3/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require' from -e:1:in `<main>'対処法
bundle updateして、再度rails sで無事テストできました。
- 投稿日:2020-09-27T17:03:25+09:00
f.collection_selectの"選択して下さい"を選んだときのバリデーション
1.やりたいこと
<%= f.collection_select :address_id, @customer.addresses, :id, :full_address, :include_blank => "選択してください" %>
フォームにおいて選択せずに一番上の"選択して下さい"を選んだ際にバリデーションをかけたい。2.実装する
app/controllers/orders_controller.rbparams[:order][:address_id] == "" flash[:notice] = "選択して下さい" redirect_to new_order_pathパラメータから送られている値を見ると
""
となっていたのでイコール文を""
に変更。3.終わりに
これを実装したかった時にQiitaやGoogleで検索しても全然出てこなかったので
投稿しました。
しっかりターミナルを見て返ってきてる値を確認しないといけないと痛感しました。
- 投稿日:2020-09-27T16:48:51+09:00
Redmineのコードリーディング環境をDockerで構築する
概要
redmineのソースコードを読むための環境をDockerで構築します。以下の状態をゴールとします。
- redmineのmasterブランチのコードがDocker上で動作する
- Debug用のツールが使える
モチベーション
動作環境を構築するのは実際のアプリケーションの挙動とコード内容を頭の中で紐付けながらコードリーディングしたいからです。
なぜコードリーディングしたいのか?
人が書いたコードを読むことは新たな発見や学びに繋がると考えています。
私の場合は、クラスの分け方、命名、テストコードの書き方、エラーハンドリングの仕方などに悩むことが最近多いので、そのあたりの引き出しを増やしたいというモチベーションがあります。なぜRedmineか?
Redmineを選んだのは以下の理由です。
- 普段からRedmineを使っているため馴染みがあり、画面や外部仕様をある程度知っている。
- 長年メンテナンスされており、世界中で使用されている。
- 普段はPHPをつかっているため、あえて別の言語やFWに触れてみる。
環境構築
以下を参考にすすめていきます。
リポジトリの用意
- redmineのレポジトリをフォーク
- フォークしたリポジトリをクローン
redmine
ディレクトリに移動- masterブランチから別ブランチを切る(自分は
code_reading
にしました)Docker関連ファイルの作成
redmine
ディレクトリ直下に以下のファイルを作成します。
- Dockerfile
- docker-compose.yml
- entrypoint.sh
ファイルの内容は以下の通りにします。
Dockerfile
RedmineのGemfileが
config/database.yml
を参照しているため、COPY . /redmine
をした後にbundle install
をする必要があります。
ちなみに参照して何をしているかというと、使用するDBにあわせてインストールするGemを変えています。例えばconfig/database.yml
にpostgresの接続情報が書いてあると、postgresに接続するためのGemがインストールされます。DockerfileFROM ruby:2.5 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn RUN mkdir /redmine WORKDIR /redmine COPY Gemfile /redmine/Gemfile COPY Gemfile.lock /redmine/Gemfile.lock COPY . /redmine RUN bundle install # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /redmine/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"docker-compose.yml
コメントアウトしているのはruby-debug/ruby-debug-ide用の設定です。
導入しましたが自分の環境では動作が安定しなかったので一旦使わないようにしています。docker-compose.ymlversion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data ports: - "5433:5432" environment: POSTGRES_PASSWORD: password web: build: . # command: bash -c "rm -f tmp/pids/server.pid && bundle exec rdebug-ide --host 0.0.0.0 --port 1234 -- bin/rails s -p 3000 -b 0.0.0.0" command: bash -c "rm -f tmp/pids/server.pid && bin/rails s -p 3000 -b 0.0.0.0" volumes: - .:/redmine ports: - "3003:3000" # - "1234:1234" # - "26162:26162" stdin_open: true tty: true depends_on: - dbデバッグ用のGemの追記
- 【Rails】better_errorsとbinding_of_callerで自分でエラーを解決できるようになろう【初心者向け】 - Qiita
- pry-byebugでrubyをデバッグする - Qiita
などの記事を参考にGemfile.に以下を追加します。
- better_errors
- binding_of_caller
- pry-rails
- pry-byebug
Gemfilegroup :development do gem "yard" #この下に追記する gem "better_errors" gem "binding_of_caller" gem "pry-rails" gem "pry-byebug" # 安定動作しなかったのでコメントアウト # gem "ruby-debug-ide" # gem "debase" enddatabase.ymlの設定
config/database.yml
を作成し以下の内容にします。config/database.ymldefault: &default adapter: postgresql encoding: unicode host: db # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: postgres password: password development: <<: *default database: redmine_development test: <<: *default database: redmine_test production: <<: *default database: redmineまた、better_errosをDocker上の環境で使えるようにするため、
development.rb
に以下を追記します。config/enviroments/development.rbBetterErrors::Middleware.allow_ip! "0.0.0.0/0"環境の立ち上げ
ファイルの用意が完了したのでコンテナを起動し、アプリケーションを動かします。
# コンテナイメージのビルド $ docker-compose build # コンテナの起動 $ docker-compose up -d # DBの作成 $ docker-compose run --rm web rake db:create # マイグレーションの実行 $ docker-compose run --rm web bin/bundle exec rake db:migrate # Redmineのデフォルトデータ投入タスク $ docker-compose run --rm web bin/bundle exec rake redmine:load_default_data起動が成功していれば
docker-compse ps
で以下のようにStatusがUpになります。$ dcom ps Name Command State Ports -------------------------------------------------------------------------------- redmine_db_1 docker-entrypoint.sh postgres Up 0.0.0.0:5433->5432/tcp redmine_web_1 entrypoint.sh bash -c rm - ... Up 0.0.0.0:3003->3000/tcpこの後に
http://localhost:3003
にアクセスするとRedmineの画面が表示されます。
また、最初はIDとパスワード共にadmin
でログインすることができます。
これで環境構築は完了です。pry-byebugによるデバッグ
docker-compose up
でコンテナが起動している状態でコンテナにアタッチします。$ docker attach redmine_web_1
確認したいコードの該当箇所に
binding.pry
を追記します。以下の例はRedmineのルートパスにアクセスした時に実行されるwellcome#index
に追加しています。app/controllers/welcome_controller.rbdef index binding.pry #調べたいところに追加する @news = News.latest User.current endこの状態で
http://localhost:3003
にアクセスするとターミナルに以下の内容が表示されステップ実行などができるようになります。From: /redmine/app/controllers/welcome_controller.rb:25 WelcomeController#index: 23: def index 24: binding.pry => 25: @news = News.latest User.current 26: end [1] pry(#<WelcomeController>)> Started GET "/" for 172.18.0.1 at 2020-09-27 07:41:34 +0000 [1] pry(#<WelcomeController>)>
[1] pry(#<WelcomeController>)>
の箇所にコマンドを打つことでステップ実行などができます。
- next
- ステップイン
- step
- ステップオーバー
- continue
- プログラムの実行を続行しpryを終了
以上
- 投稿日:2020-09-27T16:48:51+09:00
Redmineのコードリーディング環境をDocker上に構築する
概要
redmineのソースコードを読むための環境をDockerで構築します。以下の状態をゴールとします。
- redmineのmasterブランチのコードがDocker上で動作する
- Debug用のツールが使える
モチベーション
動作環境を構築するのは実際のアプリケーションの挙動とコード内容を頭の中で紐付けながらコードリーディングしたいからです。
なぜコードリーディングしたいのか?
人が書いたコードを読むことは新たな発見や学びに繋がると考えています。
私の場合は、クラスの分け方、命名、テストコードの書き方、エラーハンドリングの仕方などに悩むことが最近多いので、そのあたりの引き出しを増やしたいというモチベーションがあります。なぜRedmineか?
Redmineを選んだのは以下の理由です。
- 普段からRedmineを使っているため馴染みがあり、画面や外部仕様をある程度知っている。
- 長年メンテナンスされており、世界中で使用されている。
- 普段はPHPをつかっているため、あえて別の言語やFWに触れてみる。
環境構築
以下を参考にすすめていきます。
リポジトリの用意
- redmineのレポジトリをフォーク
- フォークしたリポジトリをクローン
redmine
ディレクトリに移動- masterブランチから別ブランチを切る(自分は
code_reading
にしました)Docker関連ファイルの作成
redmine
ディレクトリ直下に以下のファイルを作成します。
- Dockerfile
- docker-compose.yml
- entrypoint.sh
ファイルの内容は以下の通りにします。
Dockerfile
RedmineのGemfileが
config/database.yml
を参照しているため、COPY . /redmine
をした後にbundle install
をする必要があります。
ちなみに参照して何をしているかというと、使用するDBにあわせてインストールするGemを変えています。例えばconfig/database.yml
にpostgresの接続情報が書いてあると、postgresに接続するためのGemがインストールされます。
Gemfileで指定するgemを動的に変えるという発想があまりなかったので既に学びがあったと感じています。DockerfileFROM ruby:2.5 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn RUN mkdir /redmine WORKDIR /redmine COPY Gemfile /redmine/Gemfile COPY Gemfile.lock /redmine/Gemfile.lock COPY . /redmine RUN bundle install # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /redmine/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"docker-compose.yml
コメントアウトしているのはruby-debug/ruby-debug-ide用の設定です。
導入しましたが自分の環境では動作が安定しなかったので一旦使わないようにしています。docker-compose.ymlversion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data ports: - "5433:5432" environment: POSTGRES_PASSWORD: password web: build: . # command: bash -c "rm -f tmp/pids/server.pid && bundle exec rdebug-ide --host 0.0.0.0 --port 1234 -- bin/rails s -p 3000 -b 0.0.0.0" command: bash -c "rm -f tmp/pids/server.pid && bin/rails s -p 3000 -b 0.0.0.0" volumes: - .:/redmine ports: - "3003:3000" # - "1234:1234" # - "26162:26162" stdin_open: true tty: true depends_on: - dbデバッグ用のGemの追記
- 【Rails】better_errorsとbinding_of_callerで自分でエラーを解決できるようになろう【初心者向け】 - Qiita
- pry-byebugでrubyをデバッグする - Qiita
などの記事を参考にGemfile.に以下を追加します。
- better_errors
- binding_of_caller
- pry-rails
- pry-byebug
Gemfilegroup :development do gem "yard" #この下に追記する gem "better_errors" gem "binding_of_caller" gem "pry-rails" gem "pry-byebug" # 安定動作しなかったのでコメントアウト # gem "ruby-debug-ide" # gem "debase" enddatabase.ymlの設定
config/database.yml
を作成し以下の内容にします。config/database.ymldefault: &default adapter: postgresql encoding: unicode host: db # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: postgres password: password development: <<: *default database: redmine_development test: <<: *default database: redmine_test production: <<: *default database: redmineまた、better_errosをDocker上の環境で使えるようにするため、
development.rb
に以下を追記します。config/enviroments/development.rbBetterErrors::Middleware.allow_ip! "0.0.0.0/0"環境の立ち上げ
ファイルの用意が完了したのでコンテナを起動し、アプリケーションを動かします。
# コンテナイメージのビルド $ docker-compose build # コンテナの起動 $ docker-compose up -d # DBの作成 $ docker-compose run --rm web rake db:create # マイグレーションの実行 $ docker-compose run --rm web bin/bundle exec rake db:migrate # Redmineのデフォルトデータ投入タスク $ docker-compose run --rm web bin/bundle exec rake redmine:load_default_data起動が成功していれば
docker-compse ps
で以下のようにStatusがUpになります。$ dcom ps Name Command State Ports -------------------------------------------------------------------------------- redmine_db_1 docker-entrypoint.sh postgres Up 0.0.0.0:5433->5432/tcp redmine_web_1 entrypoint.sh bash -c rm - ... Up 0.0.0.0:3003->3000/tcpこの後に
http://localhost:3003
にアクセスするとRedmineの画面が表示されます。
また、最初はIDとパスワード共にadmin
でログインすることができます。
これで環境構築は完了です。pry-byebugによるデバッグ
docker-compose up
でコンテナが起動している状態でコンテナにアタッチします。$ docker attach redmine_web_1
確認したいコードの該当箇所に
binding.pry
を追記します。以下の例はRedmineのルートパスにアクセスした時に実行されるwellcome#index
に追加しています。app/controllers/welcome_controller.rbdef index binding.pry #調べたいところに追加する @news = News.latest User.current endこの状態で
http://localhost:3003
にアクセスするとターミナルに以下の内容が表示されステップ実行などができるようになります。From: /redmine/app/controllers/welcome_controller.rb:25 WelcomeController#index: 23: def index 24: binding.pry => 25: @news = News.latest User.current 26: end [1] pry(#<WelcomeController>)> Started GET "/" for 172.18.0.1 at 2020-09-27 07:41:34 +0000 [1] pry(#<WelcomeController>)>
[1] pry(#<WelcomeController>)>
の箇所にコマンドを打つことでステップ実行などができます。
- next
- ステップイン
- step
- ステップオーバー
- continue
- プログラムの実行を続行しpryを終了
以上
- 投稿日:2020-09-27T16:48:51+09:00
Redmineのコードリーディング用環境をDocker上に構築する
概要
redmineのソースコードを読むための環境をDockerで構築します。以下の状態をゴールとします。
- redmineのmasterブランチのコードがDocker上で動作する
- Debug用のツールが使える
モチベーション
動作環境を構築するのは実際のアプリケーションの挙動とコード内容を頭の中で紐付けながらコードリーディングしたいからです。
なぜコードリーディングしたいのか?
人が書いたコードを読むことは新たな発見や学びに繋がると考えています。
私の場合は、クラスの分け方、命名、テストコードの書き方、エラーハンドリングの仕方などに悩むことが最近多いので、そのあたりの引き出しを増やしたいというモチベーションがあります。なぜRedmineか?
Redmineを選んだのは以下の理由です。
- 普段からRedmineを使っているため馴染みがあり、画面や外部仕様をある程度知っている。
- 長年メンテナンスされており、世界中で使用されている。
- 普段はPHPをつかっているため、あえて別の言語やFWに触れてみる。
環境構築
以下を参考にすすめていきます。
リポジトリの用意
- redmineのレポジトリをフォーク
- フォークしたリポジトリをクローン
redmine
ディレクトリに移動- masterブランチから別ブランチを切る(自分は
code_reading
にしました)Docker関連ファイルの作成
redmine
ディレクトリ直下に以下のファイルを作成します。
- Dockerfile
- docker-compose.yml
- entrypoint.sh
ファイルの内容は以下の通りにします。
Dockerfile
RedmineのGemfileが
config/database.yml
を参照しているため、COPY . /redmine
をした後にbundle install
をする必要があります。
ちなみに参照して何をしているかというと、使用するDBにあわせてインストールするGemを変えています。例えばconfig/database.yml
にpostgresの接続情報が書いてあると、postgresに接続するためのGemがインストールされます。
Gemfileで指定するgemを動的に変えるという発想があまりなかったので既に学びがあったと感じています。DockerfileFROM ruby:2.5 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarn RUN mkdir /redmine WORKDIR /redmine COPY Gemfile /redmine/Gemfile COPY Gemfile.lock /redmine/Gemfile.lock COPY . /redmine RUN bundle install # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /redmine/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"docker-compose.yml
コメントアウトしているのはruby-debug/ruby-debug-ide用の設定です。
導入しましたが自分の環境では動作が安定しなかったので一旦使わないようにしています。docker-compose.ymlversion: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data ports: - "5433:5432" environment: POSTGRES_PASSWORD: password web: build: . # command: bash -c "rm -f tmp/pids/server.pid && bundle exec rdebug-ide --host 0.0.0.0 --port 1234 -- bin/rails s -p 3000 -b 0.0.0.0" command: bash -c "rm -f tmp/pids/server.pid && bin/rails s -p 3000 -b 0.0.0.0" volumes: - .:/redmine ports: - "3003:3000" # - "1234:1234" # - "26162:26162" stdin_open: true tty: true depends_on: - dbデバッグ用のGemの追記
- 【Rails】better_errorsとbinding_of_callerで自分でエラーを解決できるようになろう【初心者向け】 - Qiita
- pry-byebugでrubyをデバッグする - Qiita
などの記事を参考にGemfile.に以下を追加します。
- better_errors
- binding_of_caller
- pry-rails
- pry-byebug
Gemfilegroup :development do gem "yard" #この下に追記する gem "better_errors" gem "binding_of_caller" gem "pry-rails" gem "pry-byebug" # 安定動作しなかったのでコメントアウト # gem "ruby-debug-ide" # gem "debase" endまた、better_errosをDocker上の環境で使えるようにするため、
development.rb
に以下を追記します。config/enviroments/development.rbBetterErrors::Middleware.allow_ip! "0.0.0.0/0"database.ymlの設定
config/database.yml
を作成し以下の内容にします。config/database.ymldefault: &default adapter: postgresql encoding: unicode host: db # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: postgres password: password development: <<: *default database: redmine_development test: <<: *default database: redmine_test production: <<: *default database: redmine環境の立ち上げ
ファイルの用意が完了したのでコンテナを起動し、アプリケーションを動かします。
# 空のGemfile.lockを作る $ touch Gemfile.lock # コンテナイメージのビルド $ docker-compose build # コンテナの起動 $ docker-compose up -d # DBの作成 $ docker-compose run --rm web rake db:create # マイグレーションの実行 $ docker-compose run --rm web bin/bundle exec rake db:migrate # Redmineのデフォルトデータ投入タスク $ docker-compose run --rm web bin/bundle exec rake redmine:load_default_data起動が成功していれば
docker-compse ps
で以下のようにStatusがUpになります。$ dcom ps Name Command State Ports -------------------------------------------------------------------------------- redmine_db_1 docker-entrypoint.sh postgres Up 0.0.0.0:5433->5432/tcp redmine_web_1 entrypoint.sh bash -c rm - ... Up 0.0.0.0:3003->3000/tcpこの後に
http://localhost:3003
にアクセスするとRedmineの画面が表示されます。
また、最初はIDとパスワード共にadmin
でログインすることができます。
これで環境構築は完了です。pry-byebugによるデバッグ
docker-compose up
でコンテナが起動している状態でコンテナにアタッチします。$ docker attach redmine_web_1
確認したいコードの該当箇所に
binding.pry
を追記します。以下の例はRedmineのルートパスにアクセスした時に実行されるwellcome#index
に追加しています。app/controllers/welcome_controller.rbdef index binding.pry #調べたいところに追加する @news = News.latest User.current endこの状態で
http://localhost:3003
にアクセスするとターミナルに以下の内容が表示されステップ実行などができるようになります。From: /redmine/app/controllers/welcome_controller.rb:25 WelcomeController#index: 23: def index 24: binding.pry => 25: @news = News.latest User.current 26: end [1] pry(#<WelcomeController>)> Started GET "/" for 172.18.0.1 at 2020-09-27 07:41:34 +0000 [1] pry(#<WelcomeController>)>
[1] pry(#<WelcomeController>)>
の箇所にコマンドを打つことでステップ実行などができます。
- next
- ステップイン
- step
- ステップオーバー
- continue
- プログラムの実行を続行しpryを終了
以上
- 投稿日:2020-09-27T16:27:10+09:00
RailsアプリケーションにTailwindを入れてみる
https://github.com/fukadashigeru/tailwind_sample_app/pull/1
環境
ruby '2.7.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.3', '>= 6.0.3.3' gem 'webpacker', '~> 4.0'準備
Slim
Gemfileに下記追加
gem 'slim-rails' gem 'html2slim'ターミナルで
bundle install先にビュー用のerbファイルを作成していたら、下記コマンドをターミナルで打つ
bundle exec erb2slim app/views app/views -dScaffold
ターミナルで下記打つ
bin/rails g scaffold blog content:textconfig/routes.rbに下記追加
root 'blogs#index'DB作成
bin/rails db:create bin/rails db:migrateTailwindを入れてみる
ref: tailwindcss Documentation
Tailwind用のスタイルを当てる
1.Install Tailwind via npm
# Using npm npm install tailwindcss # Using Yarn yarn add tailwindcss2.Add Tailwind to your CSS
app/javascript/src/scss/application.scssを追加
@import "tailwindcss/base"; @import "tailwindcss/components"; @import "tailwindcss/utilities";app/javascript/packs/application.jsに下記追加
import '../src/scss/application.scss'3.Create your Tailwind config file (optional)
npx tailwindcss init4.Process your CSS with Tailwind
postcss.config.jsに下記追加
module.exports = { plugins: [ // ... require('tailwindcss'), require('autoprefixer'), // ... ] }確認
ターミナルでアプリケーションを立ち上げる
bin/rails s別のターミナルでwebpackerを立ち上げる(これは不要?)
bin/webpack-dev-serverローカルホストにアクセス
http://localhost:3000/Taiwind
- 投稿日:2020-09-27T14:37:01+09:00
アクションとインスタンスメソッドって一緒じゃないの?何が違うの?
コントローラーに記述されるこれ
XXX.controller.rbdef アクション名 XXXXXX endこれってアクションとかインスタンスメソッドって言ったり、どっちが正しいの?何が違うの?
アクション
=>ルーティングで指定されるときの名前
つまり、これはアクション
config/routes.rbRails.application.routes.draw do get 'index' to: "tweets#index"インスタンスメソッド
=>アクションの指定によって実際に実行される、コントローラに実装された「def XXXX ~ end」という形式のコード
つまり上にも書いたこのコードはアクションになる。なるほど。。。
XXX.controller.rbdef アクション名 XXXXXX end
- 投稿日:2020-09-27T14:36:47+09:00
【ActiveAdmin】セレクトボックス内の項目を好きな順でソートする方法
はじめに
rails_adminを使っていたのですが、セレクトボックス内の項目順序を変えるのすら出来なくて泣き叫んでいました。
調べても調べても分からんかったので、これもしかして出来ないんちゃう……?と。そこでactive_adminに移行してしまいました。
こちらでは出来ました。構成
Item belongs_to Kind
active_adminで生成したファイルの設定
app/admin/items.rbActiveAdmin.register Item do # 変更許可するカラム permit_params :name, :kind_id # (中略) form do |f| inputs do input :name input :kind, as: :select, collection: Kind.all.order(:name) end actions end end.orderで逆順にするなら
.order(name: "DESC")
まとめ
うーん美しい。
地味に苦労したのですが、目標を分割したら割とすぐ出来ました。
1.カスタムした配列をセレクトボックスで選ばせる
2.Modelからデータを引っ張ってくる他の所で配列作ったりしてもいいのですが、最終的にこれで通りました。
もっと言うと、Modelの関連付けがちゃんとしてたら
as: :select
もこれ無くて大丈夫です。それにしても表示カラムの制限とか好き放題やれて、Modelごとに綺麗に分離してるの……気持ちいい……?
active_admin大好きになってきてます
終わり
- 投稿日:2020-09-27T14:32:53+09:00
railsのserver起動時エラー『 Webpacker configuration file not found』対処
本稿では、表題の
『Webpacker configuration file not found』
エラーが発生してサーバーが起動できない時の対処方法について、実際に行った手順を共有します。1(実行環境について)
当時の環境について(ここをクリック)
(Windowsの情報) Microsoft Windows [Version 10.0.18363.1110] (c) 2019 Microsoft Corporation. All rights reserved. (Nodeのバージョン) C:\WINDOWS\system32>node -v v14.12.0 ※Nodeがインストールされていない場合は次のコマンドを実行します。 brew install node (rubyのバージョン) C:\WINDOWS\system32>ruby -v ruby 2.6.6p146 (2020-03-31 revision 67876) [x64-mingw32](参考:) (railsのインストールの様子です。) C:\WINDOWS\system32>gem install rails Fetching tzinfo-1.2.7.gem Fetching thread_safe-0.3.6.gem ・・・ (中略) ・・・ driver, actioncable, mimemagic, marcel, activestorage, actionmailbox, actiontext, thor, method_source, railties, sprockets, sprockets-rails, rails after 88 seconds 40 gems installed C:\WINDOWS\system32>rails -v Rails 6.0.3.3(railsプロジェクトの作成。 プロジェクト名:kita_rb) C:\WINDOWS\system32>cd \xampp\htdocs (作成ディレクトリに移動) C:\xampp\htdocs>rails new kita_rb -G create create README.md create Rakefile create .ruby-version create config.ru create Gemfile ・・・ (中略) ・・・ Installing webdrivers 4.4.1 Fetching webpacker 4.3.0 Installing webpacker 4.3.0 Bundle complete! 14 Gemfile dependencies, 70 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. run bundle binstubs bundler rails webpacker:install Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/ (←エラーが出ている!!) C:\xampp\htdocs>cd kita_rb (プロジェクトディレクトリに移動しておく)
1.サーバーの起動(エラー発生時)
C:\xampp\htdocs\kita_rb>rails s => Booting Puma => Rails 6.0.3.3 application starting in development => Run `rails server --help` for more startup options Exiting Traceback (most recent call last): 49: from bin/rails:4:in `<main>' 48: from bin/rails:4:in `require' ・・・ (中略) ・・・ 2: from C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/webpacker-4.3.0/lib/webpacker/configuration.rb:88:in `data' 1: from C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/webpacker-4.3.0/lib/webpacker/configuration.rb:91:in `load' C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/webpacker-4.3.0/lib/webpacker/configuration.rb:95:in `rescue in load': Webpacker configuration file not found C:/xampp/htdocs/kita_rb/config/webpacker.yml. Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - C:/xampp/htdocs/kita_rb/config/webpacker.yml (RuntimeError) ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ (エラー発生!!)本稿では、この
『Webpacker configuration file not found』
エラーが発生してサーバーが起動できない時の対処方法について、解決のため実際に行った手順を共有しようと思います。1
エラーメッセージ中に
『Please run rails webpacker:install』(webpackerをインストールしなさい)
とありましたのでこれを実行します。
2.webpackerのインストール
プロジェクトのルートにあるGemfileの中に
gem 'webpacker', '~> 4.0'
の記述があることを確認します。参照Gemfilegem 'webpacker', '~> 4.0'bundle installを実行します。
C:\xampp\htdocs\kita_rb>rails webpacker:install Yarn not installed. Please download and install Yarn from https://yarnpkg.com/lang/en/docs/install/『Yarnがインストールされていない』ということですので
インストールします。3.Yarnをインストール
C:\xampp\htdocs\kita_rb>npm install -g yarn C:\Users\tatsu\AppData\Roaming\npm\yarn -> C:\Users\tatsu\AppData\Roaming\npm\node_modules\yarn\bin\yarn.js C:\Users\tatsu\AppData\Roaming\npm\yarnpkg -> C:\Users\tatsu\AppData\Roaming\npm\node_modules\yarn\bin\yarn.js + yarn@1.22.5 added 1 package in 0.786s
再度、bundle installを実行します。C:\xampp\htdocs\kita_rb>bundle install Using rake 13.0.1 Using concurrent-ruby 1.1.7 Using i18n 1.8.5 Using minitest 5.14.2 Using thread_safe 0.3.6 Using tzinfo 1.2.7 Using zeitwerk 2.4.0 ・・・ (中略) ・・・ Using turbolinks 5.2.1 Using tzinfo-data 1.2020.1 Using web-console 4.0.4 Using webdrivers 4.4.1 Using webpacker 4.3.0 Bundle complete! 14 Gemfile dependencies, 70 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed.
ここでやっとwebpackerのインストールができそうです。C:\xampp\htdocs\kita_rb>rails webpacker:install create config/webpacker.yml Copying webpack core config create config/webpack create config/webpack/development.js ・・・ (中略) ・・・ ├─ webpack-dev-middleware@3.7.2 ├─ webpack-dev-server@3.11.0 └─ ws@6.2.1 Done in 9.80s. Webpacker successfully installed ? ?
再度、サーバーを起動します。C:\xampp\htdocs\kita_rb>rails s => Booting Puma => Rails 6.0.3.3 application starting in development => Run `rails server --help` for more startup options *** SIGUSR2 not implemented, signal based restart unavailable! *** SIGUSR1 not implemented, signal based restart unavailable! *** SIGHUP not implemented, signal based logs reopening unavailable! Puma starting in single mode... * Version 4.3.6 (ruby 2.6.6-p146), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://[::1]:3000 * Listening on tcp://127.0.0.1:3000 Use Ctrl-C to stop Started GET "/" for ::1 at 2020-09-26 16:23:01 +0900 (10.1ms) SELECT sqlite_version(*) Processing by Rails::WelcomeController#index as HTML Rendering C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/railties-6.0.3.3/lib/rails/templates/rails/welcome/index.html.erb Rendered C:/Ruby26-x64/lib/ruby/gems/2.6.0/gems/railties-6.0.3.3/lib/rails/templates/rails/welcome/index.html.erb (Duration: 5.9ms | Allocations: 416) Completed 200 OK in 24ms (Views: 13.1ms | ActiveRecord: 0.0ms | Allocations: 2331)
http://localhost:3000 をブラウザで開いて確認します。起動成功しました!!
以上、よろしければ参考になさってください。
- 投稿日:2020-09-27T14:28:48+09:00
Basic認証を交えたテストコードの書き方
【概要】
1.結論
2.○○になるのはどういう時か
補足:開発環境
1.結論
環境変数を変数に埋め込み、visitでその環境変数を埋め込んだURLに飛ぶようにする!
2.どのように記載するのか
def basic_pass(path) #---❶ username = ENV["STUDY"] password = ENV["STUDY_password"] visit "http://#{username}:#{password}@#{Capybara.current_session.server.host}:#{Capybara.current_session.server.port}#{path}" end RSpec.describe 'コメント投稿', type: :system do before do @time = FactoryBot.create(:time) @comment = Faker::Lorem.sentence end it 'ログインしたユーザーは自己学習投稿の詳細ページでコメントできる' do # ログインする basic_pass new_user_session_path #---❷ fill_in 'Email', with: @time_report.user.email fill_in 'Password', with: @time_report.user.password find('input[name="commit"]').click expect(current_path).to eq root_path end end上記のように記載しました!
❶下記のURLの具体例は環境変数になっていないので、変数に環境変数を代入する形にしました。あとは下記のURLを真似させていただきました。
❷basic_passメソッドを結合テストコードが読み込まれる前に記載しないとBasic認証テストのID,パスワードを通過できません。なので、結合テストコードの内容の一番最初にコーディングし、新規登録画面(devise gem使用)に遷移するようにしています。かなり参考にしたURL:
Capybara + Headless Chrome (System Spec) で Basic認証 を通過する方法補足:開発環境
Ruby 2.6.5
Rails 6.0.3.3
MySQL
Visual Studio Code
(Caprybara,Rspec,GoogleChrome)
- 投稿日:2020-09-27T13:58:03+09:00
【rails】HTTPClientを用いて外部APIに接続する方法(QiitaAPIに接続してみた)
はじめに
railsで外部APIに接続する際にはgemのHTTPClientを使用します。
今回は例としてQiitaのAPIに接続して、記事一覧を取得(get)してみます。HTTPClientをインストール
Gemfilegem 'httpclient'ターミナル$ bundle installroute.rbに追加
エンドポイントは
/api/qiita
とします。routes.rbRails.application.routes.draw do namespace :api do get '/qiita' to: 'qiita#index' end endQiitaAPIに接続する(Controller)
まず、最も基本的な形です。(headerやqueryを指定しないgetリクエスト)
controllers/api/qiita_controller.rbclass Api::QiitaController < ApplicationController # HTTPClientを呼び出す require 'httpclient' def index url = "https://qiita.com/api/v2/items" # URLを設定 client = HTTPClient.new # インスタンスを生成 response = client.get(url) # Getリクエスト render json: JSON.parse(response.body) # 結果をjsonにパースして表示 end endhttp://localhost:3000/api/qiita にアクセスするとこんな感じでデータの一覧がjsonで返ってきます。
整形していないので非常に見づらいですね。次にheaderやqueryを指定する場合です。
controllers/api/qiita_controller.rbclass Api::QiitaController < ApplicationController # HTTPClientを呼び出す require 'httpclient' def index url = "https://qiita.com/api/v2/items" header = { Authorization: "Bearer xxxxx" } # 例) ヘッダーに"Bearer xxxxx"を付与 query = { page: 1, per_page: 20 } # 例) 1ページ目、1ページごとのデータ取得数を20件にするquery client = HTTPClient.new response = client.get(url, header: header, query: query) #headerとqueryを指定 render json: JSON.parse(response.body) end end終わりに
今回はgetリクエストのみだけの説明にしましたが、HTTPClientではPostなど他のリクエストも送れるので調べてみてください。
QiitaのAPIの詳細は下記を参照しました。
Qiita API v2 documentation - Qiita:Developer
Qiita API v2 の概要(非公式)
- 投稿日:2020-09-27T11:33:31+09:00
しがないRailsエンジニア2年生が2年間の振り返り
初めまして。
あと1ヶ月ほどでエンジニアになって2年経ちます。
振り返りついでにその時感じた課題の変遷をまとめようと思いました。
(一旦Railsに関することだけ抜き出しています)なんとか生き残ってるエンジニアの課題を共有できれば幸いです。
エンジニア歴
- 1ヶ月目
- Railsは半年ほど独学でやってきたものの全体的にあまり理解できていなかった
- Rails以外にもエンジニアの仕事の仕方やITの概念が必要で覚えることが多かった
- 3ヶ月目
- 何となくRailsが使えてるだけで天狗になっていた時期
- 動けば良いや精神
- 6ヶ月目
- 動くものは作れるようになったけどこの時は適当に書いて動くからヨシとしていた
- 1年目
- Railsのコードを俯瞰して追えるようになり全くわからなくなった
- 過去の自分が恥ずかしくなった
- Rails以外にReactやサーバ知識など、覚えることが多くRailsは後回しにしていた
- 1年2ヶ月目
- Railsそっちのけでインフラ周りに注力していた
- Railsはまだ動けば良いや精神
- 1年7ヶ月目
- API初挑戦
- Rails全然理解していないと自覚して焦りだす
- N+1を気をつけるようになる
- やっとクラスの使い方やRailsの動きや設定周りを理解(した気がする)
- やっとmapやAR(ActiveRecord)の動きを追えるようになる
- さらに焦る
- 1年10ヶ月目
- ModuleやClassが少し分かるようになり調子に乗る
- include, expendの違いが分かり調子に乗る
- OOD(オブジェクト指向設計)ができなくプログラミング自体分からない時期突入
- Rails wayが分からなくて泣く
- 共通化ができなくて泣く
- 3年目(これから)
- Rails wayを身に付けたい
- 処理を見て非効率な部分を改善できるようになりたい
- セキュアな書き方も身に付けたい
- バックエンドに絞って頑張っていきたい
ざっと書いてみました。
最近感じたことしか覚えてないのでメモ書きです。思い出した時に追記していきます。
自分用のメモ書きにはなりますが誰かのモチベや目標に繋がれば嬉しいです。
- 投稿日:2020-09-27T11:05:36+09:00
[Ruby on Rails]データ登録時に重複したレコードがあった場合、登録させない
背景
オリジナルアプリをデプロイ後、重複したデータを登録されていることに気づきます。
アプリの仕様としては、登録するレコードは重複させたくないのです。
データベースを確認すると、
登録するユーザが異なっていれば、登録できてしまうことが判明。
解決にそこそこ時間がかかったので、備忘として記録します。
問題箇所
controllerにデータ登録時の条件を設けていました。
require 'rubygems' require 'mechanize' def create f = (params[:wiki_url]) unless f.start_with?("https://ja.wikipedia.org/wiki/") flash[:notice] = "無効なURLが入力されたため保存できませんでした。" redirect_to action: 'new' return else agent = Mechanize.new page = agent.get(f) page.encoding='utf-8' 以下、省略...unless文には、入力したデータ(URL)に「https://ja.wikipedia.org/wiki/」
が含まれていない場合は、登録させないという条件を組んでいました。しかし、重複したレコードがある場合、登録させないという条件が含まれていなかったため、
写真のような同じデータが登録されてしまったわけです。解決(結論)
以下のように書き換えて、解決しました。
require 'rubygems' require 'mechanize' def create f = (params[:wiki_url]) if not f.start_with?("https://ja.wikipedia.org/wiki/") flash[:notice] = "無効なURLが入力されたため登録できませんでした。" redirect_to action: 'new' return elsif Company.where(page_url: "#{f}").count >= 1 flash[:notice] = "登録済みのURLが入力されたため登録できませんでした。" redirect_to actiont: 'new' return else agent = Mechanize.new page = agent.get(f) page.encoding = 'utf-8' 以下、省略...teratailの記事が参考になり、解決に至りました。
同じ名前のデータが2件以上登録されているレコードをActiveRecordを用いて取得したいです。変更点は大きく2つです。
- unless文からif not文に変更
- whereメソッド追加(ココがポイント!)
unless文からif not文に変更
ruby unless文にelsifはないよ。。。
だそうです。不正データの入力防止の条件に加えて、重複防止の条件を追加したい。
しかし、unless文にelsifが使えないので、
if not文に変えてelsifを追加しました。whereメソッド追加(ココがポイント!)
条件(処理)の流れは以下の通りです。
- whereメソッドで、テーブル内の条件に一致したレコードの数を取得する
- 取得したデータの数を条件にかけて1以上(データがあるか)判定する
- 1以上(データがあり)ならば、受付けない
whereメソッドで、テーブル内の条件に一致したレコードの数を取得する
.where("条件")で条件にあうレコードを取得できます。
モデル名.where("条件").
今回、取得するのは Companyモデル の page_urlカラムです。Company.where(page_url).
さらに、page_urlカラム内に入力したデータと一致するものを検索にかけたいので、
下記のように付け加えます。Company.where(page_url: "#{f}").
#{f}は変数内の文字列を表しています。
変数fは以下の場所で定義しており、中身はこのようになっています。require 'rubygems' require 'mechanize' def create f = (params[:wiki_url]) <= ココです。 以下、省略....
page_urlカラム内で検索をかけた結果、いくつあったか判定するために
countメソッドを追加します。Company.where(page_url: "#{f}").count取得したデータの数を条件にかけて1以上(データがあるか)判定する
1以上、つまり入力したデータと同じURLの数を数えて1以上あるか確認します。
Company.where(page_url: "#{f}").count >= 11以上(データがあり)ならば、受付けない
おまけ
アプリに反映させているので、是非遊んでみてください。
Unsung:hero
- 投稿日:2020-09-27T10:35:59+09:00
Rails Tutorial 第14章 完了、全部で 155.5時間かかりました。
2020/9/13 0.5時間
14.1.1完了です。
2020/9/14 0.5時間
14.1.2.1途中です。
2020/9/15 0.5時間
14.1.2完了です。
2020/9/16 0.5時間
14.8途中です。
2020/9/17 0.5時間
14.1.4完了です。
2020/9/18 0.5時間
14.1.5完了です。
2020/9/19 2.0時間
14.2.1完了し、演習 14.2.2.2の途中です。
2020/9/20 2.0時間
14.2.2完了し、14.27の途中です。
2020/9/21 2.0時間
14.2.3-4完了し、演習16.2.5.1の途中です。
2020/9/22 2.0時間
14.2.5-14.3.1完了です。
2020/9/24 0.5時間
14.44の途中です。
9/23は朝早く出社だったのでできませんでした。夜帰宅した後は疲れていてやる気がでないです。朝にやるのがよいと改めて感じました。2020/9/25 0.5時間
演習14.3.2.3の途中です。
INNER JOINは意味は分かりますが、SQL文で書かれているのは初めて見ました。データベースの知識が昔のままなのだからかと思います。2020/9/26 2.0時間
14.3完了です。
2020/9/27 0.5時間
14章を完了です。
所要時間は14.5時間です。所要時間まとめ
1 章 22.5 時間
2 章 3.5 時間
3 章 5.5 時間
4 章 7.5 時間
5 章 7 時間
6 章 8 時間
7 章 10 時間
8 章 6 時間
9 章 11.5 時間
10 章 13 時間
11 章 16.5 時間
12 章 11 時間
13 章 19 時間
14 章 14.5 時間
計 155.5 時間 でした。1章は環境設定で大変時間がかかりました。
今後
14.4.1に、拡張機能の課題がありましたので、これからやってみたいと思います。
- 投稿日:2020-09-27T10:21:43+09:00
関連付しているモデルのレコードが消せずハマった
作業環境
Rails '5.2.3'
Ruby '2.7.1'
PostgreSQL何にハマったか
レビュー共有アプリ作成中にローカル環境で動作確認中、登録されているitemを削除しようとしましが
ActiveRecord::InvalidForeignKey in ItemsController#destroy
PG::ForeignKeyViolation: ERROR: update or delete on table "reviews" violates foreign key constraint "fk_rails_5350d1b47c" on table "comments" DETAIL: Key (id)=(6) is still referenced from table "comments". : DELETE FROM "reviews" WHERE "reviews"."id" = $1
というエラー、、。
on table "comments"
ってコメントテーブルなんてないぞ。と頭を悩ませていましたが、アプリ作成当初にreviewに対してコメント機能を作ろうと考えていたがreviewに対して一方的なコメント機能なんて必要ないのでは?と考え実装をやめたという過去があったのです(忘れていた)。
ですが、commentsテーブルは消したものと思いこんでおりハマってしまったのですねハマった背景
レビュー共有アプリをRailsで作成中、itemが複数のreviewを持っているという関係です。
item.rbhas_many :category_items, dependent: :destroy has_many :categories, through: :category_items has_many :reviews, dependent: :destroy has_many :favorites, dependent: :destroy accepts_nested_attributes_for :category_itemsreview.rbbelongs_to :user belongs_to :item has_many :notifications, dependent: :destroyエラーの原因は何か考える
ここからは初学者の考えた考察です。違っていたらご指摘いただけると幸いです。
itemを削除しようとすると同時にitemに関連したreviewも削除されます。
has_many :reviews, dependent: :destroy
これ。
で今回は、item削除→review消える→commentテーブルが残っている上にreview.rb(省略) has_many :comments, dependent: :destroyという記述がないからreview消せない、よってitemも消せないという現象かと考えました。
ではこのエラーにどう向き合うか
まずはcommentsテーブル本当に残っているのかという確認からですよね。
rails db:schema:dump
でdb/schema.rbを更新。
その後schema.rbを見てみるとschema.rbcreate_table "comments", force: :cascade do |t| (省略) endcommentsいた、、。不要なテーブルは消しましょう。
commentsテーブル削除
migrattionファイルを作成し、以下を記述して
def change drop_table :comments endrails db:migrateでさようなら。これでitem共にreviewの削除ができるようになりました!!
まとめ
たったこれだけで長々と書きましたが今回の件で得た教訓としては、機械は裏切らない、疑うのはまず自分であるということですね。思い込みはよくないです。
初投稿なので拙いところが多いですが、誰かの助けになればと思います。
- 投稿日:2020-09-27T10:03:52+09:00
【地図表示】Google Maps JavaScript APIとGeocoding APIを用いてユーザーが登録した住所から地図を表示する!
概要
Google Maps JavaScript APIとGeocoding APIを用いてユーザーが登録した住所から投稿詳細ページに地図を表示した時のことを備忘録として記録します。
環境
・ruby '2.5.7'
・rails '5.2.3'前提
・Google MapsのAPIキーを取得済であること
・投稿モデル(ここではDatespotモデル)に住所(adress)カラムがあること過程
1.投稿詳細ページの作成
投稿詳細ページは、各自の仕様に合わせて作成してください。
views/show.html.erb<div class="container"> <div class="row"> (省略) <div class="col-md-8"> <h2 class="datespot-name"><%= @datespot.name %></h2> <div class="datespot-info"> (省略) <h4 id="address">【住所】<%= @datespot.address %></h4> (省略) </div> </div> </div> </div> <%= render "map-show" %>2.地図を表示するビューを作成
地図を表示するビューを作成します。
views/_map-show.html.erb<div class="map-container"> <div class="map_wrapper"> <div id="map" class="map"></div> </div> </div> <script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['GOOGLE_MAP_API_KEY']%>&callback=initMap"></script>取得したAPIキーは、環境変数に入れておきましょう。
stylesheets/custom.scss#map{ height: 310px; width: 550px; }地図の大きさを明示的に指定しないと表示されないので、必ず指定しましょう。
3.コールバック関数を定義する
2.で記載したコールバック関数を定義します。
javascripts/map-show.jsfunction initMap() { //地図を表示する領域の div 要素のオブジェクトを変数に代入 var target = document.getElementById('map'); //マーカーのタイトル var title = $('.datespot-name').text(); //HTMLに記載されている住所の取得 var address = document.getElementById('address').textContent; //ジオコーディングのインスタンスの生成 var geocoder = new google.maps.Geocoder(); //geocoder.geocode() にアドレスを渡して、コールバック関数を記述して処理 geocoder.geocode({ address: address }, function(results, status){ //ステータスが OK で results[0] が存在すれば、地図を生成 if (status === 'OK' && results[0]){ //マップのインスタンスを変数に代入 var map = new google.maps.Map(target, { //results[0].geometry.location に緯度・経度のオブジェクトが入っている center: results[0].geometry.location, zoom: 15 }); //マーカーの生成 var marker = new google.maps.Marker({ position: results[0].geometry.location, map: map, animation: google.maps.Animation.DROP }); //取得した座標の生成 var latlng = new google.maps.LatLng(results[0].geometry.location.lat(), results[0].geometry.location.lng()); //情報ウィンドウに表示するコンテンツを作成 var content = '<div id="map_content"><p>' + title + '<br/>' + address + '<br/><a href="https://maps.google.co.jp/maps?q=' + latlng + '&iwloc=J" target="_blank" rel="noopener noreferrer">Googleマップで見る</a></p></div>'; //情報ウィンドウのインスタンスを生成 var infowindow = new google.maps.InfoWindow({ content: content, }); //marker をクリックすると情報ウィンドウを表示(リスナーの登録) google.maps.event.addListener(marker, 'click', function() { //第2引数にマーカーを指定して紐付け infowindow.open(map, marker); }); }else{ //ステータスが OK 以外の場合や results[0] が存在しなければ、アラートを表示して処理を中断 alert("住所から位置の取得ができませんでした。: " + status); return; } }); }
var target = document.getElementById('map');
は、
views/map-show.html.erbの<div id="map" class="map"></div>
を参照しています。
var title = $('.datespot-name').text();
は、
views/show.html.erbの<h2 class="datespot-name"><%= @datespot.name %></h2>
を参照しています。
var address = document.getElementById('address').textContent;
は、
views/show.html.erbの<h4 id="address">【住所】<%= @datespot.address %></h4>
を参照しています。結果
これで、ユーザーが登録した住所から投稿詳細ページに地図を表示できました!
参考
- 投稿日:2020-09-27T09:10:18+09:00
VPSのRailsに独自ドメインを適用し、SSL(https)化する(CentOS 8.2 / Nginx)
*この記事は自分のブログに投稿していた内容からの転載です。日頃、Qiitaの記事には大変お世話になっているため、私のブログ記事の中でもよくアクセスされる(=つまり参考にされていると思われる)ページをQiitaに投稿してわずかばかりでも恩返しとなればと思っています。
なお、前回投稿した以下の記事から連続して作業している状態です。ただし、前回の記事と直接的な繋がりはないため、本記事の作業だけを独立して実施しても大丈夫です。
「ConoHa VPS(CentOS 8.2)に Rails 6 + PostgreSQL + Nginx + Unicorn + Capistrano でデプロイする」
以下、早速スタートです。
CentOS(8.2)とNginxの環境を前提としています。作業開始時点は
http://IPアドレス
を入力するとRailsのアプリが表示されるようになっている状態が前提です。また、お名前ドットコムですでに独自ドメインを取得している想定で書いています(どこのレジストラから取得していてもあまり関係はないですが)。
ではさっそく、以下手順です。
1.独自ドメインを適用する
1−1.お名前ドットコムのDNS設定を変更する
お名前ドットコムのDNSレコード設定のページで以下2つを追加。例としてドメインを
example.com
にしている。
ホスト名 TYPE TTL VALUE example.com
A 3600 サーバのIPアドレス(xxx.xxx.xxx.xxx) www.example.com
CNAME 3600 example.com 上記実施後、レコード設定は下記となっているはず。
ホスト名 TYPE TTL VALUE example.com
NS 86400 01.dnsv.jp example.com
NS 86400 02.dnsv.jp example.com
NS 86400 03.dnsv.jp example.com
NS 86400 04.dnsv.jp example.com
A 3600 サーバのIPアドレス(xxx.xxx.xxx.xxx) www.example.com
CNAME 3600 example.com 設定が反映され次第、
example.com
およびwww.example.com
にアクセスすると、サーバにアクセスしに行くように変わっている。しかし、この状態で
example.com
にアクセスしてみると、Nginxのページが表示されてしまう。Nignxの設定も変更する必要がある。*メモ:DNS設定について
ホスト名 TYPE TTL VALUE example.com
A 3600 サーバのIPアドレス(xxx.xxx.xxx.xxx) www.example.com
CNAME 3600 example.com 上記が意味しているのは、
example.com
のIPアドレスはxxx.xxx.xxx.xxx
www.example.com
はexample.com
の別名ということで、つまりどちらを入力しても
xxx.xxx.xxx.xxx
にアクセスするようになる。ここで、CNAME(別名)の設定を使わずに、
ホスト名 TYPE TTL VALUE example.com
A 3600 サーバのIPアドレス(xxx.xxx.xxx.xxx) www.example.com
A 3600 サーバのIPアドレス(xxx.xxx.xxx.xxx) 上記としても動きは全く同じになるが、意味合い上、CNAMEを使って設定するのが良い。
1−2.Nginxの設定を変更する
サーバに接続しrootユーザにスイッチした後、以下で設定ファイルを開く。
vim /etc/nginx/conf.d/アプリケーション名.conf下記のような感じになっているはず。
/etc/nginx/conf.d/アプリケーション名.confupstream unicorn_アプリケーション名 { server unix:/var/www/アプリケーション名/current/tmp/sockets/unicorn.sock; } server { listen 80; server_name サーバのIPアドレス; root /var/www/アプリケーション名/current/public; access_log /var/log/nginx/アプリケーション名_access.log; error_log /var/log/nginx/アプリケーション名_error.log; location / { try_files $uri @unicorn; } location @unicorn { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://unicorn_アプリケーション名; } }上記のserver_nameのIPアドレスを、下記の通り
example.com
に変更する。/etc/nginx/conf.d/アプリケーション名.confserver_name example.com;変更後、設定ファイルに問題ないかをテスト。
nginx -t
問題なければ、Nginxを再起動する。
systemctl restart nginxその後、
http://example.com
にアクセスすると、アプリのページが表示されるようになっている。ここで、
http://IPアドレス
でアクセスすると、先ほどまではアプリが表示されていたのが、今度はNginxのページが表示されるようになっている。これは、先の設定ファイルを書き換えたことで、IPアドレスの記載がなくなった結果。もう一度、Nginxの設定ファイルに手を加え、下記の動きを実現するように変更する。
example.com
にアクセスするとexample.com
のページが開くwww.example.com
にアクセスすると(301リダイレクトで)example.com
のページが開くIPアドレス
にアクセスすると(301リダイレクトで)example.com
のページが開くつまり、(www無しの)
example.com
にすべて片寄せするという動き。再度ファイルを開き、
vim /etc/nginx/conf.d/アプリケーション名.conf下記の通り設定ファイルのserver部分を更新する。
/etc/nginx/conf.d/アプリケーション名.confserver { listen 80; server_name example.com; root /var/www/アプリケーション名/current/public; access_log /var/log/nginx/アプリケーション名_access.log; error_log /var/log/nginx/アプリケーション名_error.log; location / { try_files $uri @unicorn; } location @unicorn { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://unicorn_アプリケーション名; } } server { listen 80; server_name www.example.com サーバのIPアドレス; return 301 http://example.com$request_uri; }serverの記述をもう一つ追加し、server_nameには、
www.example.com
およびサーバのIPアドレス
を併記(両者の間には半角スペースを入れること)、そして301リダイレクトでexample.com
に向けている。$request_uriというのはその時のページを指している変数で、例えば、
www.example.com/blogs
にアクセスしたら、example.com/blogs
にリダイレクトしている。変更後、忘れずに設定内容テスト、問題なければNginxを再起動。
nginx -t
systemctl restart nginxこれでドメインでアクセスできるようにする設定は終わり。
2.SSL(https)化する
2−1.https接続を許可する(443番ポートを開放)
httpsを許可するために、443番ポートを開放する。
まず、firewalldの設定状況を確認してみる。
firewall-cmd --list-all
services: cockpit dhcpv6-client http ssh となっていることを確認。httpとsshが許可されている。
下記を実行。
firewall-cmd --add-service=https --permanentfirewalldをリロードして設定反映。
firewall-cmd --reload
変更確認のため再び実行。
firewall-cmd --list-all
services: cockpit dhcpv6-client http https ssh となっており、httpsが追加されている。
2−2.Certbotをインストールする
Certbotは、Let’s EncryptでのSSL証明書から設定までを自動で行ってくれるツール。
Certbotと、NginxでCertbotを使う時に必要なプラグインをインストール。
dnf -y install certbot python3-certbot-nginxインストールできたか確認。
certbot --version
certbot 1.7.0などと表示されればOK.
2−3.SSL証明書の取得と設定を行う
Certbotを使ってSSL証明書の取得と設定を行う。
certbot --nginx
対話形式で設定内容に関する質問をされるので回答する。下記に質問内容と回答例をすべて載せておく。
Saving debug log to /var/log/letsencrypt/letsencrypt.log Plugins selected: Authenticator nginx, Installer nginx Enter email address (used for urgent renewal and security notices) (Enter 'c' to cancel): xxxxxxxx@gmail.com #メールアドレスを入力 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must agree in order to register with the ACME server at https://acme-v02.api.letsencrypt.org/directory - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (A)gree/(C)ancel: (A)gree/(C)ancel: A #A(同意)を選択 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Would you be willing, once your first certificate is successfully issued, to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - (Y)es/(N)o: N # N(いいえ)を選択 Which names would you like to activate HTTPS for? - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1: example.com 2: www.example.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Select the appropriate numbers separated by commas and/or spaces, or leave input blank to select all options shown (Enter 'c' to cancel): 1 # exmaple.jpをHTTPS化 Obtaining a new certificate Performing the following challenges: http-01 challenge for example.com Waiting for verification... Cleaning up challenges Deploying Certificate to VirtualHost /etc/nginx/conf.d/アプリケーション名.conf Redirecting all traffic on port 80 to ssl in /etc/nginx/conf.d/アプリケーション名.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations! You have successfully enabled https://example.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - IMPORTANT NOTES: - Congratulations! Your certificate and chain have been saved at: /etc/letsencrypt/live/example.com/fullchain.pem Your key file has been saved at: /etc/letsencrypt/live/example.com/privkey.pem Your cert will expire on 2020-XX-XX. To obtain a new or tweaked version of this certificate in the future, simply run certbot again with the "certonly" option. To non-interactively renew *all* of your certificates, run "certbot renew" - Your account credentials have been saved in your Certbot configuration directory at /etc/letsencrypt. You should make a secure backup of this folder now. This configuration directory will also contain certificates and private keys obtained by Certbot so making regular backups of this folder is ideal. - If you like Certbot, please consider supporting our work by: Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate Donating to EFF: https://eff.org/donate-le証明書を取得、設定できていることを確認する。以下を実行。
certbot certificatesexample.comドメインの証明書情報が表示されればOK.
これでHTTPSで接続できるようになった。
https://example.com
でアクセスできることを確認する。また証明書の期限は3ヶ月間のため、そのままだと3ヶ月後に失効する。クーロンのジョブを追加し、定期的に証明書を自動更新させることで実質的に永続利用できるようにする。以下を実行。
echo "0 0,12 * * * root python3 -c 'import random; import time; time.sleep(random.random() * 3600)' && certbot renew -q" | sudo tee -a /etc/crontab > /dev/nullクーロンが設定されたことを確認するため、ファイルの内容を出力。
cat /etc/crontab
出力結果の最後に、「0 0,12 * * * root python3 -c ‘import random; import time; time.sleep(random.random() * 3600)’ && certbot renew -q」があることを確認。
クーロンのデーモンを再起動して設定を反映しておく。
systemctl restart crond最後に、確認のためドライラン(=テスト)を実施してみる。
certbot renew --dry-run
結果の中に、「Congratulations, all renewals succeeded.」と書いてあればOK.
2−4.Nginxの設定を変更する
ここで、Nginxの設定ファイルを確認する。
vim /etc/nginx/conf.d/アプリケーション名.confそうすると、内容のうちserver部分が自動的に変わっていることが確認できる。これはCertbotが更新したため。以下は更新された後の例。
/etc/nginx/conf.d/アプリケーション名.confserver { server_name example.com; root /var/www/アプリケーション名/current/public; access_log /var/log/nginx/アプリケーション名_access.log; error_log /var/log/nginx/アプリケーション名_error.log; location / { try_files $uri @unicorn; } location @unicorn { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://unicorn_アプリケーション名; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot } server { listen 80; server_name www.example.com サーバのIPアドレス; return 301 https://example.com$request_uri; } server { if ($host = example.com) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; server_name example.com; return 404; # managed by Certbot }ただし、上記のままだと、
https://www.example.com
や、https://IPアドレス
、でアクセスされた時にリダイレクトできていなかったり、インデントや記述位置をきれいに整えたかったりするので、以下の形に再整備した。なお、「listen 443 ssl;」と1行で書くのと、「listen 443;」「ssl on;」と2行で書くのは同じ意味。
/etc/nginx/conf.d/アプリケーション名.confserver { listen 443; ssl on; server_name example.com; root /var/www/アプリケーション名/current/public; access_log /var/log/nginx/アプリケーション名_access.log; error_log /var/log/nginx/アプリケーション名_error.log; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot location / { try_files $uri @unicorn; } location @unicorn { proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://unicorn_アプリケーション名; } } server { listen 443; server_name www.example.com サーバのIPアドレス; return 301 https://example.com$request_uri; } server { listen 80; server_name example.com www.example.com サーバのIPアドレス; return 301 https://example.com$request_uri; }これで、以下の場合は全て、
https://example.com
に301リダイレクトされるようになった。
https://www.example.com
https://IPアドレス
http://example.com
http://www.example.com
http://IPアドレス
設定を反映させるため、忘れずにテスト&Nginxを再起動させておく。
nginx -t
systemctl restart nginx3.参考
Railsのconfig/environments/production.rbには以下のオプションがある。
config/environments/production.rb# config.force_ssl = true
デフォルトでは、上記の通りコメントアウトされているはず。
このオプションのコメントアウトを解除して有効にすると、アプリにhttpでアクセスされた時にはhttpsにリダイレクトさせることができるようになる。つまり、Nginxで設定した内容と同じことが実現できるということ。
ただし、本来こういった処理はWebサーバ(Nginx)にて捌くものだと思うので、今回のようにWebサーバを使っている場合は、上記のRails機能は利用せず、Webサーバ側で対処しておく。
以上
無事にこれで終わり。
補足
記事をお読みいただきありがとうございました。
元のブログ記事へのリンクを以下に貼っておきますが、記載内容は本記事と同じため、特に参照いただく必要はないですし、むしろ、Qiitaのシンタックスハイライト機能があるため、Qiitaで記事を読んだ方がわかりやすいはずです。
knmts.com | VPSのRailsに独自ドメインを適用し、SSL(https)化する(CentOS 8.2 / Nginx)
- 投稿日:2020-09-27T01:41:43+09:00
【Mysql2】Mysql2::Error: Specified key was too long; max key length is 767 bytes→文字コードを適切にすることで解決する
ざっくり概要について
このエラーは「Mysql2に、格納できる文字データは、767バイトまでだよ!今のままでは、Mysqlにマイグレーションできないよ!」という意味で理解しました。
そのエラーを踏まえて、この記事では、以下2つの方法を実践しました。
- database.ymlが「utf8mb4」のまま、「文字制限をかけるファイルを新規作成」する方法 (@terufumi1122さんの記事のおかげで解決できました)
- 「文字制限をかける新規ファイル」を作成せずに、database.ymlを「utf8」に書き直す方法(この方法はやらないほうがいいと学びました)
その上で一番大事だなと思ったことは、 MySQLを使用する際、
「絵文字を使えるかどうか、要件定義の際にしっかり決める」ということです。では、詳細にいきましょう!!!
エラー内容について
「Mysql2::Error: Specified key was too long; max key length is 767 bytes」というエラーが発生。
【ターミナル】 〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:reset Dropped database 'devise_app_development' Dropped database 'devise_app_test' Created database 'devise_app_development' Created database 'devise_app_test' == 20200925212154 DeviseCreateUsers: migrating ================================ -- create_table(:users) -> 0.0174s -- add_index(:users, :email, {:unique=>true}) rails aborted! StandardError: An error has occurred, all later migrations canceled: Mysql2::Error: Specified key was too long; max key length is 767 bytes /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:39:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Caused by: ActiveRecord::StatementInvalid: Mysql2::Error: Specified key was too long; max key length is 767 bytes /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:39:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Caused by: Mysql2::Error: Specified key was too long; max key length is 767 bytes /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:39:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Tasks: TOP => db:migrate:reset => db:migrate (See full trace by running task with --trace)エラーが発生した状況について
環境
- Rails 6.0.0を使用
- データベースはMysql2を使用(アプリケーションはSequel Pro)
- deviseというgemを使用(そのためモデルは、rails g devise userで作成していた)
- deviseでは、マイグレーションファイルにおいて、string型(例:t.string :email)にあたるカラムを作成しようとしていた
- マイグレーションファイルを作成し、rails db:migrate を実行したところ、エラー発生
初めは状況を理解できず、下記のことをさらに行ってしまった
- rails db:migrate:statusで確認 → downになっている
- 再度、rails db:migrate → エラー
- rails db:migrate:reset → エラー ※上記はこの画面!
解決方法に行く前に、そもそも文字コードって何?「utf8」と「utf8mb4」って何?byteって何?
その概念が分からず、自分なりにですが、以下のように理解しました。
文字コードとは、「あ」だったら1番、「い」だったら2番というように、文字に対して割り振っているコードのこと。(実際には進数など使って、もっと複雑そうですが・・・)
「utf8」とは文字コードの中でも、世界で最も普及している文字コード。「utf8」は1~4バイトで文字を表現するが、MySQLでは3バイトの文字までしか扱えない。
「utf8mb4」とは、データベースMySQLで扱うための文字コード。Mysqlにおいて、絵文字などは「utf8」の4バイトに当たるので、「utf8mb4」でないと絵文字が扱えない。
MySQLにおいて、保存できる文字数は「255文字」まで。
こちらから引用させていただきました
- 「utf8」と「utf8mb4」の違いについて
- 文字コードとは
- 単一カラムインデックスの最大キー長は767バイトまで
- MysqlにおけるVARCHAR型とは
- Railsにおけるstringのデフォルト値255文字まで
上記を組み合わせて、理解したこと
つまり、MySQLにおいて、
- utf8は文字を扱う文字コード、utf8bm4は絵文字を扱う文字コード
可能な文字数は、
「utf8」は「3バイト」使うので、「767バイト➗3バイト=255文字まで」
「utf8mb4」は「4バイト」使うので、「767バイト➗4バイト=191文字まで」
stringにおけるデフォルト値が255文字なので、
「3バイト❌255文字=765バイト」(utf8)
「4バイト❌255文字=1020バイト」(utf8bm4)MySQLで保存できる767byte超えてるよ!!今回はこの状態に陥っており、エラーになっている
ということが、わかりました。
その上で、エラー解決方法へ
さて、エラー内容が起きた原因がすっきりしたところで、肝心の解決方法についてです。
冒頭で触れた通り、
まずは、上記方法で、解決しました!!!(ありがとうございます)
解決方法について、概要を話すと、
【問題点】
「4バイト❌255文字=1020バイト」(utf8bm4)MySQLで保存できる767byte超えてるよ!!今回はこの状態に陥っており、エラーになっている【それに対する考え方】
じゃあ、767byte超えないように、(「4バイト❌191文字=764バイト」)文字数の上限を191文字に設定しようよ!という方法です。(絵文字が使えるように、utf8bm4はそのまま活かす方法)以下のようなmysql.rbをconfig/initializer配下に新規作成します。(以下のコード含め、@terufumi1122さんの記事から引用しています。)
config/initializer/mysql.rb require 'active_record/connection_adapters/abstract_mysql_adapter' module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter NATIVE_DATABASE_TYPES[:string] = { :name => "varchar", :limit => 191 } end end end上記ファイルを入力した後に、
【ターミナル】 rails db:migrateをしたところ、無事にマイグレートされました!!!
(MySQLであるSequelProを見たところ、カラムができていました。)そこから、考察したこと
さて、無事に解決したのですが、問題点に対して、767byteを超えない方法について、下記のような考え方もできないかなと思いました。
【問題点】
「4バイト❌255文字=1020バイト」(utf8bm4)MySQLで保存できる767byte超えてるよ!!今回はこの状態に陥っており、エラーになっている【それに対する考え方】
じゃあ、もし、そのアプリケーションで絵文字を使わないのであれば、「3バイト❌255文字=765バイト」(utf8)に変更するのはどうか。つまり、191文字以下にするファイルを作成するのではなく、「config>database.yml」に記載している、文字コードを、「utf8bm4」から「utf8」に書き換えて、マイグレーションすればいいのでは!?と思いました。
ここから実践する訳ですが、結論から言うと、「モデルから作り直しになる(マイグレーションの書き換えでは済まない)」ということを学びました。(つまり、面倒で大変です。)
「マイグレーションの書き換えで済まない」とは、「rails db:migrate」と共に、(指示をしていない)謎のマイグレーションファイルが出現し、そのマイグレーションファイルは不要なので、架空ファイルに変更して、ドロップやリセットを行いますが消そうとしても消えません。
「rails d devise user」でいったんモデルを消して、「rails g devise user」で作り直してやっと、エラーの出ない「utf8」文字コードのアプリケーションができました。
いざ、実践!(先ほどのエラー解決した状況から、上記考察を実践しています)
①いったんロールバック
〇〇@〇〇noMacBook-Air devise_app % rails db:rollback == 20200925212154 DeviseCreateUsers: reverting ================================ -- remove_index(:users, {:column=>:reset_password_token}) -> 0.0099s -- remove_index(:users, {:column=>:email}) -> 0.0074s -- drop_table(:users) -> 0.0040s == 20200925212154 DeviseCreateUsers: reverted (0.0250s) =======================②マイグレーションの状況を確認 (downになっているので、よし!)
〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- down 20200925212154 Devise create users③マイグレーションを実行(エラーを再現できたため、よし!)
〇〇@〇〇noMacBook-Air devise_app % rails db:migrate == 20200925212154 DeviseCreateUsers: migrating ================================ -- create_table(:users) rails aborted! StandardError: An error has occurred, all later migrations canceled: Mysql2::Error: Table 'users' already exists /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:5:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Caused by: ActiveRecord::StatementInvalid: Mysql2::Error: Table 'users' already exists /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:5:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Caused by: Mysql2::Error: Table 'users' already exists /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:5:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Tasks: TOP => db:migrate (See full trace by running task with --trace)④config>database.ymlをutf8に書き直す
default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /tmp/mysql.sock⑤rails db:resetを実行(良い感じ!)
〇〇@〇〇noMacBook-Air devise_app % rails db:reset Dropped database 'devise_app_development' Dropped database 'devise_app_test' Created database 'devise_app_development' Created database 'devise_app_test' You have 1 pending migration: 20200925212154 DeviseCreateUsers Run `rails db:migrate` to update your database then try again.⑥rails db:migrateを実行(良い感じ!)
〇〇@〇〇noMacBook-Air devise_app % rails db:migrate == 20200925212154 DeviseCreateUsers: migrarails db:migrateting ================================ -- create_table(:users) -> 0.0109s -- add_index(:users, :email, {:unique=>true}) -> 0.0087s -- add_index(:users, :reset_password_token, {:unique=>true}) -> 0.0085s == 20200925212154 DeviseCreateUsers: migrated (0.0283s) =======================⑥rails db:migrate:status(おや? 000のファイルってなんだ?)
〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- up 000 ********** NO FILE ********** up 20200925212154 Devise create users⑦「**NO FILE*」になってしまっているため、架空のファイル(Sample)にマイグレーションを修正して、ロールバックするも、000ファイルがdownにならない
〇〇@〇〇noMacBook-Air devise_app % rails db:rollback STEP=2 〇〇@〇〇noMacBook-Air devise_app % 〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- up 000 Sample down 20200925212154 Devise create users⑦resetしてステータスを確認するも、000ファイルがdownにならない
〇〇@〇〇noMacBook-Air devise_app % rails db:reset Dropped database 'devise_app_development' Dropped database 'devise_app_test' Created database 'devise_app_development' Created database 'devise_app_test' You have 1 pending migration: 20200925212154 DeviseCreateUsers Run `rails db:migrate` to update your database then try again. 〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- up 000 ********** NO FILE ********** down 20200925212154 Devise create users⑧モデルを削除する
〇〇noMacBook-Air devise_app % rails d devise user Running via Spring preloader in process 10337 invoke active_record remove db/migrate/20200925212154_devise_create_users.rb remove app/models/user.rb invoke test_unit remove test/models/user_test.rb remove test/fixtures/users.yml route devise_for :users⑨モデルを作り直す
〇〇noMacBook-Air devise_app % rails g devise user Running via Spring preloader in process 10485 invoke active_record create db/migrate/20200926073222_devise_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml insert app/models/user.rb route devise_for :users⑩マイグレーションを行う
〇〇noMacBook-Air devise_app % rails db:migrate == 20200926073222 DeviseCreateUsers: migrating ================================ -- create_table(:users) -> 0.0275s -- add_index(:users, :email, {:unique=>true}) -> 0.0434s -- add_index(:users, :reset_password_token, {:unique=>true}) -> 0.0108s == 20200926073222 DeviseCreateUsers: migrated (0.0821s) =======================11ステータスを確認(やっとできた!!!!!)
〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- up 20200926073222 Devise create users以上です。
終わりに
考察を実践してみましたが、
database.ymlの記載情報を変更するには、ロールバッグでは済まず、モデル作り直しになることがわかりました。今回はモデルに大した内容を書いていなかったため、影響なしでしたが、
もしこれが、モデル含めたテーブル・カラム・レコード・マイグレーションなどの作り直しとなると手間がかかり大変ですよね。utf8含めて、database.ymlの編集は、(一番初めに「rails db:create」を行う前に)しっかり要件定義をした上で、設定することが大事だと学びました。
見ていただいて、有り難うございました。
もし謎ファイルの原因わかる方や、誤っていることを載せていたら、ぜひ教えてください。
- 投稿日:2020-09-27T01:41:43+09:00
【Mysql2】Mysql2::Error: Specified key was too long; max key length is 767 bytes→文字制限をかけるファイルを新規作成することで解決する
ざっくり概要について
このエラーは「Mysql2に、格納できる文字データは、767バイトまでだよ!今のままでは、Mysqlにマイグレーションできないよ!」という意味で理解しました。
そのエラーを踏まえて、この記事では、以下2つの方法を実践しました。
- database.ymlが「utf8mb4」のまま、「文字制限をかけるファイルを新規作成」する方法 (@terufumi1122さんの記事のおかげで解決できました)
- 「文字制限をかける新規ファイル」を作成せずに、database.ymlを「utf8」に書き直す方法(この方法はやらないほうがいいと学びました)
その上で一番大事だなと思ったことは、 MySQLを使用する際、
「絵文字を使えるかどうか、要件定義の際にしっかり決める」ということです。では、詳細にいきましょう!!!
エラー内容について
「Mysql2::Error: Specified key was too long; max key length is 767 bytes」というエラーが発生。
【ターミナル】 〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:reset Dropped database 'devise_app_development' Dropped database 'devise_app_test' Created database 'devise_app_development' Created database 'devise_app_test' == 20200925212154 DeviseCreateUsers: migrating ================================ -- create_table(:users) -> 0.0174s -- add_index(:users, :email, {:unique=>true}) rails aborted! StandardError: An error has occurred, all later migrations canceled: Mysql2::Error: Specified key was too long; max key length is 767 bytes /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:39:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Caused by: ActiveRecord::StatementInvalid: Mysql2::Error: Specified key was too long; max key length is 767 bytes /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:39:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Caused by: Mysql2::Error: Specified key was too long; max key length is 767 bytes /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:39:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Tasks: TOP => db:migrate:reset => db:migrate (See full trace by running task with --trace)エラーが発生した状況について
環境
- Rails 6.0.0を使用
- データベースはMysql2を使用(アプリケーションはSequel Pro)
- deviseというgemを使用(そのためモデルは、rails g devise userで作成していた)
- deviseでは、マイグレーションファイルにおいて、string型(例:t.string :email)にあたるカラムを作成しようとしていた
- マイグレーションファイルを作成し、rails db:migrate を実行したところ、エラー発生
初めは状況を理解できず、下記のことをさらに行ってしまった
- rails db:migrate:statusで確認 → downになっている
- 再度、rails db:migrate → エラー
- rails db:migrate:reset → エラー ※上記はこの画面!
解決方法に行く前に、そもそも文字コードって何?「utf8」と「utf8mb4」って何?byteって何?
その概念が分からず、自分なりにですが、以下のように理解しました。
文字コードとは、「あ」だったら1番、「い」だったら2番というように、文字に対して割り振っているコードのこと。(実際には進数など使って、もっと複雑そうですが・・・)
「utf8」とは文字コードの中でも、世界で最も普及している文字コード。「utf8」は1~4バイトで文字を表現するが、MySQLでは3バイトの文字までしか扱えない。
「utf8mb4」とは、データベースMySQLで扱うための文字コード。Mysqlにおいて、絵文字などは「utf8」の4バイトに当たるので、「utf8mb4」でないと絵文字が扱えない。
MySQLにおいて、保存できる文字数は「255文字」まで。
こちらから引用させていただきました
- 「utf8」と「utf8mb4」の違いについて
- 文字コードとは
- 単一カラムインデックスの最大キー長は767バイトまで
- MysqlにおけるVARCHAR型とは
- Railsにおけるstringのデフォルト値255文字まで
上記を組み合わせて、理解したこと
つまり、MySQLにおいて、
- utf8は文字を扱う文字コード、utf8bm4は絵文字を扱う文字コード
可能な文字数は、
「utf8」は「3バイト」使うので、「767バイト➗3バイト=255文字まで」
「utf8mb4」は「4バイト」使うので、「767バイト➗4バイト=191文字まで」
stringにおけるデフォルト値が255文字なので、
「3バイト❌255文字=765バイト」(utf8)
「4バイト❌255文字=1020バイト」(utf8bm4)MySQLで保存できる767byte超えてるよ!!今回はこの状態に陥っており、エラーになっている
ということが、わかりました。
その上で、エラー解決方法へ
さて、エラー内容が起きた原因がすっきりしたところで、肝心の解決方法についてです。
冒頭で触れた通り、
まずは、上記方法で、解決しました!!!(ありがとうございます)
解決方法について、概要を話すと、
【問題点】
「4バイト❌255文字=1020バイト」(utf8bm4)MySQLで保存できる767byte超えてるよ!!今回はこの状態に陥っており、エラーになっている【それに対する考え方】
じゃあ、767byte超えないように、(「4バイト❌191文字=764バイト」)文字数の上限を191文字に設定しようよ!という方法です。(絵文字が使えるように、utf8bm4はそのまま活かす方法)以下のようなmysql.rbをconfig/initializer配下に新規作成します。(以下のコード含め、@terufumi1122さんの記事から引用しています。)
config/initializer/mysql.rb require 'active_record/connection_adapters/abstract_mysql_adapter' module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter NATIVE_DATABASE_TYPES[:string] = { :name => "varchar", :limit => 191 } end end end上記ファイルを入力した後に、
【ターミナル】 rails db:migrateをしたところ、無事にマイグレートされました!!!
(MySQLであるSequelProを見たところ、カラムができていました。)そこから、考察したこと
さて、無事に解決したのですが、問題点に対して、767byteを超えない方法について、下記のような考え方もできないかなと思いました。
【問題点】
「4バイト❌255文字=1020バイト」(utf8bm4)MySQLで保存できる767byte超えてるよ!!今回はこの状態に陥っており、エラーになっている【それに対する考え方】
じゃあ、もし、そのアプリケーションで絵文字を使わないのであれば、「3バイト❌255文字=765バイト」(utf8)に変更するのはどうか。つまり、191文字以下にするファイルを作成するのではなく、「config>database.yml」に記載している、文字コードを、「utf8bm4」から「utf8」に書き換えて、マイグレーションすればいいのでは!?と思いました。
ここから実践する訳ですが、結論から言うと、「モデルから作り直しになる(マイグレーションの書き換えでは済まない)」ということを学びました。(つまり、面倒で大変です。)
「マイグレーションの書き換えで済まない」とは、「rails db:migrate」と共に、(指示をしていない)謎のマイグレーションファイルが出現し、そのマイグレーションファイルは不要なので、架空ファイルに変更して、ドロップやリセットを行いますが消そうとしても消えません。
「rails d devise user」でいったんモデルを消して、「rails g devise user」で作り直してやっと、エラーの出ない「utf8」文字コードのアプリケーションができました。
いざ、実践!(先ほどのエラー解決した状況から、上記考察を実践しています)
①いったんロールバック
〇〇@〇〇noMacBook-Air devise_app % rails db:rollback == 20200925212154 DeviseCreateUsers: reverting ================================ -- remove_index(:users, {:column=>:reset_password_token}) -> 0.0099s -- remove_index(:users, {:column=>:email}) -> 0.0074s -- drop_table(:users) -> 0.0040s == 20200925212154 DeviseCreateUsers: reverted (0.0250s) =======================②マイグレーションの状況を確認 (downになっているので、よし!)
〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- down 20200925212154 Devise create users③マイグレーションを実行(エラーを再現できたため、よし!)
〇〇@〇〇noMacBook-Air devise_app % rails db:migrate == 20200925212154 DeviseCreateUsers: migrating ================================ -- create_table(:users) rails aborted! StandardError: An error has occurred, all later migrations canceled: Mysql2::Error: Table 'users' already exists /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:5:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Caused by: ActiveRecord::StatementInvalid: Mysql2::Error: Table 'users' already exists /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:5:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Caused by: Mysql2::Error: Table 'users' already exists /Users/〇〇/projects/devise_app/db/migrate/20200925212154_devise_create_users.rb:5:in `change' /Users/〇〇/projects/devise_app/bin/rails:9:in `<top (required)>' /Users/〇〇/projects/devise_app/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Tasks: TOP => db:migrate (See full trace by running task with --trace)④config>database.ymlをutf8に書き直す
default: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /tmp/mysql.sock⑤rails db:resetを実行(良い感じ!)
〇〇@〇〇noMacBook-Air devise_app % rails db:reset Dropped database 'devise_app_development' Dropped database 'devise_app_test' Created database 'devise_app_development' Created database 'devise_app_test' You have 1 pending migration: 20200925212154 DeviseCreateUsers Run `rails db:migrate` to update your database then try again.⑥rails db:migrateを実行(良い感じ!)
〇〇@〇〇noMacBook-Air devise_app % rails db:migrate == 20200925212154 DeviseCreateUsers: migrarails db:migrateting ================================ -- create_table(:users) -> 0.0109s -- add_index(:users, :email, {:unique=>true}) -> 0.0087s -- add_index(:users, :reset_password_token, {:unique=>true}) -> 0.0085s == 20200925212154 DeviseCreateUsers: migrated (0.0283s) =======================⑥rails db:migrate:status(おや? 000のファイルってなんだ?)
〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- up 000 ********** NO FILE ********** up 20200925212154 Devise create users⑦「**NO FILE*」になってしまっているため、架空のファイル(Sample)にマイグレーションを修正して、ロールバックするも、000ファイルがdownにならない
〇〇@〇〇noMacBook-Air devise_app % rails db:rollback STEP=2 〇〇@〇〇noMacBook-Air devise_app % 〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- up 000 Sample down 20200925212154 Devise create users⑦resetしてステータスを確認するも、000ファイルがdownにならない
〇〇@〇〇noMacBook-Air devise_app % rails db:reset Dropped database 'devise_app_development' Dropped database 'devise_app_test' Created database 'devise_app_development' Created database 'devise_app_test' You have 1 pending migration: 20200925212154 DeviseCreateUsers Run `rails db:migrate` to update your database then try again. 〇〇@〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- up 000 ********** NO FILE ********** down 20200925212154 Devise create users⑧モデルを削除する
〇〇noMacBook-Air devise_app % rails d devise user Running via Spring preloader in process 10337 invoke active_record remove db/migrate/20200925212154_devise_create_users.rb remove app/models/user.rb invoke test_unit remove test/models/user_test.rb remove test/fixtures/users.yml route devise_for :users⑨モデルを作り直す
〇〇noMacBook-Air devise_app % rails g devise user Running via Spring preloader in process 10485 invoke active_record create db/migrate/20200926073222_devise_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml insert app/models/user.rb route devise_for :users⑩マイグレーションを行う
〇〇noMacBook-Air devise_app % rails db:migrate == 20200926073222 DeviseCreateUsers: migrating ================================ -- create_table(:users) -> 0.0275s -- add_index(:users, :email, {:unique=>true}) -> 0.0434s -- add_index(:users, :reset_password_token, {:unique=>true}) -> 0.0108s == 20200926073222 DeviseCreateUsers: migrated (0.0821s) =======================11ステータスを確認(やっとできた!!!!!)
〇〇noMacBook-Air devise_app % rails db:migrate:status database: devise_app_development Status Migration ID Migration Name -------------------------------------------------- up 20200926073222 Devise create users以上です。
終わりに
考察を実践してみましたが、
database.ymlの記載情報を変更するには、ロールバッグでは済まず、モデル作り直しになることがわかりました。今回はモデルに大した内容を書いていなかったため、影響なしでしたが、
もしこれが、モデル含めたテーブル・カラム・レコード・マイグレーションなどの作り直しとなると手間がかかり大変ですよね。utf8含めて、database.ymlの編集は、(一番初めに「rails db:create」を行う前に)しっかり要件定義をした上で、設定することが大事だと学びました。
あとは、今回「767byte」を超えない方法を考察しましたが、@terufumi1122さんの記事には「767byte」を超えてもいいようにするともありました。
エラーを解決するには、アプリケーションに目的に沿って、いろんな方面から柔軟に見る必要があることと、その引き出しを増やすために「WHY」の視点が大事だと改めて感じました。
見ていただいて、有り難うございました。
もし謎ファイルの原因わかる方や、誤っていることを載せていたら、ぜひ教えてください。
- 投稿日:2020-09-27T00:17:58+09:00
Rails APIサーバー開発時に入れたGemの使い方 (rack-cors, rspec-rails, factory_bot_rails, rubocop)
前提
docker-composeを用いたRailsのAPIサーバー環境構築
この記事でGemについてはあまり触れなかったので続きGemfileの編集
下記のGemをインストールする。
... gem 'rack-cors' group :development, :test do ... gem 'rspec-rails', '~> 3.9' gem 'factory_bot_rails' end group :development do ... gem 'rubocop', require: false endrack-cors
postman使う時に必要。
やっておかないとCORS問題発生してエラー出る。config > initializers > cors.rb
を以下のように書くと、どこからでもAPI叩けるようになる。cors.rbRails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins '*' resource '*', headers: :any, methods: %i[get post put patch delete options head] end endrspec-rails, factory_bot_rails
モデルやコントローラーをgenerateするときに自動生成するファイルがあるのであらかじめ入れておいた方が良い。
rspec-rails GitHub
Rails 5ではrspec-railsの3系を使用する。(Rails 6では4~)bundler installしたら、以下で初期ファイル?インストール
rails generate rspec:install … create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rbまた、.rspecとapplication.rbに以下を追加
参照 RailsアプリへのRspecとFactory_botの導入手順
.rspec--color --require rails_helper --format documentationapplication.rbconfig.generators do |g| g.test_framework :rspec, view_specs: false, helper_specs: false, controller_specs: false, routing_specs: falseモデルのテストは自動生成されたが、
結合テストはRequest specで書くために手動でファイルを作った。spec > requests > hoge_api_spec.rb
のように名付けた。リクエストスペックは以下のような構成で書いた
hoge_spec.rbRSpec.describe 'HogeAPI' do describe 'POST #create' do # アクション名を示す context 'xxxな場合' do # 条件 before do # ダミーのデータを用意 FactoryBot.create(:hoge) ... end it 'yyする' do expect do # DBの変更を検知するときはくくる post '/hoge/create', params: { name: "Hoge" } end.to change(Hoge, :count).by(+1) and change(Table2, :count).by(0) expect(response.status).to eq(201) # ステータスコードの確認 end end参考
【rspec】Railsモデルテストの基本
【Rails】APIテストの書き方
RSpecを使ってAPIのテストを行う
Railsプロジェクトで、FactoryBotを用いたテストデータを作成する方法ダメだったapplication.rbの書き方
application.rbのgeneratorsはcontrollerを生成するためにcontrollerのテストファイルを生成しないようにしたいだけなので、
Everyday Rails - RSpecによるRailsテスト入門を参考にapplication.rb# NG config.generators do |g| g.test_framework :rspec, controller_specs: falseと最初は書いていたが、コントローラー生成は期待通りだったもののなぜかモデルのテストファイルがエラーで生成できなくなった。
rubocop
rubocopはVSCodeでFormatする時にも使うのでローカルにもインストールした。
ファイルの書き方が正しいかどうかを確認してくれる。
// 確認したい時 rubocop // 確認して直せるところは自動でなおすとき rubocop -aめっちゃ警告でるので、警告の内容をみて不要であればスキップするように設定する。
例
.rubocop.ymlAllCops: Exclude: # 除外したいファイルは Exclude に指定する。 - 'spec/*.rb' - 'db/schema.rb' - 'test/*' - 'config/**/*' - 'Gemfile' - 'bin/*' Style/FrozenStringLiteralComment: Enabled: false Style/Documentation: Enabled: false Style/StringLiterals: Enabled: false Metrics/BlockLength: Exclude: - 'spec/**/*' Metrics/MethodLength: Max: 30 Metrics/AbcSize: Max: 30 Style/AsciiComments: Enabled: falseAllCopsは全ての警告に共通する設定
参考