Haroon Ahmed

Reactions

16 Nov 2017

I wrote a little side project in Ruby on Rails to learn more about Server-side Javascript Rendering or SJR for short.

Lot’s of companies have been using react.js, angular.js, vuejs or something else for a while now. DHH mentioned how basecamp use a sprinkle of javascript everywhere and for the few client-side intensive pages, they use backbone.js. I also read how shopify got rid of their client side framework batman.js in favour of following a similar path to basecamp. GitHub looks like they use pjax. It looks like SJR is actually really popular.

So why are so many companies using a client side framework, are they getting some insane traffic? I assume they all must be high fidelity UI. Looking at the rails documentation, the importance of SJR really is downplayed.

This rails app was built on edge rails, backed by postgresql. I retrieve the latest news articles using the New York Newswire API via an active_job in the background, (annonymous) users can then post their reactions to the article. Users can either add a comment or emoji from the options available. SJR was used to save comments via ajax on the server then display the comment at the top of the comments list or save the reaction via ajax then update the reaction count for the said emoji. Let’s take a look at the code that makes this happen.

View: Articles#show.html.erb

<%= form_with(url: article_reactions_path(@article), scope: :reaction, class: 'form-inline my-2 mr-auto') do |form| %>
  <%= form.text_field :content, class: 'form-control mr-sm-2',                placeholder: 'Your reaction...', id: 'comment-input' %>
  <%= form.submit 'Submit', class: 'btn btn-outline-success my-2 my-sm-0' %>
<% end %>

We use a using form_with which under the hood adds date-remote="true" to our form, which allows submits to our form via ajax.

Controller: ReactionsController

def create
    @reaction = @article.reactions.new(reaction_params)
    @reaction.reaction_type = 'text'
respond_to do |format|
      if @reaction.save
        format.html { redirect_to article_url(@article), notice: 'Your reaction has been created successfully.' }
        format.js
        format.json { render json: @reaction, status: :created, location: @reaction }
      else
        format.html { redirect_to article_url(@article), error: 'Error saving your reaction.' }
        format.js
        format.json { render json: @reaction.errors, status: :unprocessable_entity }
      end
    end
  end

The controller creates the reaction using the reaction_params, then updates the reaction_type to text for this reaction type (reaction can be text or emoji). Finally the controller responds to the request, if the object is saved successfully or not.

format.html — what the standard rails response procedure, we redirect and add a notice to the page.

format.json — json response, for client interactive sites, which is what you want if you are using a client side library. We send back a json object of the reaction and a created status to let the client side library know the request was successful.

format.js — this response uses SJR, so instead of redirecting a request or sending back a json object, rails calls the corresponding actions .js.erb in this case it would be create.js.erb which then handles the response to the client.

View: create.js.erb

<% if @reaction.errors.any? %>
  $('#notice').show();
  $("#comment-input").focus();
<% else %>
  // add comment to comments list
  $("<%= escape_javascript(render @reaction) %>").prependTo("#comments_list");
  $("#comment-input").val("").focus();
<% end %>

In this response we are checking if there was an error with the reaction model, if there was — show a nice looking notice on the page and focus the cursor on the comments input box. If the model was saved successfully, our next step is to add the comment to the dom. We escape the javascript and render the reaction model — this uses reactions/_reactions.html.erb to generate the html which is then prepended to the top of the comments list. So we see that new comment right at the top. Finally we clear out the input and focus on the comment input, so you can add your next comment. Collectively this creates a very responsive page, I was really impressed with how it all glued together. Now finally how do we test this thing?

Test: ReactionsControllerTest.rb

test "should save comment using SJR" do
  assert_difference('Reaction.count', +1) do
   post article_reactions_url(@article), xhr: true, params: {  reaction: { content: 'something' }  }
  end
  assert_response :success
  assert_equal "text/javascript", @response.content_type
  reaction = Reaction.last
  assert_equal 'text', reaction.reaction_type
  assert_equal 'something', reaction.content
  assert_equal 2, Reaction.comments.count
  assert_select_jquery :prependTo, '#comments_list' do
    assert_select '.reaction-content', reaction.content
  end
end

We add xhr: true to the post request to make an ajax request and assert that a new article has been created using assert_difference. We load the last reaction and verify the text and content are correct. Then assert_select_jquery basically verifies the jquery code we call inside create.js.erb $(“<%= escape_javascript(render @reaction) %>”).prependTo(“#comments_list”); checking if the new dom element was added successfully to the #comments_list and checking if the content is the same as the reaction content.

You can go ahead and check out the code below to see all this in action.

https://github.com/hahmed/reactions

I used angular.js 1.x at Surventrix when I created a couple of client intensive pages, on one of the pages users were required to fill in a list of fields which would be merged into a word document which could take upto 2 hours to complete. Showing incomplete/complete fields easily, saving fields with responsive feedback, showing errors for each field, maintaining where the users were on the page after filling out a field, auto saving every field entry (quite an issue with one of our competitors — hours of work was lost). So I had a fair bit of knowledge about client side frameworks, I even tried out knockout.js too some time ago.

Was real nice trying out a built in rails solution, which I felt was quick to get used to, unit testing was real easy to do — no extra frameworks or tools. Shopify mentioned one of the reasons for moving to SJR helped the team get up to speed quickly with the repo. Developer productivity is a thing! Anyway looking forward to try out react.js and how well it works with the rails framework.