niki12260714の日記

フリーランスのITエンジニアの呟き。

独自定義のモデルを元にしたformのvalidatesを日本語化

変にハマったので、メモ。
内容はここの続きです。
あと、大前提として、「i18n」を入れて、日本語化の準備が出来ていることが必須です。

niki12260714.hatenablog.com

この方法で戻ってくるエラーメッセージを見ると
「col aは必須です」
のように、モデルに定義したattributesは英語になってます。
これを日本語にするには、「config/locals/models」の中に「ja.yml」を作成し、以下のように記述します。

ja:
 activemodel:
  attributes:
   item_add:
    col_a: "カラムA"
    col_b: "カラムB"
    col_c: "カラムC"

ネットでぐぐって最初にヒットするのは、大体がDBと結びついたモデルのカラム名を日本語化する話だと思います。
自分の場合、独自モデルで、Active Modelを継承したものなので、activemodelと記述しないと駄目です。
気付けば、そういうことか! ってなる話でした。

※これで日本語化されてないとなると、多分パスが通ってない気がするので、こちらを参考にパスを通すと良いかと。

qiita.com

Active Modelを定義し、form_forを使用、validatesでエラー処理までの流れ

画面の入力項目が一つのテーブルだけではないというのはよくあるパターン。
そうすると、form_forが使えないので、form_tagを使うことになります。
が、そうするとvalidatesの処理とかセオリー通りにいかないし、form_forの方がBootstrapでごにょごにょするのに便利なわけですし。
なんで、こういう場合は「そのformで使うモデルを独自に定義し、それを使う」という技があります。
※あと、検索画面で「検索条件欄に入力が無かったらエラーにしたい」というのをvalidatesで定義しておきたいとか。

というわけで、独自モデルの定義。
railsが5以上の場合は、app/modelの下にconcernsというフォルダが出来ているので、ここを使うらしい?
※ファイルの配置位置は、私の解釈なんで、間違っていたらご指摘ください。
「item_add.rb」というファイルを追加し、以下を記述

【model】
class ItemAdd
 include ActiveModel::Model
 attr_accessor :col_a, :col_b, :col_c

 validates :col_a
  presence: true,
  length: {minimum: 1, maximum: 100}
end

ポイントは、赤字にした「include」のところで、ActiveModelを継承すること。
で、attr_accessorで、そのformで使う要素を列挙すること。
attr_accessorに書いた要素に対し、validatesを定義しておきます。
これは通常のModelに対する書き方と一緒。

で、この独自に定義したmodelをcontrollerでnewしてあげて、viewで使うわけです。

【controller】
def init
 #独自定義のモデルをロードする
 @add_form = ItemAdd.new()
end

【view】
<%= form_for @add_form, url: {controller: :xxx, action: :xxx} do |form| %>
 <%= form.label :col_a, "要素A" %>
 <%= form.text_field :col_a, id: :col_a%><br />
 <%= form.label :col_b, "要素B" %>
 <%= form.text_field :col_b, id: :col_b%><br />
 <%= form.label :col_c, "要素C" %>
 <%= form.text_field :col_c, id: :col_c%><br />
 <br />
 <%= form.submit "追加", class: :"btn btn-default" %>
<% end %>

これでview側で、form_forが使えます!
さて、これでsubmitされたら、validatesを通してチェックをしたいわけで、それはcontrollerでこんな感じで書きます。

【controller】
def xxx
 #入力値を元にモデルオブジェクトを生成
 add_form = ItemAdd.new(col_a: params[:item_add][:col_a], col_b: params[:item_add][:col_b], col_c: params[:item_add][:col_c])
 if !add_form.valid?
  #validatesでエラーになった場合の記述
  flash[:danger] = add_form.errors.full_messages[0]
 else
  #validatesに問題がない場合の記述
 end
end

ポイントは、赤字のnewしているところと、newしたオブジェクトに対し、valid?と判定をかけているところ。
newする時に、formでpostされたデータを入れてあげます。
その結果が正しいか(valid?)を確認し、falseが戻ってきたらエラーってことで、エラー処理を書けばよいです。
エラーのメッセージは、通常のmodelのvalidatesで戻されるのと同一です。

 

→エラーメッセージの日本語化について、追加の記事があります。

niki12260714.hatenablog.com

railsで二つのセレクトボックスを連動させる

都道府県のセレクトボックス選ぶと、隣にある市町村のセレクトボックスの中身が変わる。
そういう奴をrailsで作る方法です。
ネットに沢山情報がありますが、自分が分かりやすかった方法がこちら。

【プログラムの動き】
1.セレクトボックスにonchangeイベントを設定し、coffeescriptに記述した関数を呼び出す
2.呼び出された関数で、セレクトボックスの選択値を取得、Ajax通信を行い、データを取得
3.取得したデータを、coffeescriptでセレクトボックスの要素に設定する

というわけで、1から順番に。

【View】
<%= select_tag 'parent_select', options_for_select(@date_array), onchange: 'change_data()' %>

赤字のところで、「セレクトボックスが選択されたら、change_dataという関数を呼び出す」と設定します。

coffeescript

f:id:niki12260714:20180620175754j:plain

coffeescriptはインデントが大事なので、画像でコードを表示
app/assets/javascriptの中に配置されるcoffeescriptに記述します。
自分が詰まった場所について、それぞれ解説。
「4行目:$ ->」は、「jQueryを使用します」という宣言。
「5行目:@change_data =() ->」は、先ほどViewで設定した呼び出し関数ですが、ここでポイントは「@」と「()」。
coffeescriptのサイトでコンパイルすると分かりますが、@はthisを表します。
これがいないと、View側から見えないので、セレクトボックスを選択するとエラーになっちゃいます。
()も、呼び出される時には必要な設定。
「7行目:val = $('[name=parent_select]').val()」は、jQueryを使ってセレクトボックスで選択された値を取ってきます。
9~18行目はAjax通信部分で、json形式の戻り値を処理します。
処理は別関数に切り出してます、21行目以下。
jsonをfor文で回し、中身をoptionの中に入れてセレクトボックスの末尾に追加。

 

【controller】
def
 data = Item.where(id: params[:id])
 render json: group.select("id AS id, item_name AS name")
end

 Ajax通信の個所。
render jasonで、json形式にデータを展開します。

以上!

AWS Cloud9でpumaを再起動

参考にしたのはこちら。

qiita.com

ターミナルで「ps ax」を打つと、これが出てきました。

f:id:niki12260714:20180613171258j:plain

AWSの場合、「puma x.xx.xx~」を殺せばよい模様。
「kill -9 6109」でpumaが終了するので、「rails s -b $IP -p $PORT」を打てば再起動する。

※「server.pid」ファイルをrmする必要があるかは不明。多分rmしなくても良い気がする……。

【調査中】railsでleft_joinsするモデルに対して条件を追加する方法

こちらと同じことをしたくて調べています。

teratail.com

自分の場合、まさに外部結合する方のテーブルに条件を追加するSQLを発行したいのです。

SELECT A.id, B.id
FROM A
LEFT OUTER JOIN B ON A.id = B.a_id AND B.col = (動的な値)
WHERE A.col = (なんかの値)

赤字のところを追加したいんですが、これが分からん。
あんまりモデルにデフォルトスコープ持たせたくないのと、後でメンテするときに分かりやすい記述にしておくべきと思うので、今はfind_by_sqlSQLべた書きにしています。
これを折々調べようと思うんで、ここにメモとして残しておきます。

railsで親子モデルのそれぞれに条件設定してn+1問題を解決する方法

親子のモデルで結合する時、joinsかincludesを使うわけですが、取得結果表示で、「子供のループで回したい」となると、joinsだとn+1問題が発生します。
なので、includesをしたいのですが、includesは「親に対してSQL実行」→「親の結果をもとに子のSQLを実行」になるわけです。
とすると、「子が特定の条件を持つ親を取得」はエラーになる。
で、どうするか。
参考にしたのはこちら。

qiita.com

親.joins(子:[孫: [ひ孫: :玄孫]]).where("子.カラムA = ?", 値).preload(:子)

joinsした後にwhere句かけて、preloadするわけです。
※Where句の書き方は、プレイスホルダーの書き方じゃないとエラーが出たので、注意

 

これで親、子それぞれでSQL発行してくれるので、Viewで描画するときに親子でeach文回せます。

→自分課題ですが、これをさらに孫ループもしたい場合はどうすればよいのか……