【Rails】Validaition failed、他エラーや課題を解決した時にやったこと

2018年4月11日

プログラミングスクールに通いながらオリジナルのWEBアプリを作っています。

そんな中でこれまで直面した問題・課題・エラーメッセージを解決してきたのでそれをまとめています。もし私と同じ事象にあっているかたの参考になれば幸いです(レベルが低すぎてならないと思うけど‥)

password_digest:が暗号化されない問題

userモデルを作成するときにpassword_digestを使って以下のように生成しています。

$ rails g model User nickname:string email:string password_digest:string

またuser.rbファイルの構成を以下のように実装。has_secure_passowordを入れました。

class User < ApplicationRecord
  before_save { self.email.downcase! }

  validates :name, presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i },
                    uniqueness: { case_sensitive: false }
                    
  has_secure_password
end

そしてパスワード暗号化のためのGemをインストール。

Gemfileの以下の部分のコメントを外してから

# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'
$ bundle install

自前でrails cでユーザーを作成してみました。

user = User.new(nickname: ‘やまさん’, email: ‘yama123zsensi@yahoo.co.jp’, password_digest: ‘yama’)
user.save

これのpassword_digestのカラムを見ると、暗号化されていません。

問題箇所はココ

user = User.new(nickname: ‘やまさん’, email: ‘yama123zsensi@yahoo.co.jp’, password_digest: ‘yama’)

ユーザーを生成するときに、password:って書かないと暗号化されない。直接password_digestに入れちゃうと駄目。

Validation failed: Kuchikomis is invalidが出る

ショップの口コミ投稿サイトを作っています。
なので

shop は has_many :kuchikomisだし、
kuchikomiは belongs_to :shop

です。

口コミが投稿された時にshop#createアクションの中で処理をしています。
(口コミなんだったらkuchikomi#createじゃないの?という話もありますが、僕の仕様はそうなんです。。)

この中でshopがDBに無かったら保存し、かつkuchikomiも保存する処理です。

で、このようにPOSTで送られてきたデータからインスタンスを生成しています。

    @shop = Shop.new(shop_params)
    @kuchikomi = @shop.kuchikomi.build(kuchikomi_params)

build ←これが不味かった。

この時点ではshopはまだDBに無いためshop.idはありません。
それをbuildを使って関連付けしながらkuchikomiインスタンスを生成しています。

buildはshopがDBに無いうちは使っちゃ駄目なのでした。

なので一旦これでいけました。

    @shop = Shop.new(shop_params)
    @kuchikomi = kuchikomi.new(kuchikomi_params)

Validation failed: User must exist

こんどはこのエラー。

店舗の口コミサイトを作ってますが、ここに当然ユーザーも絡んできます。
ユーザーによって店舗に口コミが投稿されるからです。

userモデルは、has_many :kuchikomisですし、
kuchikomiモデルは、belongs_to :user
となっているのです。

このエラーはkuchikomi.saveで、口コミをDBに保存するタイミングで出ています。
なのでモデル周りの実装が悪いんだろうなと思っていました。

色々調べて回ったら外部キーにnilを許可してないことから起こっているようです。

結論、これでいけました。

kuchikomiモデルで、

belongs_to :user, optional: true

このようにoptional: trueを追記したら大丈夫でした。

参考:https://qiita.com/iguchi1124/items/218e35a145f372062ea4

必ず外部キーが存在しなきゃいけない場合は

belongs_to :user, required: ture

nilがある場合は

belongs_to :user, optional: true

とすると良い、ということですね。

no implicit conversion of Symbol into Integer

これはハッシュを生成して、それをビューで表示させようと思ったら出たエラーです。

簡単に書くと以下のようなことをやっていました。

コントローラーで、

    @tests= {} #ハッシュの初期化
    @tests[0] = { :user_id => 'uid1', :nickname => 'もぐもぐ', :image => '画像', :comment => 'おいしい'}
    @tests[1] = { :user_id => 'uid2', :nickname => 'もぐもぐ2', :image => '画2像', :comment => 'おいしい2'}

とやって、ビューで

<% @tests.each do |test| %>
	<%= test[:nickname] %>
<% end %>

とやると上記のエラーが出ます。

翻訳すると「暗黙的にシンボルを整数に変換しない」らしいのですが意味不明です。

あれこれ試していたら最初の@testsを初期化するところで以下のようにしたらエラーが出ないことがわかりました。

	@tests = []

すごくもやもやします(;・∀・)

ここで次のように検索して調査を開始。

Rails 2次元 ハッシュ
Rails ハッシュの初期化は必須なの?

調べてわかったことは、まずRailsではハッシュは必ず初期化して使わないといけない、ということです。

ハッシュの初期化は以下の文が一般的です。

hash = {}

これは僕も知ってたからこれを使ったのですが、エラーが出ちゃったんです。次に2次元ハッシュと3次元ハッシュの初期化のやり方を探していました。

#二次元ハッシュの初期化 定型文
hash = Hash.new { |h,i| h[i] = {} }

#三次元以上のハッシュの初期化
hash = Hash.new { |h,k| h[k] = Hash.new(&h.default_proc) }

このようになるらしいです。

よく見ると僕の書き方が適合しない気がします。それで「配列の中のハッシュの初期化」で検索したら答えがわかりました。

参照→https://qiita.com/sonoshou/items/bb1f6ef756041b58a5be

	ary = [{},{},{},{},{}]
	こんなことをやるには

	ary = Array.new(5){{}}
	=> ary = [{},{},{},{},{}]
	ary[0][:hoge] = "test"
	=> "test"
	ary
	=> [{:hoge=>"test"}, {}, {}, {}, {}]

このようにする必要があります。

上の僕の例だと

    @tests= Array.new(2){{}} #ハッシュの初期化
    @tests[0] = { :user_id => 'uid1', :nickname => 'もぐもぐ', :image => '画像', :comment => 'おいしい'}
    @tests[1] = { :user_id => 'uid2', :nickname => 'もぐもぐ2', :image => '画2像', :comment => 'おいしい2'}

とやって、ビューで

<% @tests.each do |test| %>
	<%= test[:nickname] %>
<% end %>

で出来ました。

本来Array.newの引数の2は、ActiveRecordの件数を取りたかったのでモデル名.lengthかモデル名.countで件数をとって引数に渡しました。

lengthとcountメソッドの違いですが、lengthは配列の長さで、countはSELECT count(*)文を発行しています。

本質的にはcountなんでしょうけど、SQL文を発行するのは遅くなると思うので、lengthを使いたいところです。

と思ってたら、件数が増えてくるとサーバーのメモリの負荷が多くなってくるそうです。そんな中でlengthをするよりは件数カウントするのをDBに任せる(count)にしたほうが良いそうです。

なのでcountでいくことにしました。