32756
どのように実装すればよいでしょうか  
言語はjavascript(es6可能)です。  
目的は、modelの情報と表示したいcolumn指定から、modelの関連情報(has_manyなものやbelongs_toなもの含め）いい感じにtableタグを出力するためのviewModelを得ることです。

```

    // can use es6
    function makeTbodyModel(columns, model) {
      // pls implement
    }
    
    columns = [ 'name', 'age', { 'friends': [ 'name' ] }, 'gender' ];
    model = {
      name: taro,
      age: 8,
      friends: [
        {
          name: jiro,
          age: 7,
          friends: [/*anydata*/],
          gender: male
        },
        {
          name: hanako,
          age: 12,
          friends: [/*anydata*/],
          gender: female
        }
      ],
      gender: male
    };
    
    makeTbodyModel(columns, model); /* => 下記の返り値が得られることを期待しています
    [
      [
        { value: taro, rowSpan=2 },
        { value: 8,    rowSpan=2 },
        { value: jiro, rowSpan=1 },
        { value: male, rowSpan=2 },
      ],
      [
        { value: hanako rowSpan=1 },
      ]
    ] */
    
```
CC BY-SA 3.0
2017-02-14T17:34:07.807
32674
2017-02-17T18:06:48.690
427
question
javascript
Modelと表示したいColumns指定から、HTMLのTableのTbody用のViewModelを生成する関数
しばらく自分で頑張ってみた結果、完全ではないですが実装できたので書いておきます。  
すこし、質問のときとテストデータやメソッド名が違いますが、本質は変わらないのでそのまま掲載します.

```

    // 非破壊のinsert
    // returns insertされたあとのarray
    function arrayInserted(array, toIndex, ...elements) {
      let clone = array.concat([]);
      clone.splice(toIndex, 0, ...elements);
      return clone;
    }
    
    function buildTRecordsForOneModel(columns, model) {
      let attrs = columns.filter(e => e.constructor == String).map(e => {
        return {value: model[e]};
      });
      let hasManyAssoc = columns.filter(e => e.constructor == Object)[0] || null;
    
      let hasManyModels; 
      let recordsForOneModel;
      if(hasManyAssoc != null) {
        let assocName = Object.keys(hasManyAssoc)[0];
        let subColumns = hasManyAssoc[assocName];
        hasManyModels = buildTRecordsForManyModels(subColumns, model[assocName]);
        recordsForOneModel = hasManyModels.map((record, index) => {
          if(index === 0) {
            attrs.forEach(attr => {
              attr.rowSpan = hasManyModels.length
            });
            return arrayInserted(attrs, columns.indexOf(hasManyAssoc), ...record);
          }else {
            return record;
          }
        });
        //console.log(`recordsForOneModel: ${JSON.stringify(recordsForOneModel)}`);
        if(recordsForOneModel.length === 0) {
          attrs.forEach(attr => attr.rowSpan = 1);
          recordsForOneModel = [arrayInserted(attrs, columns.indexOf(hasManyAssoc), {value: null, rowSpan: 1})];
        }
      }else {
        attrs.forEach(attr => attr.rowSpan = 1);
        recordsForOneModel = [attrs];
      }
      //console.log(`model: ${JSON.stringify(model)}`);
      //console.log(`cols: ${JSON.stringify(columns)}`);
      //console.log(`attrs: ${JSON.stringify(attrs)}`);
      //console.log(`hasManyModels: ${JSON.stringify(hasManyModels)}`);
      //console.log(`recordsForOneModel: ${JSON.stringify(recordsForOneModel)}`);
      return recordsForOneModel;
    }
    
    function buildTRecordsForManyModels(columns, models) {
      let recordsForManyModels = [];
      for(let model of models) {
        recordsForManyModels = recordsForManyModels.concat(buildTRecordsForOneModel(columns, model));
      }
      //console.log(`recordsForManyModels: ${JSON.stringify(recordsForManyModels)}`);
      return recordsForManyModels;
    }
    
    let columns = [ 'name', 'age', { friends: [ { friends: ['name'] }, 'name' ] }, 'gender' ];
    let models = [{
      name: 'taro',
      age: 8,
      friends: [
        {
          name: 'jiro',
          age: 7,
          friends: [],
          gender: 'male'
        },
        {
          name: 'hanako',
          age: 12,
          friends: [
            { name: 'kumiko' }, { name: 'togeko' }
          ],
          gender: 'female'
        }
      ],
      gender: 'male'
    }];
    
    let result = buildTRecordsForManyModels(columns, models);
    
    console.log(result.map(i=> i.map(j => JSON.stringify(j)).join(' | ')).join("\n"));
    console.log(`html render example -> \n<table>\n${result.map(tr => `  <tr>\n${tr.map(td => `    <td rowspan=${td.rowSpan}>${td.value}</td>`).join("\n")}\n  </tr>`).join("\n")}\n</table>`);
    
```

実行結果

```

    {"value":"taro","rowSpan":3} | {"value":8,"rowSpan":3} | {"value":null,"rowSpan":1} | {"value":"jiro","rowSpan":1} | {"value":"male","rowSpan":3}
    {"value":"kumiko","rowSpan":1} | {"value":"hanako","rowSpan":2}
    {"value":"togeko","rowSpan":1}
    html render example -> 
    <table>
      <tr>
        <td rowspan=3>taro</td>
        <td rowspan=3>8</td>
        <td rowspan=1>null</td>
        <td rowspan=1>jiro</td>
        <td rowspan=3>male</td>
      </tr>
      <tr>
        <td rowspan=1>kumiko</td>
        <td rowspan=2>hanako</td>
      </tr>
      <tr>
        <td rowspan=1>togeko</td>
      </tr>
    </table>
    
```

## 問題点

同階層のモデルに２つ以上のhasMany関連がcolumnsで指定された場合、ｈｔｍｌのtableとしてrendering不可能であると気づきました。よって、hasMany関連は一つまでという制約を課さなくてはいけません。  
上記コードはbelongsTo|hasOne関連に対応してません。しかし、対応するとなると、関連先にhasMany関連が指定されている、belongsTo|hasOne関連が複数ある場合も、rendering不可能になってしまうので結構難しそうです。
CC BY-SA 3.0
2017-02-17T18:06:48.690
32756
2017-02-17T18:06:48.690
427
32674
answer