【Rails】JavaScriptで書いた変数をコントローラーで利用する方法

【Railsでアプリ開発中】プログラミング初心者の勉強ブログ #34

こんにちは。

34日目。今回はJavaScriptからrailsコントローラーに渡した変数を使い回す方法を考えていきます。

はじめに

この記事は、railsフレームワークでオリジナルwebアプリ「免許学科試験学習サイト」作成中の僕が、プログラミングをしている中で気づいたことや学んだことを書いております。プログラミング初心者なので知識は少ないですが、現在通っているプログラミングスクール「DIVE INTO CODE」で学んでいることや、ネットで見つけた様々な記事を参考に記事を作成しております。

 

目次

[toc]

 

前回までの内容

AjaxでJavaScriptの変数をrailsコントローラーに渡す方法

「JSで動く一問一答機能」を免許アプリにどうしても実装したい僕は、前々回の記事(【Rails】AjaxでJavaScriptの変数をコントローラーに渡す①【DIVE INTO CODE】)でAjaxを使いJSからrailsコントローラーを叩き変数を渡すところまで成功しました。

しかし、渡した変数は通常railsのモデルから引っ張ってきた変数と同じように使うことができませんでした。

具体的にいうと、例えば「@user = User.new」の「@user」(Userモデルから新しいuserを生成)であれば、「@user.name」で「nameカラム」からuserの名前を取得できます。

一方で、Ajaxを使いJSからrailsコントローラーへ渡した変数は、後ろに「.name」をつけても反応しません。

またアソシエーションしたモデルからもデータを引っ張り出すことも無理です。

 

せっかく頑張って渡してきたのに、結局使いまわせないっていう。

「rails c」してコンソールで確認しても、ちゃんとデータは入っているんです。

JSから渡してこれたのは間違い無いんです。

 

もうどうしたらいいんだよ、ってなってしまったんですけど、

 

一回冷静になって考えてみました。

 

JavaScriptからrailsコントローラーへ渡した変数が使えない理由

なぜ同じように使えないか。

 

それは、

「モデルから生成した変数」と「JSから引っ張ってきた変数」が違うから。

何が違うかはわからないが、とにかく何かが違うから。

 

同じ中身が入っていても、何かが違うから、同じようにデータを取り出せない。

これしか考えられなかった僕は、「何が違うのか」を検証することにしました。

 

JavaScriptからrailsコントローラーに渡した変数を使い回す方法

とにかくやりたいことは、

  1. JSからrailsに渡してきた免許の問題データ(変数)を、もう一度viewで使いたい。
  2. viewで使い回して、ブラウザに「免許の問題」を繰り返し表示させたい。

です。

「取得」と「表示」です。

今回なんとか実装できたので、以下書いていきます。

 

1、コンソールで変数の中身を確認する

まず、何が違うのかを明らかにします。

そのためにコンソールでもう一度中身を確認します。

 

まず、普通にモデルから引っ張ってきた免許の問題たちです。

引っ張り方は、「@sample = Question.sample(5)」。

Questionモデルに入っている「免許の問題」を、ランダムに5つ@sampleに入れております。

 

コンソールを見ると、

@sampleの中のデータ

問題が綺麗に入ってます。

 

 

次に、JSからAjaxでなんとか引っ張ってきた問題たちを確認します。

コントローラー内で「params[:sample]」の形で取得できるようにしてます。

こちらもコンソールで見ると、

params[:sample]の中のデータ

上と比べて詰まってて見辛いですが、問題がちゃんと入っています。

 

(上と下ですが、抽出した問題が異なります。すみません。

今回言いたいのは、「どの問題が入っているか」は関係ないので違う回のsampleで抽出したものをのせてます。)

 

二つを見比べてわかること。

それは、「データの入れ方が違う」ということです。

最初みたとき、詰めて書いてあるか綺麗に並んで書いてあるかが違うだけで、中身は一緒だと思ってました。

 

ただよく見ると、

「@sample = Question.sample(5)」の方は配列で入っているのに対し、

JSから取得した「params[:sample]」は連想配列でデータが入ってます。

 

原因はここにありました。

だからいつもと同じように変数を使うことができなかったんです。

なのでここを解消していきます。

 

2、変数を使い回すためには

他にも方法はあるのかもしれませんが、僕が最初に考えたことは、

 

JSから渡してきた「params[:sample]」に入っているquestionのidを元に、

もう一回Questionモデルから「免許の問題」を引っ張ってくる、という方法。

 

スマートさには欠けますが、これで確実にアソシエーションもできるインスタンス変数を再生成してviewに渡せます。

 

controllers/itimon_ittous_controller.rb(抜粋)

def index
 samples = params[:samples] #jsから取得した問題データを格納
 count = params[:count] #jsから取得したカウントデータを格納(scriptタグ内でjsを経由するごとに1増える)
 questions = Question.all #一旦全ての問題をquestionに
 sample_array = [] #idのみを入れる配列を定義

 samples.each {|key, value|
  sample_id = value[:id]
  sample_array << questions.find(sample_id)
 }
 #連想配列「samples」のkey(インデックス番号)とvalue(問題のデータ)を分け、valueの中からidだけ取り出して配列にする。
 #eachを使うことで、samplesの配列数分のidを取り出す。

 @samples = sample_array #idだけの配列をインスタンス変数@samplesとしてviewに渡す。
 @count = count.to_i #countの中身のデータ型を文字列から数値に直す。
end

 

これでviewに変数を戻して使い回せるようになりました。

結構強引な気がします。知識がないとごり押しを始めます。

 

questions = Question.all
sample_array = []

samples.each {|key, value|
  sample_id = value[:id]
  sample_array << questions.find(sample_id)
 }

この部分、がっつりN+1問題とやらになってます。

 

最初「N+1問題」っていうものにいまいちピンときてなかったのですが、

実際に生じてみて、確かにクエリを「n+1回」発行することになるなと。

修正が必要ですが、リファクタリングは後回しする悪い癖が出ます。

ていうか全部作ったら一個一個確認します。

 

3、変数に入っている情報でRailsのviewを書き換える

コントローラーからindexアクションでviewへ変数を渡せてたので、

それを使ってviewを書き換えていきます。

今回はjQueryの「.html」メソッドを使い書き換えを行います。

また、初回と2回目以降のアクションを「indexアクション」一つに統一させ、条件分岐で記述しました。

 

まず、書いたコードです。

 

controllers/itimon_ittous_controller.rb

def index

    gon.fields = Field.all

    if params[:count] == nil #初回のアクション定義
      questions = Question.all 
      @samples = questions.sample(5)
      @count = 0 #最初はカウントゼロ。

      gon.samples = @samples
      gon.count = @count

      gon.result = {} #正誤判定用

    else #以下から2回目以降のアクション

      samples = params[:samples] #jsから取得した問題データを格納
      count = params[:count] #jsから取得したカウントデータを格納(scriptタグ内でjsを経由するごとに1増える)
      questions = Question.all #一旦全ての問題をquestionに
      sample_array = [] #idのみを入れる配列を定義

      samples.each {|key, value|
        sample_id = value[:id]
        sample_array << questions.find(sample_id)
      }
    #連想配列「samples」のkey(インデックス番号)とvalue(問題のデータ)を分け、valueの中からidだけ取り出して配列にする。

      @samples = sample_array #idだけの配列をインスタンス変数@samplesとしてviewに渡す。
      @count = count.to_i #countの中身のデータ型を文字列から数値に直す。
    end
end

 

views/itimon_ittous/index.html.erb

<script> #「次の問題へ進む」ボタンを押したときの定義のみ抜粋。「nextQuestion」というクラスを当ててます。
  $(function(){
    $('.nextQuestion').click(function() {
      $('body').append('<div class="modal-overlay1"></div>');
      $('body').append('<div class="modal-overlay2"></div>');
      $('#questionModal').fadeOut();
      $('.modal-overlay1').fadeOut(function(){
        $('.modal-overlay1').remove();
      });
      $('#answerModal').fadeOut();
      $('.modal-overlay2').fadeOut(function(){
        $('.modal-overlay2').remove();
      });
      $('.seikai').fadeOut(function(){
        $('.seikai').remove();
      });


      gon.count += 1; #カウントを1増やす
      var num = gon.count + 1;

      $.ajax({
        url: '/itimon_ittous/index',
        type: 'GET',
        dataType: 'html',
        async: true,
        data: {
          count: gon.count, 
          samples: gon.samples
        },#ここでコントローラに渡す値を定義
      });

      $('#mondai_num').html(num + '/' + gon.samples.length); #viewから渡した変数でhtmlの書き換え
      $('#field').html('分野:' + gon.fields[gon.samples[gon.count].field_id].field_name); #viewから渡した変数でhtmlの書き換え
      $('#question').html(gon.samples[gon.count].content); #viewから渡した変数でhtmlの書き換え

      if (num === gon.samples.length) {
        $('.nextQuestion').hide();
        $('.resultView').html('結果を表示');
      }; #最後の問題の時に切り替える定義

      modalResize();
      $('#questionModal').fadeIn();
      $(window).on('resize', function(){
          modalResize();
      });
      function modalResize(){
          var w = $(window).width();
          var h = $(window).height();
          var x = (w - $('#questionModal').outerWidth(true)) / 2;
          var y = (h - $('#questionModal').outerHeight(true)) / 4;
          $('#questionModal').css({'left': x + 'px','top': y + 'px'});
      }
    });
  });

  #省略

</script>

これで画面遷移のない一問一答ができます。

 

実際のブラウザ画面です。

1/5、2/5・・・と、しっかり問題が書き換えられてます。

正解か不正解か判断し、表示も正しくできてます。

成果物1

 

最後の問題まで終わると、結果画面が表示されます。

成果物2

 

まあちょっとは免許の勉強できそうです。

 

 

まとめ

今回でやっと「一問一答機能」が完成に近づいてきました。

最近は「自分で課題を解決できる力」をつけるため、メンターさんにあまり質問しないようにしてます。

やたら時間かかってしょうがないですが、これでいいんじゃないかと思ってます。

 

この「一問一答機能」を実装していく中で、DICテキストを見直すことが多いのですが、

だいたい見直すカリキュラムは「Ruby入門」か「JavaScript入門」です。

 

配列やハッシュの操作、データ型、変数について、

メソッドの使い方でも「get」と「post」の違いなど、

開発していく上で、基本の基本の理解を求められる機会が多いです。

 

railsのレールから少し外れたことをするとき、これらの基本の理解が曖昧だと、

問題が何なのか本当にわからなくなります。

 

アプリ開発はアウトプットチャンスばかりなので、

逃さず一個一個復習できればと思います。

 

以上ありがとうございました。

保存保存

返信を残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA