Friday, 26 December 2025

Web Tutorial: Ruby On Rails Xmas Poll (Part 4/4)

We'll, of course, want to find a way to view the poll results. To do that, let's first go back to Oracle APEX and create an endpoint for this. Remember the template we created in the first part of this web tutorial? It was called /poll/:id/results/.

Then we create a GET handler for this.


And this is the Pl/SQL code. Do bear in mind that here, averages and medians only make sense because the answers in the poll are numerical. In any case, tables are joined and columns are grouped to get these averages and medians. Before using the AVG() and MEDIAN() functions, we must first run TO_NUMBER() on the data, to convert them to numerical values.
SELECT
pq.TITLE,
AVG(TO_NUMBER(pr.RESULT)) AS AVG_RESULT,
MEDIAN(TO_NUMBER(pr.RESULT)) AS MEDIAN_RESULT
FROM POLL_QUESTIONS pq LEFT JOIN POLL_RESULTS pr
ON pr.QUESTION_SERIAL_NO = pq.SERIAL_NO AND pr.POLL_ID = :id AND pq.POLL_ID = :id
GROUP BY pq.TITLE
ORDER BY AVG_RESULT DESC, MEDIAN_RESULT DESC


The results should be something like this.
{
 "items": [
  {
   "title": "IT CAME UPON THE MIDNIGHT CLEAR",
   "avg_result": 4,
   "median_result": 5
  },
  {
   "title": "JINGLE BELL ROCK",
   "avg_result": 4,
   "median_result": 4.5
  },
  {
   "title": "SILENT NIGHT",
   "avg_result": 3.5,
   "median_result": 3
  },
  {
   "title": "THE FIRST NOEL",
   "avg_result": 3.16666666666666666666666666666666666667,
   "median_result": 3
  },
  {
   "title": "JINGLE BELLS",
   "avg_result": 3,
   "median_result": 3
  },
  {
   "title": "HARK THE HERALD ANGELS SING",
   "avg_result": 2.83333333333333333333333333333333333333,
   "median_result": 3
  },
  {
   "title": "DECK THE HALLS",
   "avg_result": 2.66666666666666666666666666666666666667,
   "median_result": 3
  },
  {
   "title": "JOY TO THE WORLD",
   "avg_result": 2.54545454545454545454545454545454545455,
   "median_result": 3
  },
  {
   "title": "RUDOLPH THE RED-NOSED REINDEER",
   "avg_result": 2.5,
   "median_result": 2.5
  },
  {
   "title": "I SAW MOMMY KISSING SANTA CLAUS",
   "avg_result": 2.27272727272727272727272727272727272727,
   "median_result": 2
  }
 ],
 "hasMore": false,
 "limit": 25,
 "offset": 0,
 "count": 10,
 "links": [
  {
   "rel": "self",
   "href": "https://oracleapex.com/ords/teochewthunder/polls/poll/1/results/"
  },
  {
   "rel": "describedby",
   "href": "https://oracleapex.com/ords/teochewthunder/metadata-catalog/polls/poll/1/results/"
  },
  {
   "rel": "first",
   "href": "https://oracleapex.com/ords/teochewthunder/polls/poll/1/results/"
  }
 ]
}


Back to the Rails server! In the CLI, run this command to create the controller result.
rails generate controller result

Once we've done that, it's time to modify the generated file. Again, we need httparty. We'll reuse the environment variable ORDS_API_URL but append "/results" to it.

app/controllers/result_controller.rb
require "httparty"

class ResultController < ApplicationController
    ORDS_API_URL = ENV["ORDS_API_URL"] + "/results"

    def index

    end

end

Of course, the next step is to mirror what we did for the index action of the root page, and use HTTParty's get() method to call the URL endpoint, then have an If block to handle the result.

app/controllers/result_controller.rb
require "httparty"

class ResultController < ApplicationController
    ORDS_API_URL = ENV["ORDS_API_URL"] + "/results"

    def index
        response = HTTParty.get(
            ORDS_API_URL
        )

        if response.code == 200


        else

            flash.now[:alert] = "Error fetching data."
            @api_data = {}
        end
    end
end

If successful, we return the result to the view by binding it to api_data.

app/controllers/result_controller.rb
require "httparty"

class ResultController < ApplicationController
    ORDS_API_URL = ENV["ORDS_API_URL"] + "/results"

    def index
        response = HTTParty.get(
            ORDS_API_URL
        )

        if response.code == 200
            @api_data = response.parsed_response
        else
            flash.now[:alert] = "Error fetching data."
            @api_data = {}
        end
    end
end

This is the view. We have a header, a div styled using the CSS class pollresult, and a table.

app/views/result/index.html.erb
<h1>Poll Results</h1>
<div class="pollresult">
  <table>
    <tr>
      <td width="300px"><b>Carol</b></td><td width="150px" style="text-align: right;"><b>Average Rating</b></td>
      <td width="150px" style="text-align: right;"><b>Median Rating</b></td>
    </tr>
  </table>
</div>

Refer to the sample JSON result I showed you earlier. We'll use the title, avg_result and median_result properties here.

app/views/result/index.html.erb
<h1>Poll Results</h1>
<div class="pollresult">
  <table>
    <tr>
      <td width="300px"><b>Carol</b></td><td width="150px" style="text-align: right;"><b>Average Rating</b></td>
      <td width="150px" style="text-align: right;"><b>Median Rating</b></td>
    </tr>

    <% @api_data["items"].each do |result| %>
      <tr>
        <td><%= "#{result['title']}" %></td>
        <td style="text-align: right;"><%= "#{result['avg_result']}" %></td>
        <td style="text-align: right;"><%= "#{result['median_result']}" %></td>
      </tr>
    <% end %>

  </table>
</div>


For the CSS, if you're a lazy bastard like me, you might just want to piggyback off this existing class, or create a new one with a custom design.

app/assets/stylesheets/application.css
.pollform, .pollresult
{
  width: 600px;
  padding: 10px;
  border-radius: 10px;
  border: 3px solid rgb(200, 0, 0);
  background-color: rgb(0, 200, 0);
  margin: 5% auto 0 auto;
}


Do you see the problem? The average is way too long and needs to be truncated.

We use sprintf(), passing in "%.1f" to truncate the text to one decimal place.

app/views/result/index.html.erb
<h1>Poll Results</h1>
<div class="pollresult">
  <table>
    <tr>
      <td width="300px"><b>Carol</b></td><td width="150px" style="text-align: right;"><b>Average Rating</b></td>
      <td width="150px" style="text-align: right;"><b>Median Rating</b></td>
    </tr>

    <% @api_data["items"].each do |result| %>
      <tr>
        <td><%= "#{result['title']}" %></td>
        <td style="text-align: right;"><%= "#{sprintf('%.1f', result['avg_result'])}" %></td>
        <td style="text-align: right;"><%= "#{sprintf('%.1f', result['median_result'])}" %></td>
      </tr>
    <% end %>
  </table>
</div>


There you go!


For a final touch, add a nav link to each of these pages.

app/views/poll/index.html.erb
<h1><%= @api_data["items"][0]["name"] %></h1>
<h2><%= link_to "View Results", result_page_path %></h2>
<div class="pollform">


app/views/result/index.html.erb
<h1>Poll Results</h1>
<h2><%= link_to "View Poll", root_path %></h2>
<div class="pollresult">


Then style the h2 tag.

app/assets/stylesheets/application.css
h1
{
  text-align: center;
  color: rgba(255, 255, 255, 0.5);
}


h2
{
  text-align: center;
  font-size: 0.8em;
}


.pollform, .pollresult
{
  width: 600px;
  padding: 10px;
  border-radius: 10px;
  border: 3px solid rgb(200, 0, 0);
  background-color: rgb(0, 200, 0);
  margin: 5% auto 0 auto;
}






Unit Tests

It's time to write a few unit tests! Bear in mind that these are just samples. There are probably better tests one can write. This is the default test that is generated when we create a controller.

test/controllers/poll_controller_test.rb
require "test_helper"

class PollControllerTest < ActionDispatch::IntegrationTest
    test "should get index" do
        get poll_page_url
        assert_response :success
        assert_not_nil assigns(:api_data)
    end
end


And more. Here, we test submitting the form by sending a sample payload, and checking a couple of expected behaviors. We check if there's a redirect to the form page, and that there's a flash notice.

test/controllers/poll_controller_test.rb
require "test_helper"

class PollControllerTest < ActionDispatch::IntegrationTest
    test "should get index" do
        get poll_page_url
        assert_response :success
        assert_not_nil assigns(:api_data)
    end

    test "should submit results" do
        post submit_poll_form_url, params: { answers: { "1" => "3", "2" => "5", "3" => "2"} }
        assert_response :redirect
        assert_redirected_to root_path
        assert_not_nil flash[:notice]
    end
end

The result controller test should look like this as well.

test/controllers/result_controller_test.rb
require "test_helper"

class ResultControllerTest < ActionDispatch::IntegrationTest
    test "should get index" do
        get result_page_url
        assert_response :success
        assert_not_nil assigns(:api_data)
    end
end


When you run these tests, that's what you should get.




Conclusion

Looks like it's the day after Christmas.

This concludes my first Ruby On Rails web tutorial in years. I like to think it's better than my last couple efforts. That's because despite being an old fart, I'm still growing and adapting, and so should you. Enjoy your holidays!

Poll-ite season's greetings!
T___T

No comments:

Post a Comment