月曜日までに考えておきます

ITネタとゲームネタ中心に興味のあること色々書きます。

RailsとJavascriptのレンダリングわからん

Railsのサーバサイドだけで解決している編

Railsで例えばこういうデータがあったとして、

positive_phrasesテーブル

text
頑張る人は報われる
ここで諦めちゃダメだ
君ならやれる
さあ、もう一度トライしよう

view側でこの文言を装飾したいとすると、

- @positive_phrases.each do |positive_phrase|
  tr
    td
      = in_social_game_context(positive_phrase.text)
def in_social_game_context(text)
  "#{text} ※ソーシャルゲームのガチャの話です"
end

こういうヘルパーを作ったら、以下のような表示がされます。

ここまでは楽勝ですね。

Javascript

より良いUXのためにデータをページ遷移無しで追加/更新できるようにするとか、コンテンツ量が多い時のpagenationを「次へ」ボタンを押させるのではなく、スクロールに応じて動的に追加していく(いわゆる無限スクロールってやつですね)などの要求が出てくるケースはよくあると思います。

そういうことをやろうとすると、JavascriptAPIをコールしてJSONで値を取得して要素を追加するというやり方になりますよね。 例えばこういう感じに。

positive_phrases/index.html.slim

input#new-phrase type="text"
button#submit-by-jquery
  | Create Phrase by jQuery

positive_phrases.coffee

  $("#submit-by-jquery").on("click", ->
    $.ajax(
      type: "POST"
      url: "/positive_phrases.json"
      data:
        positive_phrase:
          text:$("#new-phrase").val()
      success: (data) ->
        $("<tr><td>#{data.text}</td></tr>").appendTo($("#tbody"))
        #$("<tr><td>#{in_social_game_context(data.text)}</td></tr>").appendTo($("#tbody"))
      error: (a, b, c) ->
        alert("error")
      dataType: "json"
    )
  )

これで取得してきたJSONをHTMLに追加しようとした時に、Javascriptからin_social_game_contextというRailsのhelperは呼べないんですよね。

$("<tr><td>#{data.text}</td></tr>").appendTo($("#tbody"))
#$("<tr><td>#{in_social_game_context(data.text)}</td></tr>").appendTo($("#tbody"))  => これが呼べない

RailsJavascriptでダブルメンテという問題

まあこれぐらいならJavascript側でも"※ソーシャルゲームのガチャの話です"って追加してやるだけで解決するわけですが、以下のような二つの問題が生じます。

1つ目の問題

その後の仕様変更で「※ソーシャルゲームのガチャの話です」を「※ただしイケメンに限る」に変えましょう、という話になりました。

そういうときにRails側だけ変更して、Javascript側は「※ソーシャルゲームのガチャの話です」になったままで、いっけなーいって気づくようなことってよくあると思います。2重メンテを担保し続けるのはハード。

  inSocialGameContext = (text) ->
    "#{text} ※ソーシャルゲームのガチャの話です"
    
  ...
        success: (data) ->
        $("<tr><td>#{inSocialGameContext(data.text)}</td></tr>").appendTo($("#tbody"))
  ...

JS側で同じようなメソッドを作って呼んでやるが、仕様変更の対応を忘れて片方だけ直した、みたいになっていると

(Rails側のView)

            = but_only_handsome(positive_phrase.text)

JSで取得した部分だけ違う表示になってしまう。

2つ目の問題

片方を修正忘れする問題の以前に、Viewに書くhelperやdecoratorはこの例よりももっと複雑なケースが多いと思います。

特にRailsだと便利なGemで表示フォーマットを1メソッド呼ぶだけで複雑な整形を容易に実現している、というケースがあって、Javascriptだとそれを自前で実装しないといけないというケースは多いんじゃないかなと。

RailsJavascriptの両方で同じ表示テンプレートを用意してやるのが手間ですね。

と言っても

ダブルメンテつらいけどやるしかないのか?と思っているのが現状。

他に考えられる案を挙げて見ると、

  • JavascriptによるリッチなUXなんてやめましょう

=> それが本質的な解決策ならいいですが、そうじゃないケースが多いはず

=> レンダリングのテンプレート組み立ては一箇所で済むのですが、Javascriptだと表示してから描画されることになるのでSEO評価されない可能性がありそう(※)です。これまた受け入れてもらえないケースが多そうですね。

(※)そこでReactのサーバーサイドレンダリングですよ、的な話はちょっと脱線するのであえて触れません

そうなると、Railsでのレンダリング、JSでのレンダリングの両方やらなきゃいけないのがつらいところなのかなと。

remote: trueとjs.erbを使うという手段もありますが、こちらは以下のような問題があるのかなあと思っています。

  • formでしか使えない(?)
    • 今回の例で言うとアイテムの追加には対応できるけど、無限スクロールでの要素追加なんかには対応できなさそう
  • レンダリングしたDOMをHTMLに挿入する、という仕組みはjQueryではないちゃんとしたJSフレームワークとは相性が悪そう
    • vue.jsにModel管理させて、あとはtemplateで描画してほしいねん・・・

この辺り、皆さんどうやってうまく解決しているのか気になるところですね。