form_with .date_select 保存できない パラメータ
Railsのフォームヘルパーであるform_with であるが、面倒なところがまたあった。
form_withのメソッドで年月日をセレクトボックスで入力できるようにする.date_select というものがある。これ、便利だけれど、パラメータの保存が面倒だったので備忘録。
form_withの使い方はここのサイトにある通り。引数で様々なオプションを指定することになるので、ここでよく確認しておこう。
https://pikawaka.com/rails/form_with#form.date_select
さて、今回はプロフィール登録画面で生年月日を入力するフォームを作った。
※ HTMLはhamlで書いています。
(Viewファイル)
sprintfというちょっと特殊な書き方をしているのは年月日の区切りを入れるためです。
以下のサイトを参考に
● https://yoshiob.hatenadiary.org/entry/20091105
この記述で送られるパラメータをUsers_ControllerのupdateアクションからUsers_Tableのbirth_dayカラムに保存できるようにしたい。
このViewのフォームから送られるパラメータは
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ibjRO〜〜i6xZnQ8Txdw==",
"user"=>{"name"=>"ほげ" , "nickname"=>"ほげちゃん", "profile"=>"ほげほげ"},
"birth_day"=>{"birth_day(1i)"=>"2000", "birth_day(2i)"=>"1", "birth_day(3i)"=>"1"},
"commit"=>"登録する", "id"=>"1"}
このような構造になる。注目すべきは三段目。
● 今回はform_withによってUserモデルにアクセスするパラメータを作ったので、入力フォームに記述した内容はParams: { user: { ここに入力内容 }}というように二重のハッシュで格納されるはずである。
しかしbirth_day の内容はuser:{ }の外に作られる。これの何が問題か、というと、ストロングパラメータを作る際にrequire(:user)をつけるところでエラーになる。
requireメソッドで二重構造のハッシュを解消しているが、その中にbirth_dayのキーがないので、permitメソッドを適用できなくて、Parameter_permittion_errorみたいなのになる。
● もう一点、birth_dayキーの中身を見てみると、入力した年月日の情報がバラけてそれぞれ"birth_day(1i)"的なキーとバリューで保存されている。このままだとDBのuserテーブルにあるbirth_dayカラムにきちんとまとめて保存されない。(birth_dayカラムのデータ型はDATE型にしている)
このバラバラになっているデータをひとまとめにする必要がある。
これらの状況を解決していく。備忘録。
●コントローラの記述
① params[:user][:birth_day] = birthday_join
この記述でまず右辺のbirthday_joinメソッドを起動させる。返り値をparamsのuserキーの中にbirth_dayキーとして新たに保存する。これでストロングパラメータのrequire問題を解決する。(この辺カラム名との整合性に注意)
② birthday_joinメソッドの中身
・ date = params[:birth_day] … paramsの中の:birth_dayハッシュの値を取得
・ if date["birth_day(1i)"].empty? && date["birth_day(2i)"].empty? 〜〜
…入力が空だった時のエラー回避、空のままで値を返す記述
・ Date.new(date["birth_day(1i)"].to_i,date["birth_day(2i)"].to_i,date["birth_day(3i)"].to_i)
…バラけてたデータをto_iメソッドで数値化して、それをDateクラスで改めてDate型のインスタンスとして再構成する。このインスタンスを元のParamsに戻します。
これにて
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ibjRO〜〜i6xZnQ8Txdw==",
"user"=>{"name"=>"ほげ" , "nickname"=>"ほげちゃん", "profile"=>"ほげほげ",
"birth_day"=>{"2000-1-1"},"commit"=>"登録する", "id"=>"1"}
になって全ての入力内容がuser: { }のハッシュに入ったので、ストロングパラメータでPermittionがかけられてエラーが起きなくなりました。
このように.date_selectはパラメータの値が面倒臭いので気をつけて実装しましょう。
<参考サイト>
● https://qiita.com/ozackiee/items/3c027d07cdeb61df6029
has_one で, field_for を使う, accepts_nested_attributes_for
railsのヘルパーメソッドであるform_withでDBに情報登録する際に、主たる保存先のテーブル以外のところにレコードを保存したい時に使うのが、fields_forメソッドである。
今回はUserのテーブルに個人情報を登録する際、住所も入力させる。その際、住所はAddressテーブルという別の場所を指定する。
さてUserはそれぞれ住所を一個しか持っていないのでアソシエーションの関係は、
User modelでは has_one :address
Address modelでは belongs_to :user
となっている。この状況でのfields_forの使いかたをまとめる。
※HTMLはhamlで書いています
① Userモデルの記述
accepts_nested_attributes_for :address この記述によってUserモデルで一緒にAddressテーブルに保存ができるようにParamsを構成してくれるようになる。
この :address はhas_one :address でアソシエーション組んだモデル名と同じにする必要がある。これが異なると
No association found for name `address'. Has it been defined yet?
と怒られる。
② Viewファイルの記述
f.fields_for :address この引数でUserモデルの has_one :address を読み込んで自動で保存のためのパスを生成してくれている。
そしてaccepts_nested_attributes_for :addressが働いて、Paramsの中に address_attributes というハッシュを作ってくれる。この中にfields_for で入力した値を入れてパラメータとして送るのである。
ここを勘違いして引数を :addresses とかにしちゃうとアソシエーションを無視ししてしまい、accepts_nested_attributes_for :addressが働いてくれない。Paramsのなかに adresses: というハッシュができてその中に住所情報が格納される。でもこんなハッシュ名は保存先がないからエラーになってしまう。
(ちなみに第二引数の@user.address || Address.new はeditように既存レコードがあるかの判別をしている)
③ Controllerの記述
この情報登録で送られるパラメータはこうなる
Parameters: {"utf8"=>"✓", "authenticity_token"=>"toRFpCGlh+vdfjSkGB69zwkYFL9UkeRw0n",
"user"=>{"second_name"=>"保毛", "first_name"=>"帆桁",
"address_attributes"=>{"post_code"=>"111-2233"},
"commit"=>"登録する", "id"=>"1"}
ストロングパラメータでpermitを与えるキーにaddress_attributes: を加えてあげる。これでRailsが自動でアソシエーションを組んでいるAddressテーブルにレコードを送ってくれる。
●まとめ
名称を揃えなければいけないのは3箇所。
⑴ accepts_nested_attributes_for :address
⑵ has_one :address
⑶ f.fields_for :address
これがアソシエーションの関連がhas_many になったら複数形で合わせる。ここをしっかり抑えれば大丈夫。
<参考>
● https://ruby-rails.hatenadiary.com/entry/20141208/1418018874
● https://blog.dakatsuka.jp/2011/09/30/nested-has-one.html
has_one で, field_for を使う, accepts_nested_attributes_for
railsのヘルパーメソッドであるform_withでDBに情報登録する際に、主たる保存先のテーブル以外のところにレコードを保存したい時に使うのが、fields_forメソッドである。
今回はUserのテーブルに個人情報を登録する際、住所も入力させる。その際、住所はAddressテーブルという別の場所を指定する。
さてUserはそれぞれ住所を一個しか持っていないのでアソシエーションの関係は、
User modelでは has_one :address
Address modelでは belongs_to :user
となっている。この状況でのfields_forの使いかたをまとめる。
※HTMLはhamlで書いています
① Userモデルの記述
accepts_nested_attributes_for :address この記述によってUserモデルで一緒にAddressテーブルに保存ができるようにParamsを構成してくれるようになる。
この :address はhas_one :address でアソシエーション組んだモデル名と同じにする必要がある。これが異なると
No association found for name `address'. Has it been defined yet?
と怒られる。
② Viewファイルの記述
f.fields_for :address この引数でUserモデルの has_one :address を読み込んで自動で保存のためのパスを生成してくれている。
そしてaccepts_nested_attributes_for :addressが働いて、Paramsの中に address_attributes というハッシュを作ってくれる。この中にfields_for で入力した値を入れてパラメータとして送るのである。
ここを勘違いして引数を :addresses とかにしちゃうとアソシエーションを無視ししてしまい、accepts_nested_attributes_for :addressが働いてくれない。Paramsのなかに adresses: というハッシュができてその中に住所情報が格納される。でもこんなハッシュ名は保存先がないからエラーになってしまう。
(ちなみに第二引数の@user.address || Address.new はeditように既存レコードがあるかの判別をしている)
③ Controllerの記述
この情報登録で送られるパラメータはこうなる
Parameters: {"utf8"=>"✓", "authenticity_token"=>"toRFpCGlh+vdfjSkGB69zwkYFL9UkeRw0n",
"user"=>{"second_name"=>"保毛", "first_name"=>"帆桁",
"address_attributes"=>{"post_code"=>"111-2233"},
"commit"=>"登録する", "id"=>"1"}
ストロングパラメータでpermitを与えるキーにaddress_attributes: を加えてあげる。これでRailsが自動でアソシエーションを組んでいるAddressテーブルにレコードを送ってくれる。
●まとめ
名称を揃えなければいけないのは3箇所。
⑴ accepts_nested_attributes_for :address
⑵ has_one :address
⑶ f.fields_for :address
これがアソシエーションの関連がhas_many になったら複数形で合わせる。ここをしっかり抑えれば大丈夫。
<参考>
● https://ruby-rails.hatenadiary.com/entry/20141208/1418018874
● https://blog.dakatsuka.jp/2011/09/30/nested-has-one.html
Can't resolve image into URL: undefined method `to_model'
画像表示ができないエラーである。
●前提
・carrierwaveで画像を投稿できる機能をつけて、投稿画像を一覧画面で表示しようとしています。
・seedファイルを使って初期表示用のアイテムをDBに登録済みである。そこで入れた画像データは’いらすとや’の画像のURLである。
●エラー部分
この部分テンプレートで一覧記事の一個一個を定義しています。その画像部分で「表示がうまくできないよ!」と言っています。
ImageUploaderの'to_model' methodというのがUndefinedだよって言ってます。
●思考
Carrierwaveの仕組みは、formのfile_fieldから直接ファイルを投稿できるっていうものです。画像のURLを文字列として入力して保存するわけじゃない。なので、表示画像もそれにあわせて、Carrierwaveの機能で保存したファイルを直接呼び出している。
しかし、seedファイルに保存してたレコードの画像はURLを保存していた。URLをCarrierwaveの機能で呼び出そうとしたからエラーになったのである。
●解決
① 該当部分に .url を追加する
これによりURLの値でも取得してくれる。でも画像はちゃんと表示できなかった。やめた。
② seedファイルの中身を治した
そうです。元から入れるレコードが悪さしていたので、面倒臭がらずそれを直すべきなのです。
テーブルをリセットして、Seedファイルを直して、完了。
扱うファイル形式とかデータ型とかももっと意識していかないとダメだなぁ。備忘備忘
Excon::Error::BadRequest in ●●Controller#●● S3に Carrierwaveで credentials
以前AWSのS3バケットにデータを保存する設定をしました。そこにCarrierwaveで画像を保存したいとおもっていろいろ設定したんです。その設定が間違っていたらしく、タイトルの文言のエラーが出ました。その備忘録。
●エラー画面
●前提
●AWSのS3バケットのSECRET_ACCESS_KEYをcredentials.ymlでアクセス・管理できるようにする
●そのSECRET_ACCESS_KEYを環境変数に保存することでセキュリティを高める
この設定をしたのですが、credentials.ymlから正しくSECRET_ACCESS_KEYを取得できていない。エラー文からも
The authorization header is malformed; the authorization component \"Credential=<%= ENV[\"AWS_ACCESS_KEY_ID\"] %>/20200325/ap-northeast-1/s3/aws4_request\" is malformed.
「malformed」奇形の〜 という意味の文言が。リクエストの形が奇形になっていてBadRequestになってしまっているということだろう。この中で自分で設定しているのはcredentials.ymlなのでそこだろうと。
設定の治し方は以前の記事の修正をしたのでそちらにまとめてあります。
https://kc-masui-programmer.hatenablog.com/entry/2020/03/16/204215
デプロイは難しいな。備忘備忘。
credentials.yml.enc デプロイ 設定 本番環境
Rails5.2から、以前のsecrets.ymlに変わって新たに導入されるようになった秘密情報の管理ファイルであるcredentials.ymlの使い方をまとめる。
<やること>
●AWSのS3バケットのSECRET_ACCESS_KEYをcredentials.ymlでアクセス・管理できるようにする
●そのSECRET_ACCESS_KEYを環境変数に保存することでセキュリティを高める
<追記:失敗しました。①はやらないことにします>
# ローカル環境
$ vim ~/.zshrc # または~/.bash_profile
# iを押してインサートモードに移行し、下記を追記する。
export AWS_ACCESS_KEY_ID='Access key ID'
export AWS_SECRET_ACCESS_KEY='Secret access key'
# 編集が終わったらescapeキーを押してから:wqと入力して保存・終了
$ source ~/.bash_profile
# 本番環境 $ sudo vim /etc/environment # iを押してインサートモードに移行し、下記を追記する。 export AWS_ACCESS_KEY_ID='Access key ID'
export AWS_SECRET_ACCESS_KEY='Secret access key'
# 編集が終わったらescapeキーを押してから:wqと入力して保存・終了
# 編集した環境変数を適用するために一旦ログアウトします。
$ exit
環境変数から呼び出すように設定しましたが、うまくS3に投稿できませんでした。Carrierwaveがcredentials.ymlの値を呼び出したら
access_key_id: [/"AWS_ACCESS_KEY_ID"/]
で呼び出されてしまってちゃんと値が出力されませんでした。credentials.ymlを使えば暗号化されるのでわざわざ環境変数から呼び出す必要ないので、もう環境変数に入れるのはやめます。
②credentials.yml.encに設定を記述
config/credentials.yml.enc
$ EDITOR=vim bin/rails credentials:edit
# vimでcredentialsの中身を記述する
# ↓#がついているのをとる
aws:
access_key_id: 自分のAWS_ACCESS_KEY_ID
secret_access_key: 自分のAWS_SECRET_ACCESS_KEY
# master.keyで暗号化されるので安心
secret_key_base: 94cd28a0bbe83b737292da7928f2f5fca17937922b0a79a64c22cc4999d87d3426808ad3ea655e7a68652d6e3e3c1021339ceb87ee615322351640bed63ce8bd
#secret_key_baseは必ず最下段に置くようにする
③carrierwave.rbに設定を記述
S3に画像保存をする目的でこの設定をするので、画像保存のためのgemであるCarrierwaveを設定します。
config/initializers/carrierwave.rb
require 'carrierwave/storage/abstract' require 'carrierwave/storage/file' require 'carrierwave/storage/fog' CarrierWave.configure do |config| config.storage = :fog config.fog_provider = 'fog/aws' config.fog_credentials = { provider: 'AWS', aws_access_key_id: Rails.application.credentials.aws[:access_key_id], aws_secret_access_key: Rails.application.credenrials.aws[:secret_access_key], region: 'ap-northeast-1' } config.fog_directory = 'バケット名' config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/バケット名' end
こんな感じ。Rails.application.credentials.aws[:access_key_id],のところが以前のsecrets.ymlと呼び出し方が違う。[:access_key_id]というようにキー名指定になっているので注意。自分はここの間違いで時間食った。
このCarrierwaveの設定でcredentials.ymlの変数を呼び出して、SECRET_ACCESS_KEYやらを取得しています。
④master.keyを本番環境にコピーしてくる
credentials.ymlは暗号化されているのでGithubに乗って他人に見られても大丈夫です。この暗号を解くキーがmaster.keyに記述されます。だからmaster.keyファイルは外部に漏れてはいけない。.gitignoreに自動で追加されています。
さて、問題なのは.gitignoreされているのでデプロイしても本番環境にmaster.keyは存在していない状態になっています。なので、credentials.ymlの暗号が解けない!という状態。エラーになる。
このままでは意味がないので、githubを介さずに、直接本番環境にmaster.keyをコピーして使えるようにします。
ローカル環境
$ scp -i .ssh/example.pem AppName/config/master.key ec2-user@11.222.333.44:/var/www/AppName/shared/config/
# ディレクトリ構造は各自合わせてください
〜各部分の解説〜
● scp -i … Secure Copy Protocolというsshを利用したセキュリティ高いLinuxの転送コマンドです。
(参考)https://qiita.com/chihiro/items/142ebe6980a498b5d4a7
● .ssh/example.pem …SSLサーバ証明書とかに使われる.pemファイルの保存場所の指定。本番環境にアクセスするのに必要な鍵情報が入っているので、指定する必要がある。
参考:https://www.ecoop.net/memo/archives/guide-for-pem.html
今回のコマンドはこの.sshディレクトリが存在するディレクトリ上で実行する。(コマンドの実行は.pemファイルがどこに保存されているかで決まってくるので注意が必要)
● AppName/config/master.key … ローカル環境におけるmasuter.keyの所在地の指定。基本configディレクトリ配下でしょう
● ec2-user@11.222.333.44:/var/www/AppName/shared/config/ … 本番環境におけるコピー先の指定。このconfigディレクトリ配下にmaster.keyをコピーしてくる。IPアドレスとかディレクトリ構造は各自ご確認。
先に書いた通りディレクトリ構造が正しく指定できてないとエラーが出続けるので、根気よく頑張ってください。
参考:https://qiita.com/yuuuking/items/53a37a2e998972be32b8
※追記※もっと簡単に、本番環境でmaster.keyを作成して、本番環境でvim をして、ローカルのmaster.keyの内容をコピペすればいいという。う、う、う、、、、。
⑤deploy.rbに設定を記述
config/deploy.rb
set :linked_files, fetch(:linked_files, []).push("config/master.key")
この set :linked_files, というdeployオプションは、本番環境のsharedディレクトリにあるファイルのシンボリックリンクをcurrentディレクトリに作ってくれるというものである。
「シンボリックリンク」…ショートカットとかエイリアスのようなもの。参照場所への連結通路を作ってくれる。
本番環境のsharedディレクトリは、デプロイによってver.更新されても共通で参照されるディレクトリ(設定ファイルが入ったものとか)が格納されるディレクトリである。
現在、sharedには④の行程でコピーしてきたmaster.keyがある。これをデプロイ最新状態のcurrentディレクトリで使えるようにシンボリックリンクを作るっていう設定である。
この設定はシンボリックリンクを作るだけだから、sharedへのファイルコピーが必要だってわけですね。
参考:https://qiita.com/aplifyjp/items/5d24394dbd03712a2f1f
⑥本番環境でmaster.keyがあるか確認する設定
config/environments/production.rb
config.require_master_key = true
#でコメントアウトされているので解除。master.keyがなかったらエラーを表示してくれるよ。これでファイルがつくれてないとかの設定ミスに気づけます?
以上です。
これでcredentials.yml.encで秘密鍵が扱えるようになります。
参考:https://qiita.com/yuuuking/items/53a37a2e998972be32b8
rails ルーティング (.:format) とは 何
rails routes のコマンドをターミナルで入力すると、Railsで作られているルーティングの一覧が表示されます。
$ rake routes
Prefix Verb URI Pattern Controller#Action
tweets GET /tweets(.:format) tweets#index
POST /tweets(.:format) tweets#create
new_tweet GET /tweets/new(.:format) tweets#new
edit_tweet GET /tweets/:id/edit(.:format) tweets#edit
tweet GET /tweets/:id(.:format) tweets#show
PATCH /tweets/:id(.:format) tweets#update
PUT /tweets/:id(.:format) tweets#update
DELETE /tweets/:id(.:format) tweets#destroy
真ん中のURI pattern( /tweets(.:format) )のとこにある、(.:format) って何?って話です。
結論:URLの先に送るファイルの形式である
基本次のページを表示するって時は.htmlファイルを利用すると思うんですよ。そのファイル形式を(:format)という変数として扱いますよ〜って話だという。例えば次のページが.pdfファイルだったら、format は .pdfということになる。
このくらいの認識でいいのかなと。
このformatを使う時が来るのは、railsのcontrollerにrespond_toというレスポンスの形式ごとに対応を分ける記述をする時とかです。
この構文の意味は、@messageを保存する時に、formatがhtml形式で送られた時とjson形式で送られた時で対応を変えるというものである。
jsonやxmlというのはjavascriptを扱ってレスポンスを飛ばした時に送られてくるデータ形式です。詳しくはajaxなどを説明する別なところで。
このように送られてくるデータ形式で対応を変える時に意識するのが、formatという変数である。
以上。備忘備忘。
参考:
● https://stackoverflow.com/questions/20320263/what-does-format-mean-in-rake-routes
● https://techacademy.jp/magazine/24827