skip to Main Content

My Latest Project: Adding AJAX to my Rails Project

For my latest project with Flatiron, I was required to add AJAX features to my previous Ruby on Rails project. I found this a bit awkward because I built my previous project (Champion: A Playbook Manager for Football Coaches) without thinking about JavaScript at all, since I knew no JavaScript at the time. So, I took the approach that I just needed to fulfill the project requirements and go on from there. So, it felt a bit “bolted on” and not something I would actually do on a real project or in the future. That said, I did learn a lot and I did meet the requirements.

So, let me walk you through the requirements and how I met them. I will also note a few questions that lingered as I worked on this.

First Requirement

Must translate JSON responses from your Rails app into JavaScript Model Objects using either ES6 class or constructor syntax. The Model Objects must have at least one method on the prototype. (Formatters work really well for this.)

I did this a couple of times in my reworking of the code. Here is one example:

 class Playbook {
  constructor(name, description, situation) {
  this.name = name;
  this.description = description;
  this.situation = situation;
  }
 ​
   htmlForTd() {
     return this.description + “<br/>” +
     this.situation;
    }
 }

This class is for holding playbook information. The constructor populates the properties of the instance with variables drawn from the database. The htmlForTd is a prototype method that returns a formatted string that can be placed in a table element for the website. I use ES6 class syntax to create the object, constructor and prototype method. I did something very similar for the User class in user.js:

 class User {
     constructor(user) {
       this.id = user.id;
       this.name = user.name;
       this.email = user.email;
    }
 ​
 format() {
       let html = `<h5>${this.name}</h5><p>${this.email}</p>`;
       return html;
    }
 }

Second Requirement

Must render at least one index page (index resource – ‘list of things’) via JavaScript and an Active Model Serialization JSON Backend.

So this requirement involved two steps. First, setting up an AMS in the Rails server. I hadn’t set those up previously, so I needed to add them. Here is one example from playbook_serializer.rb:

 class PlaybookSerializer < ActiveModel::Serializer
   attributes :id, :name, :description, :situation
 end

Here I create a serializer for the playbook data object that includes four attributes, and nothing more. That means my Rails server will respond to JSON requests from my AJAX frontend with only the data I want. So, in my JavaScript I have:

 $('a:contains("Playbooks")').on('click', function(e) {
  e.preventDefault();
  fetch(`/users/${this.dataset.id}/playbooks.json`)
  .then(res => res.json())
  .then(json => {
    let html = `<div id='main-content'><h1>Your Playbooks</h1><table class='table'><thead><tr><th scope='col'>Name</th><th scope='col'>Description</th></tr></thead><tbody>`;
 ​
    json.forEach(e => {
      const pb = new Playbook(e);
      html += makeRowAndTdforPlaybook(pb, this.dataset.id);
    });
     
   html += `</tbody></table><a href="/users/${this.dataset.id}/playbooks/new">Create new playbook</a>`;
    $('#main-content-area').empty();
    $('#main-content-area').append(html);
  });
 });

In this code, we use jQuery to grab the anchor with “Playbooks” in it’s name. This is the link to the index page for all the playbooks. We then make a fetch request that gets the playbooks from the rails backend, using the previously described serializer. The JSON is parsed, and then iterated over to create new Playbook objects and use their prototype functions to format HTML which is finally inserted into the DOM. All of this is done with AJAX(via fetch) and no page reloading.

Third Requirement

Must render at least one show page (show resource – ‘one specific thing’) via JavaScript and an Active Model Serialization JSON Backend.

This is similar to the previous requirement, but focused on a show instead of an index. I have a list of playbooks (created with the code above) so I showed the rest of the playbook data in the list via an AJAX call – so when you click on the link to the particular playbook, the relevant data is shown right there in the table:

 function getPlaybookDataFromUrl(url) {
     fetch(url + '.json')
    .then(res => res.json())
    .then(data => {
       const playbook = new Playbook(data);
       const playbook_id = "td#" + playbook.name.toLowerCase().replace(' ', '-') + '-description';
       $(`${playbook_id}`).empty();
       $(`${playbook_id}`).append(playbook.htmlForTd());
    });
 }

This is the fetch request – it accepts a url for the particular playbook, then gets it, appending ‘.json’ so our rails server will user our serializer. A new playbook instance is created and it’s formatter is called to insert the data, properly formatted, into the table. All done via AJAX and AMS.

Fourth Requirement

Your Rails application must dynamically render on the page at least one serialized ‘has_many’ relationship through JSON using JavaScript.

This was where my project started to feel really strange – like I would never have done things the way I had done them in the first place. The original app was all Ruby on Rails. I would have designed the app to be an API – the rails all back end and the front end put together by JS calls to the server. I think that would have made more sense. In any event, to fulfill this requirement, I turned to my user model.

The way the JS below works: I catch a click on the “Profile” anchor. My user has many games, so when I display the user profile information, I display the many games associated with that user through its serializer. The content is then appended to the DOM with formatters:

 $('a:contains("Profile")').on('click', function(e) {
     e.preventDefault();
 ​
     fetch(`/users/${this.dataset.id}`)
    .then(res => res.json())
    .then(json => {
       const user = new User(json);
       const games = json.games;
       let html = `<div id='main-content'><h1>${user.name}'s Profile</h1>`;
      html += `</div>`;
       $('#main-content-area').empty();
       $('#main-content-area').append(html);
       $('#main-content').append(user.format());
       $('#main-content').append('<h2>Your Games</h2>');
       games.forEach(g => {
         let game = new Game(g);
         $('#main-content').append(game.format());
      });
    });
  });

The relevant serializers, back on the server, looks like this:

 class UserSerializer < ActiveModel::Serializer
   attributes :id, :name, :email
 ​
   has_many :games
 end
 ​
 class GameSerializer < ActiveModel::Serializer
   attributes :id, :date, :location
   
   has_one :opponent
   belongs_to :user
 end

Though this felt contrived, I hope it meets the technical requirement.

Requirement Five

Must use your Rails application to render a form for creating a resource that is submitted dynamically and displayed through JavaScript and JSON without a page refresh.

For this final requirement, I catch the submit button for creating a new playbook. I then serialize the values in the form so that I can submit them as an AJAX request. My fetch request sends a POST request with the serialized values. Finally, the response from the server is parsed and the new playbook is placed in the DOM – through a playbook object and formatter prototype function.

   $('#new_playbook').on('submit', function(e) {
     e.preventDefault();
     
     const values = $(this).serialize();
     fetch('/playbooks', {
       method: 'POST',
       headers: {
         'Content-Type': 'application/x-www-form-urlencoded',
      },
       body: values,
    })
    .then(response => response.json())
    .then(json => {
       const playbook = new Playbook(json);
       $('#main-content-area').html('');
       $('#main-content-area').append(playbook.show());
    });
  });

Questions that Lingered

  • Where does validation fit in? Should I do validation in Rails or JavaScript? The Champion app had lots of validation built in, but it seems my AJAX calls bypass all the validation.
  • It seems that the Rails asset pipeline simply loads your JavaScript – no matter what the files are named. Can’t this result in serious problems with scope? Is it possible to only have certain JS files load for certain pages?

All in all, I learned an awful lot on this project. Mainly, I learned that I would never do things this way again!! Apps should consider all the tools that they will employ as they are designed – not after the fact. In the future, I suppose, I will consider the front end and the back end technologies together.