Ruby on Rails Docker エラー忘備録

2023/03/30に公開

こんばんは
ポートフォリオ作成時に発生したエラー及び、予期せぬ動作に対して、どのようにアプローチしたかを記述しています。
つまり、「エラーとその解決方法」というよりかは、動作をしただけです。
ベストプラクティスではないでしょうし、前提としての実装がおかしいということもあります。
私のような未経験の方は斜にかまえてみてもらえると幸いです。

環境

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

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も記述する必要がある

database.yml
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 で実際にパッケージを取得する
Dockerfile
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

関連付けを取得すると[]になる

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