RoR 画面の入力値とモデルのバインディング

#始めに
画面の入力値とモデルのバインディングについて、実験的に実装してみます
Railsの便利系のGemは一切使わず、標準的なRubyのライブラリのみで実装しています

#実行環境
JRuby 9.1.0
Rails 4.2.9

#実装
##モデル
###exam_categori
:column_name=>"ID", :data_type=>"NUMBER"
:column_name=>"CATEGORY_CODE", :data_type=>"VARCHAR2"
:column_name=>"SAKUSEIYMD", :data_type=>"DATE"
###exam
:column_name=>"ID", :data_type=>"NUMBER"
:column_name=>"WORD", :data_type=>"VARCHAR2"
:column_name=>"SENTENCE", :data_type=>"CLOB"
:column_name=>"WLEVEL", :data_type=>"NUMBER"
:column_name=>"SAKUSEIYMD", :data_type=>"DATE"
:column_name=>"RENBAN", :data_type=>"NUMBER"
※IDにはブラウザ側で振られたランダムな数字を設定する

##画面
###大雑把な仕様の説明
![image.png](https://qiita-image-store.s3.amazonaws.com/0/136860/08e68416-e1ca-2865-c24a-7c77623c06f1.png)

###大雑把なinputフィールドのIDのつけ方について

![image.png](https://qiita-image-store.s3.amazonaws.com/0/136860/32c34d29-fc7a-3b22-d426-d8677f456c07.png)
→inputタグのid属性の命名規則を「model名#入力行数.モデルのカラム名」のようにする
Ralis側ではPOSTされたリクエストパラメタを

```
exam [
 {1=>[wlevel=value,word=value,sentence=value]},
 [2=>{wlevel=value,word=value,sentence=value}] ,
 [3=>{wlevel=value,word=value,sentence=value]} ,
]
```
のようなデータ構造に変換したい

##Rails側の実装
```exam_controller.rb
class ExamController< ApplicationController
protect_from_forgery :expect  => ["create"]
    def register
        a = params
        .to_a #--(1)
        .group_by do |k,v| --(2)
            str=k[/^.*#/]
            str.present? ?str.tr("#",""):""	
        end
        .to_a--(3)
        .select{|k,v|k.present?}--(4)
        .map do|k,v|--(5)
            [k,v.group_by {|a,b|a.scan(/#(\d)/)[0]}]
        end
        puts a
        render text:""
  end
end
```
(1)リクエストパラメタ「params」を配列化する
(2)リクエストパラメタのキーで「モデル名#」にマッチングするものでグループ化する
(3)(2)の結果を配列化する
(4)(2)の結果でkeyがnilになるものを除外する
つまり、ここで、paramsに含まれていたコントローラやviewの情報が排除される
(5)(4)で変換した配列を["モデル名",{行数=>[画面の入力値の集まり]}]のデータ構造に変換する

#実行結果
##リクエストされたパラメタ値

```
Parameters: 
 {"exam_categori#1.category_code"=>"カテゴリ１", 
  "exam#1.word"=>"テスト１", "exam#1.sentence"=>"テスト１の説明", 
  "exam#1.wlevel"=>"1", "exam#2.word"=>"テスト２", 
  "exam#2.sentence"=>"テスト２の説明", "exam#2.wlevel"=>"2", 
  "exam#3.word"=>"テスト３", "exam#3.sentence"=>"テスト３の説明", 
  "exam#3.wlevel"=>"3", "id#1"=>"799454"}
```

##rails側で変換した結果
```
[
    ["exam_categori", 
        {["1"]=>[["exam_categori#1.category_code", "カテゴリ１"]]}], 
    ["exam", 
        {["1"]=>[["exam#1.word", "テスト１"], 
                 ["exam#1.sentence", "テスト１の説明"], 
                 ["exam#1.wlevel", "1"]], 
         ["2"]=>[["exam#2.word", "テスト２"], 
                 ["exam#2.sentence", "テスト２の説明"], 
                 ["exam#2.wlevel", "2"]], 
         ["3"]=>[["exam#3.word", "テスト３"], 
                 ["exam#3.sentence", "テスト３の説明"], 
                 ["exam#3.wlevel", "3"]]}]
    ["id", 
         {["1"]=>[["id#1", "799454"]]}]
]
```

# 参考：画面側の実装

```exam.html
<!DOCTYPE html>

<script>
function doPost(){
    var id = Math.floor(Math.random () * 1000000) 
    var param = _g()
    .concat(
       [`id#1=${id}`])
    .join('&')
    commute('http://localhost:3000/exam/register/',param)
}
function commute(url,param){
    var req = new XMLHttpRequest();
    req.open('POST',url , true);
    req.setRequestHeader('content-type',
    'application/x-www-form-urlencoded;charset=UTF-8');
    console.log(param)
    req.send(param);
    req.onload =  _r(req,
        function(){_t('display',req.responseText );});
}
function _r(req,caller){
  return function (event){
    if (req.readyState === 4 && req.status === 200) 
      caller();
  };
}
function _f(str){
    return document.getElementById(str).value;
}
function _t(str,text){
    return document.getElementById(str).innerText=text;
}
function _k(str){
   return Object.keys(document.getElementsByTagName(str))
}
function _g(){
    console.log(_k('input'))
    return _k('input')
    .filter(a=>isNaN(a))
    .map(a=>`${a}=${_f(a)}`)
}


</script>

<body>
<div style="position:relative;left:7pt;padding:10pt;border: 1pt solid #2cbf88;width:600pt">
  <div style="position:relative;left:7pt;padding:10pt;border: 1pt solid #2cbf88;width:400pt">
      カテゴリ：
      <div style="position:relative;left:5pt;padding:5pt"> 
          <input type='text'id='exam_categori#1.category_code' size='50'>
      </div>
  </div>
  <div style="position:relative;left:7pt;padding:5pt;"></div>  
  <div style="position:relative;left:7pt;padding:10pt;border: 1pt solid #2cbf88;width:500pt">
    <div style="position:relative;left:5pt;padding:5pt">
        レベル1：
        <input type='text'id='exam#1.word' size='10'>
        <input type='text'id='exam#1.sentence' size='50'>
        <input type='hidden' id='exam#1.wlevel' value="1">
    </div>
    <div style="position:relative;left:5pt;padding:5pt">
        レベル2：
        <input type='text'id='exam#2.word' size='10'>
        <input type='text'id='exam#2.sentence' size='50'>
        <input type='hidden' id='exam#2.wlevel' value="2">
    </div>
    <div style="position:relative;left:5pt;padding:5pt">
        レベル3：
        <input type='text'id='exam#3.word' size='10'>
        <input type='text'id='exam#3.sentence' size='50'>
        <input type='hidden' id='exam#3.wlevel' value="3">
    </div>
  </div>
</div>
<input type='button' onclick='doPost();' value="登録">
<div id='display'></div>
```