0

Rendering JS in Rails

Ruby on Rails isn’t extremely JS friendly by nature (or even async friendly). However, there are some lesser-known tricks where you can use JavaScript in a Rails-friendly manner.

Lets start off with a controller with index, create, and destroy routes to read and manipulate Thing objects:

class ThingController < ApplicationController
  def index
    @things = Thing.all
  end

  def create
    @thing = Thing.create(thing_params)
  end

  def destroy
    @thing = Thing.find params[:id]
    @thing.destroy
  end

  private
  
    def thing_params
      params.require(:thing).permit(:name, :description)
    end
end

We can use the index route to render our main view. For this example, we’ll use HAML.

#thing-list-wrapper
  - @things.each do |thing|
    .thing
      %h1= @thing.name
      %p= @thing.description

.form-wrapper
  = form_for :things, url: thing_path, remote: true do |form|
    = form.text_field :name
    = form.text_area :description
    = form.submit

This view will render a set of things, with names in a <h1> tag and their descriptions in a <p> tag. 

But lets say that instead of having a formal form submission to a POST route and a redirect to a “success” page, lets asynchronously call the create POST route and update the index view.

Notice the remote: true option the form_for helper method. This will make sure any submission POST requests will be handled asynchronously.

This option tells Rails not to follow any submit button redirects.

In this example, we’ll be expecting JS from our create (and later destroy) routes that will manipulate our index view. Lets indicate that in the controller. 

class ThingController < ApplicationController
  respond_to :js, only: [:create, :destroy]
  ...
end

Then in app/views/things/create.js.haml we can append the created @thing to our #thing-list-wrapper element:

$('#thing-list-wrapper').append("<div class='thing'><h1>#{j @thing.name }</h1><p>#{j @thing.description }</p></div>");

Boom. We just handled async resource creation and updated our view to show it.

Similarly, we can handle the destroy route in the same manner. Lets update our view to add a delete button and an identifier that we can use to handle the async destroy. In app/views/things/index.html.haml:

...
    .thing{ data: { thing_id: @thing.id } }
      %h1= @thing.name
      %p= @thing.description
      = link_to 'Delete', thing_path, method: :delete, remote: true
...

Again, the remote: true option in the link_to helper will tell Rails that we don’t want user’s to follow the link and instead handle this as an async request. 

We also added a data-thing-id attribute to the wrapping .thing div so we can identify the element to remove.

In app/views/things/destroy.js.haml we can find the .thing element and remove it from the DOM.

$('.thing[data-thing-id=#{ @thing.id }]').remove();

Thats it! We just handled creating and destroying database resources and their corresponding DOM elements.

Efrem Roberson

Leave a Reply

Your email address will not be published. Required fields are marked *