0

Background (changed after correspondance with @TomLord):

I am building a simple site with only a index page and no subpages. The index page has a "search" field that takes input (id) and sends a GET request to a RESTful API running in the background. The response from the API is JSON {"market price": "4306057.0", "retail price": "4995000.0"}.

I want to display this result on the index page, together with the search field. However, when I press the button the result is not displayed anywhere.


Code:

index.html.erb

<section id="search">
  <div class="container">
    <%= form_tag({controller: "model_request", action: "result"}, method: "get", remote: true) do %>
      <%= label_tag(:q, "Search for") %>
      <%= text_field_tag(:q, "913384637") %>
      <%= submit_tag("Get the price!") %>
    <% end %>
  </div>
</section>

<section id="display_result">
  <div class="container">
  </div>
</section>

And the modelrequest_controller.rb looks like this:

class ModelrequestController < ApplicationController

  def result
    id = params['q'].capitalize
    response = RestClient::Request.execute(
      method:  :get,
      url:     "http://0.0.0.0:80/?id=#{id}")
    @result = JSON.parse response

    respond_to do |format|
      format.js {render layout: false}
    end
  end

  end

My routes.rb

Rails.application.routes.draw do

  root 'index#index'

  match "/modelrequest", to: "modelrequest#result", via: 'get'

end

The javascript for results looks like this:

result.js.erb

$("#display_result").html("<%= escape_javascript(render partial: 'modelrequest/result', locals: { result: @result } ) %>");

And the simple _result.html.erb for displaying the partial is

<div id="display_result">
  <%= @result %>
</div>

Output:

Started GET "/model_request?utf8=%E2%9C%93&q=913384637&commit=Get%20the%20price!" for 127.0.0.1 at 2018-12-19 20:55:33 +0100
Processing by ModelRequestController#result as JS
  Parameters: {"utf8"=>"✓", "q"=>"913384637", "commit"=>"Get the price!"}
  Rendering model_request/result.js.erb
  Rendered model_request/_result.html.erb (0.8ms)
  Rendered model_request/result.js.erb (6.8ms)
Completed 200 OK in 383ms (Views: 11.6ms | ActiveRecord: 0.0ms)
tmo
  • 1,393
  • 1
  • 17
  • 47
  • You can define a `show.js.erb` (or `show.js.haml`), to **replace the contents of a container** (probably a `div`) with the search results. Something along the lines of: https://stackoverflow.com/a/35900332/1954610 – Tom Lord Dec 17 '18 at 13:49
  • Additionally, note the `remote: true` in the form. You want this to be an AJAX request, not a full page reload. – Tom Lord Dec 17 '18 at 13:50
  • Hi @TomLord, thanks for the input. I've edited my post and updated the code. – tmo Dec 18 '18 at 09:45
  • The idea is that you pace a "results `div`" on the index page. Let's call that `.results`. Then in the `show.js.haml`, you update the contents of this container. So something like: `$(".results").html("#{escape_javascript(......`. You can call this container whatever you like (`#main`?), but it needs to be present on that page. – Tom Lord Dec 18 '18 at 16:21
  • Also, in your case, it's the **form** that needs to send data remotely; there's no need for a `link_to` here. For example, see http://www.korenlc.com/remote-true-in-rails-forms/. We're not changing the way that your form works; we're only changing what gets rendered at the end. Originally, you were rendering a new page; now, we're replacing the contents of a `div`, on the same page. – Tom Lord Dec 18 '18 at 16:24
  • Dear @TomLord, thanks again! The `GET` method to the API now works, the index page is able to load and pressing the button actually results in a request to the API which responds correctly. Hurray! However, I am still not able to make the result appear correctly. It seems that it is looking for a template, despite `{render layout: false}` in the controller. I have changed the code in my question to a updated version. – tmo Dec 19 '18 at 14:40
  • Yes, it's looking for a template -- that's correct. You've named the controller `ModelrequestController`, which means it's looking for `app/views/modelrequest/result.js.erb`. However, you've named the file `app/views/model_requests/result.js.erb`. Either rename the folder, or rename the controller to `ModelRequestsController`. – Tom Lord Dec 19 '18 at 17:47
  • This is what the error message is telling you :) – Tom Lord Dec 19 '18 at 17:48
  • Great. Stupid mistake. Now everything seems to work, just need to figure out how to display the result in index.html.haml. If you write your comments up as an answer (just partially) I will be happy to accept it. Thanks! – tmo Dec 19 '18 at 19:59

1 Answers1

1

In a "normal" rails site, a search form works as follows -

You define a form in the view. This will, by default, POST data to an endpoint - but you can also do this via a GET request. In your case, you originally chose:

form_tag("/search", method: "get")

This will send the form data to GET /search, synchronously, and therefore perform a full page reload.

This is a perfectly valid thing to do. However, you wanted to remain on the index page - therefore the request needs to be asynchronous:

form_tag("/search", method: "get", remote: true)

...But now, that endpoint needs to do something different. Instead of rendering a new page, it needs to partially update the current page.

The standard approach for this in a rails application is to do something along the lines of:

# In the controller action, e.g. SearchController
def show
  @results = # ...

  respond_to do |format|
    format.js {render layout: false}
  end
end

# In the view, e.g. `search/show.js.erb`
$("#display_result").html("<%= escape_javascript(render partial: 'results', locals: { results: @results } ) %>");

# In an HTML partial, e.g. `search/_results.html.erb`
<% results.each do %>
  ...

The key idea is that on your main page (index.html.erb?), you must have a container to display the results. Then, rather than rendering a new page, you are merely rendering an HTML update inside that container.


This is, of course, not the only way of doing it. Modern websites will often instead fetch JSON data for a search result and then determine how to display that via JavaScript.

However, the above approach is the most "minimal" change from your current design - without needing to add additional design patterns, you can leave most of your existing haml/erb templates as-is, and just add a little code to render the results as a partial.

Tom Lord
  • 27,404
  • 4
  • 50
  • 77
  • Hi Tom! I've accepted your answer. I think I understand the overall structure, and I have made it work if I don't use the `respond_to do |format| ...` but simply call `@result` in with a `link_from(...` so the result pops up in a message when I click the link. However, rendering the partial still does not work, even though I am defining a container to display the result (see code above). The only difference I can see is `_results.html.erb` where mine is simply `
    <%= @result %>
    `. My suspicion is that my container is defined wrong in `index.html.erb`.
    – tmo Dec 21 '18 at 13:26
  • Just spent half an hour trying to figure out why my partial stopped working and it was this: "$("#display_result").html("... I deleted or changed the ID of the container rendering the js. *smacks forehead* Cheers! – Cody Nov 02 '19 at 02:07