Ruby on Rails Docker エラー忘備録
こんばんは
ポートフォリオ作成時に発生したエラー及び、予期せぬ動作に対して、どのようにアプローチしたかを記述しています。
つまり、「エラーとその解決方法」というよりかは、動作をしただけです。
ベストプラクティスではないでしょうし、前提としての実装がおかしいということもあります。
私のような未経験の方は斜にかまえてみてもらえると幸いです。
環境
Ruby: 3.1.2
Ruby on Rails: 7.0.3.1
DB: mysql:8.0.29-oracle
docker, docker-compose
認証
Filter chain halted as :authenticate_api_v1_user! rendered or redirected Completed 401 Unauthorized in
- access_token,uid,client情報がヘッダーに必要だったけどそれが付与されていなかった
param is missing or the value is empty
- content/typeのapplication/jsonを指定していなかった
Tableをdropしたあとにrspecを実行すると ActiveRecord::StatementInvalid:
Mysql2::Error: Table 'app_test.images' doesn't exist
- /model以下に削除したテーブルのモデルがあった。つまり、既存のテーブルのモデルと関連付けされたままだった。モデルとその関連付けを削除して解決
@buf=["{"success":false,"errors":["'test1@example.com' に確認用のメールを送信しました。メール内の説明を読み、アカウントの有効化をしてください。"]}"]
- deviseのconfirmbleを有効にしてサインアップ時のメール認証を有効にしている場合、認証をしていないアカウントでログインしようとすると上記のエラーがでる
NoMethodError: undefined method `downcase' for nil:NilClass
- devise_token_authをダウンロードしてspecを実行するとエラーがでた。
- initializerにauthorization: "authorization" を追加して解決
https://github.com/lynndylanhurley/devise_token_auth/issues/1540
ActiceModel, ActiveRecord
Completed 500 Internal Server Error in 341ms (ActiveRecord: 60.4ms | Allocations: 6168)
- Railsのコードでなんらかの間違いがある
- 下記の場合だと
hoge.id
のhogeがnilになっている
NoMethodError undefined method `id' for nil:NilClass
ActionView::Template::Error (The asset "rails_admin/application.css" is not present in the asset pipeline.
- rails-adminなど、assetsを本番環境で使う必要がある場合、
precompile
する必要がある。 bundle exec rails assets:precompile RAILS_ENV=production
- 上記のようにローカルでアセットをプリコンパイルしてコミットしておけば、デプロイのたびに、本番環境で個別にプリコンパイルする必要がなくなる
<ActiveRecord::Associations::CollectionProxy []>
- アソシエーションで取得しようとしているが中身が空
ActiveModel::UnknownAttributeError: unknown attribute…
- モデルが持っていない属性が指定されている。controllerのparamsをrequireで受け取るときにモデルを指定していたが、そのモデルのpermitに関係のない属性が記述されていた
ActiveRecord::AssociationTypeMismatch:
User(#32140) expected, got [239] which is an instance of …, Array(#11120)ActiveRecord::AssociationTypeMismatch:
User(#32140) expected, got #<ActiveRecord::Associations::CollectionProxy [#<User…
- 外部キーを指定するにはIDが必要で、インスタンスや配列を渡していた
同じtime型の値のまま更新処理をすると、updated_atが更新されてしまう
- aplication.rbに
config.active_record.default_timezone = :local
を記述したら解決。 - paramsで受け取ったtime型の値を保存しようとすると、強制的に異なる時間がDBに保存されていた。
- railsの時間をDBに保存するように設定すると、DBにparamsと同じ値が挿入されるようになった
- docker環境下,rails環境下,それぞれTimezoneの設定が必要
- dockerログを見るとhoge=21:00のように更新された値のみセットされていた。このことからhogeが更新されているのが推測できる。hogeの受け取った値と保存した値を調べれば容易にたどり着いた
`check_validity!': Could not find the association :hoge in model Foo
- 親モデルから孫モデルをアソシエーションで取得する際に,親モデルに定義する
through
が子モデルの複数形である必要があった - つまり,親モデルに,
has_many :孫モデル, through: :子モデルの複数形
と定義することによって、親モデル.孫モデル
で親から孫のモデルを直接取得できる
has_many throughで孫モデルを親モデルから複数個指定したい場合
- ホテルにお気に入りを登録したユーザー
- ホテルに口コミを書いたユーザー
それぞれをsource: :hoge
で明示的にモデルを指定することで関連付けができる
has_many :favorite_users, through: :favorites, source: :user, dependent: :destroy
has_many :reviews_users, through: :reviews, source: :user, dependent: :destroy
NameError:
Rails couldn't find a valid model for HotelFacilitiy association. Please provide the :class_name option on the association declaration. If :class_name is already provided, make sure it's an ActiveRecord::Base subclass.
- 関連付けに指定している名前がおかしかった
# エラー
has_one :hotel_facilitiy, primary_key: :hotel_id, dependent: :destroy
# 動作
has_one :hotel_facility, primary_key: :hotel_id, dependent: :destroy
find_eachでnilが返ってきてしまう。
result = User.find_each do |user|
user.name
end
puts result # => nil
- 内部の情報を確かめたい場合は当然内部をputsする必要があった
User.find_each do |user|
puts user.name
end
- もしくは
User.find_each.map(&:name)
のようにして配列で返せば期待した値を配列で取得できる
Rails find_each method returning nill on console?
マイグレーション, DB
ActiveRecord::NoDatabaseError: We could not find your database: app_production. Which can be found in the database configuration file located at config/database.yml. Mysql2::Error: Unknown database 'app_production’
- dockerのvolumeを削除したので当然dbも消えていた
- production環境で
rails:db create
をすると永遠に作成できない - dev環境でapp_development, test_development, app_productionを作成してからprodのdocker-composeでrails db:migrate
ActiveRecord::ConnectionNotEstablished (Can't connect to local MySQL server through socket '/run/mysqld/mysqld.sock’
datebase.yml
のproductionにhostを指定していなかった。また、username,passwordも記述する必要がある
production:
adapter: mysql2
encoding: utf8mb4
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
database: app_production
host: db
username: <%= Rails.application.credentials.db[:user_name] %>
password: <%= Rails.application.credentials.db[:password] %>
Couldn't find database client: mysql, mysql5. Check your $PATH and try again.
- Dockerfileでmysql-clientを記述していなかった
-
apk update
でこれからAlpine Linuxがとってくるパッケージの参照先を最新にするapk add
で実際にパッケージを取得する
RUN apk update && \
apk add \
mysql-dev \
git \
tzdata
RUN apk update && \
apk add \
mysql-dev \
mysql-client \
git \
tzdata
rails c 時にdb:migrateをすると止まってしまうので注意
Multiple migrations have the name Hoge
- 同じ名前のマイグレーションの複製はできない
NameError: uninitialized constant Hoge Object.const_get(camel_cased_word)
- migrationではクラス名が必須
migrate:resetでエラー
- 既にカラムがcreateのdownによって消えているのに、余計なremoveのマイグレーションでもう一度削除していた。このマイグレーションファイルを削除で解決
該当コードとログと解決に至るまで
== 20220916190408 RemoveReviewsCountAndAverageRatingToHotels: migrating =======
-- remove_column(:hotels, :reviews_count, :integer, {:default=>0, :null=>false})
rails aborted!
StandardError: An error has occurred, all later migrations canceled:
Mysql2::Error: Can't DROP 'reviews_count'; check that column/key exists
/app/db/migrate/20220916190408_remove_reviews_count_and_average_rating_to_hotels.rb:3:in `change'
Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Can't DROP 'reviews_count'; check that column/key exists
/app/db/migrate/20220916190408_remove_reviews_count_and_average_rating_to_hotels.rb:3:in `change'
Caused by:
Mysql2::Error: Can't DROP 'reviews_count'; check that column/key exists
/app/db/migrate/20220916190408_remove_reviews_count_and_average_rating_to_hotels.rb:3:in `change'
Tasks: TOP => db:migrate:reset => db:migrate
(See full trace by running task with --trace)
up 20220912103535 Remove file url from hotel image and review image
down 20220916190408 Remove reviews count and average rating to hotels
db/migrate/20220916190408_remove_reviews_count_and_average_rating_to_hotels.rb
class RemoveReviewsCountAndAverageRatingToHotels < ActiveRecord::Migration[7.0]
def change
remove_column :hotels, :reviews_count, :integer, default: 0, null: false
remove_column :hotels, :average_rating, :decimal, default: 0, null: false, precision: 2, scale: 1
end
end
Docker Compose 環境下でテーブルを削除したい時に、間違えた記述をしてしまい、dropはmigrateされたがエラーが吐かれてschemaには反映されず
- drop_tableをしたあとにremoveでindexも削除しようとしてしまった
該当コードとログと解決に至るまで
class DropTableReviewImages < ActiveRecord::Migration[7.0]
def change
drop_table :review_images do |t|
t.references :review, index: true, foreign_key: true
t.string :key
t.timestamps
end
remove_index :review_images, :key
end
end
- tableのdropはmigarateされているがその後にエラー
- drop_table(:review_images)
-> 0.2413s
-- remove_index(:review_images, [:key], {:unique=>true})
rails aborted!
StandardError: An error has occurred, all later migrations canceled:
Mysql2::Error: Table 'app_development.review_images' doesn't exist
/app/db/migrate/20221217070400_drop_table_review_images.rb:9:in `change'
Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Table 'app_development.review_images' doesn't exist
/app/db/migrate/20221217070400_drop_table_review_images.rb:9:in `change'
Caused by:
Mysql2::Error: Table 'app_development.review_images' doesn't exist
/app/db/migrate/20221217070400_drop_table_review_images.rb:9:in `change'
- この時にschemaは何も反映されていない。しかし、実際にはテーブルは削除されている。なので、コードを修正して、もう一度migrateしようとしてもできない
-- drop_table(:review_images)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:
Mysql2::Error: Unknown table 'app_development.review_images'
/app/db/migrate/20221217070400_drop_table_review_images.rb:3:in `change'
Caused by:
ActiveRecord::StatementInvalid: Mysql2::Error: Unknown table 'app_development.review_images'
/app/db/migrate/20221217070400_drop_table_review_images.rb:3:in `change'
Caused by:
Mysql2::Error: Unknown table 'app_development.review_images'
/app/db/migrate/20221217070400_drop_table_review_images.rb:3:in `change'
- changeから、up downにする。そしてupを空にしてmigrateして、schemaに反映させる。また、この時にdownに可逆的な元のテーブルの内容を記載する。そうすることで、down時にテーブル削除前の状態に戻る。
class DropTableReviewImages < ActiveRecord::Migration[7.0]
def up
# drop_table :review_images do |t|
# t.references :review, index: true, foreign_key: true
# t.string :key
# t.timestamps
# end
end
def down
create_table :review_images do |t|
t.references :review, index: true, foreign_key: true
t.string :key
t.timestamps
end
add_index :review_images, [:key], unique: true
end
end
- migrateをするとschemaが更新されていることを確認。そしてこのままrollbackすることでdownに記載されたmigrateが実行され、drop前にテーブルが戻る
- 最後に、upのコメントアウトを外して、再度migrateをしてテーブルの削除を実行する
コントローラー
Zeitwerk::NameError: expected file /app/controllers/v1/images_controller.rb to define constant V1::ImagesController, but didn't
- コントローラーに記述してるクラス名が複数形になっていなかった
/app/controllers/v1/health_check_controllers.rb to define constant V1::HealthCheckControllers, but didn't (Zeitwerk::NameError)
- 末尾のcontrollerは単数形である必要があるが、
health_check_controllers
のように複数形になっていた
ActionController::MissingExactTemplate: V1::FiltersController#index is missing a template for request formats: text/html
- コントローラーで指定しているparamsの値とrequests specのparamsの値が違う
Failure/Error: get v1_filters_path, params: { filter_params: "lower_rest" }
def filter_params
params.permit(:sort)
end
BrakemanでUnscoped Find
- 以下は全てのユーザーがparamsにidを入れると宿泊料金を参照できてしまう
def set_stay_rate
@stay_rate = StayRate.find(params[:id])
end
current_userのみが参照できるように変更
def set_stay_rate
@stay_rate = current_user.stay_rates.find(params[:id])
end
RSpec
NoMethodError: undefined method `v1_users_path' for #<RSpec::ExampleGroups::V1Users::GETV1UsersIdV1UsersIndex::Nested "ユーザーの情報を返すこと" (./spec/requests/v1/users_spec.rb:11)
- index は
v1_users_path
の複数形 - showは
v1_user_path
と単数形にする必要があった
複数モデル保存時のテストでnilが返ってしまう。
- spec内のparamsのuser_idの部分がおかしかった
-
return if
でinvalidの時にnilが返っている - return if をコメントアウトするとfalseが返ってきた。hotel.save!で失敗している。postmanでは正常に保存できていたため、specのどこかがおかしい
-
ActiveRecord::Base.transaction
の部分をコメントアウトするとUser情報がおかしいとのエラー。
def save
return if invalid?
ActiveRecord::Base.transaction do
hotel = Hotel.new(name:, content:, user_id:)
build_hotel_images(hotel:)
build_today_rest_rate(today: build_day(hotel:))
hotel.save!
end
rescue ActiveRecord::RecordInvalid
false
end
let_it_beをdescribe配下で使うと適応されない
- describeのタグが閉じていておかしかった。つまりcontextがdescribe配下にネストできていなかった
request specでpatchをしたあとgetのresponseは変更されているが、let_it_beで定義したhotelを直接参照すると変更されていない。
- reloadをして動作
context "ホテルを空室から満室にのみ変更する場合" do
let_it_be(:hotel) { create(:accepted_hotel, user: client_user) }
let_it_be(:favorite) { create(:with_user_favorite, hotel:) }
it "通知がユーザーに送信されないこと" do
only_patch_full_params = { hotel: { full: true }}
expect { patch v1_hotel_path(hotel.id), params: only_patch_full_params, headers: auth_tokens }.not_to change(Notification, :count)
get v1_hotel_path(hotel.id)
expect(symbolized_body(response)[:full]).to eq(true)
hotel.reload
expect(hotel.full).to eq(true)
end
end
config
require File.expand_path('../config/environment', dir) uninitialized constant..
- initializeに追加したファイル名と登録するクラス名が一致していなかった
文法
ArgumentError: missing keyword: :user_id
- キーワード引数がおかしい
Arrayに三項演算子で二次元配列の形式で値を追加していくときにboolが追加されていた
- if else で記述して解決
# エラー
def create_list_of_id_and_time_length_from_the_current_time(stay_rates:)
making_each_stay_array(date: stay_rates).map do |val|
arr = []
arr << val[2] << midnight_service?(start_time: val[0]) ? MAX_TIME : [*convert_at_hour(Time.current)...convert_at_hour(val[0])].length
end
end
#=> [[12324,true], [322, false]]
# 動作
def create_list_of_id_and_time_length_from_the_current_time(stay_rates:)
making_each_stay_array(date: stay_rates).map do |val|
arr = []
arr << val[2] << if midnight_service?(start_time: val[0])
MAX_TIME
else
[*convert_at_hour(Time.current)...convert_at_hour(val[0])].length
end
end
end
#=> [[12324,5], [322, 6]]
Rubocop
private
should not be inlined in method definitions. (convention:Style/AccessModifierDeclarations)
Ruby 3.0から導入されたアクセスメソッドをprivateで宣言する方法を用いると、構文エラーになる
class Foo
private attr_reader :baz
# private should not be inlined in method definitions. (convention:Style/AccessModifierDeclarations)
def initialize(baz)
@baz = baz
end
private
# ten_other_methods
end
解決策
class Foo
attr_reader :baz
private :baz
def initialize(baz)
@baz = baz
end
private
# ten_other_methods
end
Github ActionsでUnable to find gem panolint; is the gem installed? Gem::MissingSpecError
- panolintをインストールして実行したら動作
- 似たようなエラーのissueでrubocopのexcludeにvender/**/*を追加したら解決したというのがあった
https://github.com/rubocop/rubocop/issues/9832
関連付けを取得すると[]になる
def select_stay_rates
stay_rate_list = StayRate.none
hotels.eager_load(:days, :stay_rates, :hotel_images).map do |hotel|
stay_rate_list = stay_rate_list.or(extract_open_stay_rate(hotel:))
end
stay_rate_list
end
- RspecのFactoryでデータを3つ挿入したテストをしていたが、最初のホテルの宿泊料金(stay_rate)が空だった。
- 関連付け先が空なのでmapで回すと空になり、その宿泊料金が空のホテルに対して
extract_open_stay_rate
という処理をしていた
def select_stay_rates
stay_rate_list = StayRate.none
hotels.eager_load(:days, :stay_rates, :hotel_images).map do |hotel|
next if hotel.stay_rates.blank?
stay_rate_list = stay_rate_list.or(extract_open_stay_rate(hotel:))
end
stay_rate_list
end
- next if で空の場合は飛ばすように変更して動作
Unable to load application: ArgumentError: missing required option :name
bundler: failed to load command: puma (/usr/local/bundle/bin/puma)
aws s3のバケット名をENV fetchから直接文字列にして取得
Aws::S3::Resource.new.bucket(ENV.fetch("S3_BUCKET", nil))
Aws::S3::Resource.new.bucket("hoteler-image")
Discussion