午前中は銀行に行ったり、郵便局に行ったりしてた。
銀行は支払いの手続き、郵便局は郵便物の転送がうまくいってないっぽいので。

今日のやったこと、できたこと

  • 経理処理
  • 郵便局
  • プログラミング
  • 企画の作成

明日への課題

  • プログラミング

今日のワーク

郵便物の転送は問題ないそうだ。
ちゃんと転送されてるって、でも何も届いてない。意味がわからない。
しかも、履歴もないから諦めるしかないっていうね。

そして、今日はプログラミングで色々勉強になることがあった。
いや、もう昨日今日で丸一日分くらい悩んでた。

今回はちょっと面倒で、親子関係のテーブル2つを同時に更新しようかなと。
しかも子の項目を追加したり削除したりできるようにしようかなと思った。

さらに詰まった理由が、項目にファイルフィールドがあったこと。ここで大分詰まった。

まず、ファイルフィールドについては、オブジェクトにnewでストロングパラメータを突っ込んだらダメみたいだ。
こんな感じは値を取れなくなる。

@hoge = Hoge.new(hoge_params)
p @hoge.upload_file.original_filename

def hoge_params
 params.require(:hoge).permit(:name, :email, :upload_file)
end

このpはエラーになる。
ストロングパラメーターのところまでは大丈夫だけど、newの引数にした時点でActionDispatchの内容は引き継がれない。
ここに気がつかないで、なんで値取れないのかずーっと悩んでた。

なので、newした後にファイル情報だけ手動でparamsから取得するようにした。

@hoge = Hoge.new(hoge_params)
@hoge.upload_file = params[:hoge][:upload_file] unless @hoge.upload_file.blank?
p @hoge.upload_file.original_filename

def hoge_params
 params.require(:hoge).permit(:name, :email, :upload_file)
end

このpは通る。辺なコードだな。。。
2度手間ですが、致し方ないのかもしれない。もっと効率のいい方法があったら知りたいな。

そして、次はこれに親子関係のデータ登録が絡んでくる。
つまり、こんな感じのテーブル関係になる

class Hoge < ApplicationRecord
 has_many :comments, :dependent => :destroy
end

class Comment < ApplicationRecord
 belongs_to :hoge
end

こんな関係性のテーブルでviewは両方の項目があり、しかもcommentは複数件登録可能にする。
view的にはざっとこんな感じ。_form.html.hamlだけだけど。
ちなみに、フィールドをネストさせるために、gem “nested_form_fields”を使ってます。

= form_for @hoge do |f|
 = f.label :name, "名前"
 = f.text_field :name
 = f.nested_fields_for :comments do |q|
  = q.label :title, "タイトル"
  = q.text_field :title
  = q.remove_nested_fields_link '削除', class: 'btn btn-danger', role: 'button'
 = f.add_nested_fields_link :comments, '追加', class: 'btn btn-primary', role: 'button'
 = f.submit '保存'

大分端折ってるけど、こんな感じでフォームから登録される。
commentsは追加できるから送られてくることになる。

すると、commentsは配列で送信されてくる。
しかも、その時のキーは”comments”ではなくて、”comments_attribetes”になるのと、実はidも送信されてくる。

なので、コントローラーのストロングパラメーターは気をつけないといけない。
値が取れなくなるからね。今回の場合はこんな感じで書いた。

def  hoge_params
 params.require(:hoge).permit(:name, comments_attributes: [:id, :title, :_destroy])
end

_destroyは削除用に必要みたい。
ちなみに、ブラウザで書き出されたフォームを見ると、勝手にhiddenフィールドでid項目が追加されてます。

そして、受け取った値はそのまま突っ込めば大丈夫。
例えば、createならこんな感じになる。

def create
 @hoge = Hoge.new(hoge_params)
 if @hoge.save
  redirect_to root_path, notice: '登録が完了しました'
 else
  flash.now[:alert] = "登録に失敗しました"
  render :action => "new"
 end
end

ただ、これだけじゃだめで、modelの修正が必要。
最初、ネットで調べてこんな感じに修正した。

class Hoge < ApplicationRecord
 has_many :comments, :dependent => :destroy
 accepts_nested_attributes_for :comments, allow_destroy: true
end

commentは修正の必要はない。
これで、自動的にネストされた件数分saveを実行してくれる。
はずだった。。。

なのに、なぜかvalidatesっぽいエラーが表示され登録されない。
その問題は2つのテーブルをreferencesしてる項目にあったっぽい。

つまり、2つのテーブルを同時に登録しようとすると、commentを登録するときにまだhogeのidが決まっていない。
なので、commentのhoge_idが取得できないことになる(らしい)。

しかも、これはrails5からっぽい。違ってたらごめんなさい。
なので、追加の記述が必要になるらしく、さらにmodelをこんな感じで修正した。

class Hoge < ApplicationRecord
 has_many :comments, :dependent => :destroy, inverse_of: :hoge
 accepts_nested_attributes_for :comments, allow_destroy: true
end

class Comment < ApplicationRecord
 belongs_to :hoge, inverse_of: :comments
end

この「inverse_of:」が必要になるっぽい。
これを追記したら、無事登録されました。よかったー。

で、本当はこの親子両方にfile_fieldがあって、その取得も全部手動でやらなきゃいけなかった。
コードはこんな感じになった。無駄に長い。あと、upload_imageは画像をアップロードする自分で書いた関数だと思って。

def create
 @hoge = Hoge.new(hoge_params)
 @hoge.upload_file = upload_image(params[:hoge][:upload_file]) unless @hoge.upload_file.blank?
 @hoge.comments.each_with_index do |comment, i|
  comment.upload_file = upload_image(params[:hoge][:comments_attributes][i][:upload_file]) unless comment.upload_file.blank?
 end
 if @hoge.save
  redirect_to root_path, notice: '登録が完了しました'
 else
  flash.now[:alert] = "登録に失敗しました"
  render :action => "new"
 end
end

こんな感じでnewした中を回してあげればOK。
そして、paramsも_attributesの中を同じインデックスで回してあげればちゃんと取得できるはず。

あと、updateの場合に別でつまずいた。updateはfindしたデータに対してupdateの引数にフォームからの値を渡すと思うんだけど、ここが曲者でした。
なぜなら、フォームから受け取ったデータのupload_fileにupload_imageで変換をかまさなきゃいけない。

しかも、その時の値の取り方がまた辺なパターンだった。
こんな感じにコード書いた。

def update
 hoge = hoge_params
 hoge[:upload_file] = upload_image(params[:hoge][:upload_file]) unless hoge[:upload_file].blank?
 hoge[:comments_attributes].each_with_index do |comment, i|
  comment[1][:upload_file] = upload_image(params[:hoge][:comments_attributes][i.to_s][:upload_file]) unless comment[1][:upload_file].blank?
 end
 if @hoge.update(hoge)
  redirect_to @hoge, notice: "更新が完了しました"
 else
  render :action => "edit"
 end
end

ちなみに、upload_imageはファイルを保存したパス+original_filenameを返してくる仕様にしました。
まぁ、CarrierWave使った方がいいかもしれないけど、今回は手作りしました。ただ、画像サイズはmini_magick使ってるけど。

あと、無駄に考えすぎてたのが、updateでファイルを選択してない場合、今登録されてる画像が削除されないのかなと。
なので、upload_fileが空の場合はfindした元の値をparamsから取得した値に入れるロジックを書いたけど、不要でした。さすがです。

で、フォームにアップロードした画像を表示させようと思ったらここでもつまずいた。
nested_fields_forが自動で出力してくれてるから、そこに値を取得して出力するロジックの組み込み方がわからなかった。
で調べた結果がこんな感じ「- index =」のところを追加してる。

= form_for @hoge do |f|
 = f.label :name, "名前"
 = f.text_field :name
 = f.nested_fields_for :comments do |q|
  - index = q.object ? @hoge.comments.index(q.object) : '__nested_field_for_replace_with_index__'
  = q.label :title, "タイトル"
  = q.text_field :title
  = q.label :upload_file, "画像"
  = q.file_field :upload_file
  - if @hoge.comments[index.to_i].upload_file.present?
   = image_tag @hoge.comments[index.to_i].upload_file
  = q.remove_nested_fields_link '削除', class: 'btn btn-danger', role: 'button'
 = f.add_nested_fields_link :comments, '追加', class: 'btn btn-primary', role: 'button'
 = f.submit '保存'

これでindexに回してる時のindexが入る。
ただ、注意点として文字列なので、使うときは「.to_i」しなきゃだめですよ。

結局、日本語サイトにはどこにもなくて、本家のgithubみたら「__nested_field_for_replace_with_index__」があるのを見つけて、使い方は海外のサイトで見つけた。

やっぱり、英語への抵抗をなくさないと苦労するね、Railsは。
だいたい深く悩んだ場合の答えは日本語でないからね。英語サイト頼ることになるよね。

さらにtag機能も実装したのでgem “acts-as-taggable-on”を入れたり、「bootstrap-tagsinput」導入したりしてた。
これは2度目なので特にトラブルもなく導入できたんだけど。

あと、text_areaにwysiwyg導入しようかなと思って、summernoteも導入したんだけど、よく考えたら要らなかった。
もっとシンプルな内容でよかったんだと。

そんなこんなで、全部で丸1日以上かかった。
全くかかりすぎだし、本当に疲れたよ。

ただ、なんとか形になってよかったなとホッとしてる。
作ろうと思ってたシステムの半分くらいはこれで完成したことになるし。

あとは、ワードプレスとの連携のところかな。
ワードプレスのDBとの連携ってやったことないから、すんなりいくとは思えないけど。

9月もあと少しで終わってしまうし、実質あと1日と行っても過言じゃないか。
なんとか少しでも進めて見たいと思う。

そんな感じ。