Friday, 19 December 2025

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

For this part of the web tutorial, you'll need to have Rails installed. Here, you'll start a new project in the CLI using the rails new command followed by the name of your project. We'll add "--skip-active-record" because we won't be using the built-in database functionnality.
rails new xmas-poll-2025 --skip-active-record


Go to the file named gemfile and add these two lines. We are going to install httparty and dotenv-rails. httparty allows us to call endpoints, which is going to be absolutely necessary. dotenv-rails helps us get variables from the .env file. This is not so necessary, but it's a good practice and we're gonna do it.

Gemfile
gem "httparty"
gem "dotenv-rails", groups: [:development, :test]


Then navigate to the xmas-poll-2025 directory and run this command in the CLI.
bundle install


Still in the CLI, run this command to create the controller poll.
rails generate controller poll


This will create the file, poll_controller.rb, in the controllers directory of the app directory. Some other files will be created, but we can examine them later. Let's first make sure that we have an index action defined, and a submit action.

app/controllers/poll_controller.rb
class PollController < ApplicationController
    def index

    end

    def submit

    end
end


Go to routes.rb in the config directory. Make sure your main route is defined. Add a submit route for the form we're about to create. Note that we have the alias "poll_page" for the main route, and the alias "submit_poll_form" for the form submission route. These will be useful as references. We'll also define the index action of the poll controller as the root.

config/routes.rb
Rails.application.routes.draw do
    get "poll/", to: "poll#index", as: "poll_page"

    root "poll#index"
    post "poll/submit", to: "poll#submit", as: "submit_poll_form"
end


In the views directory, navigate to layouts. You should see application.html.erb. Change the title and add placeholders for alerts and notices.

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
    <head>
        <title><%= content_for(:title) || "Xmas Poll 2025" %></title>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="mobile-web-app-capable" content="yes">
        <%= csrf_meta_tags %>
        <%= csp_meta_tag %>

        <%= yield :head %>

        <%# Enable PWA manifest for installable apps (make sure to enable in config/routes.rb too!) %>
        <%#= tag.link rel: "manifest", href: pwa_manifest_path(format: :json) %>

        <link rel="icon" href="/icon.png" type="image/png">
        <link rel="icon" href="/icon.svg" type="image/svg+xml">
        <link rel="apple-touch-icon" href="/icon.png">

        <%# Includes all stylesheet files in app/assets/stylesheets %>
        <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
        <%= javascript_importmap_tags %>
    </head>

    <body>
        <% if flash[:notice] %>
            <div class="notice"><%= flash[:notice] %></div>
        <% end %>
        <% if flash[:alert] %>
            <div class="alert"><%= flash[:alert] %></div>
        <% end %>
        <br />
        <%= yield %>
    </body>
</html>


We'll create a view for poll. In the views directory, create the poll directory and in it, create index.html.erb.

In here, we'll want to use the form helper. We can manually write the HTML form, but this really just defeats the purpose of using Ruby On Rails. The form will use the route submit_poll_form, which we defined earlier. Add a button too.

app/views/poll/index.html.erb
<div>
  <%= form_with url: submit_poll_form_path, local: true do |form| %>
    <button>Send</button>
  <% end %>
</div>


Run this command in the CLI.
rails server


When you go to http://localhost:3000, you should see this!


Let's just spruce this up a bit. Go to this CSS file. Here, set the font. Background color we'll keep to a Christmassy deep red. Create pollform. I want to give it a nice cheerful bright green background, with round corners. The button will be a big red one. Obviously, all this is just visual and makes no difference to functionality.

app/assets/stylesheets/application.css
body
{
  font-family: Verdana;
  font-size: 14px;
  background-color: rgb(100, 0, 0);
}

.pollform
{
  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;
}

button
{
  width: 10em;
  height: 2em;
  margin-top: 10%;
  float: right;
  font-size: 1.5em;
  border-radius: 20px;
  border: 3px solid rgb(255, 2525, 255);
  background-color: rgb(255, 0, 0);
  color: rgb(255, 255, 255);
}

button:hover
{
  background-color: rgb(155, 0, 0);
}


In here, set the div class to pollform.

app/views/poll/index.html.erb
<div class="pollform">
  <%= form_with url: submit_poll_form_path, local: true do |form| %>
    <button>Send</button>
  <% end %>
</div>


There you go. Nothing in the form yet, but you can see that the form details will appear in the bright green area.


Time to work on the controller. Remember installing httparty? We'll use it here with an import.

app/controllers/poll_controller.rb
require "httparty"

class PollController < ApplicationController
    def index

    end

    def submit

    end
end


Before we continue, we need to add this line to the .env file. This is the URL of the API endpoint we defined in the previous part of the web tutorial.

.env
ORDS_API_URL=https://oracleapex.com/ords/teochewthunder/polls/poll/1


Then use the variable ORDS_API_URL from .env, in this way.

app/controllers/poll_controller.rb
require "httparty"

class PollController < ApplicationController
    ORDS_API_URL = ENV["ORDS_API_URL"]

    def index

    end

    def submit

    end
end


For index, we want to fetch the questions. Use the get() method of HTTParty, passing in ORDS_API_URL as an argument. This makes a call to that specific URL using a GET request. The result should be assigned to the variable response.

app/controllers/poll_controller.rb
class PollController < ApplicationController
    ORDS_API_URL = ENV["ORDS_API_URL"]

    def index
        response = HTTParty.get(
            ORDS_API_URL
        )
    end

    def submit

    end
end


The fetched results should look like this.
{
  "items": [
    {
      "name": "Xmas Poll 2025",
      "serial_no": 1,
      "title": "JOY TO THE WORLD"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 2,
      "title": "IT CAME UPON THE MIDNIGHT CLEAR"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 3,
      "title": "I SAW MOMMY KISSING SANTA CLAUS"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 4,
      "title": "HARK THE HERALD ANGELS SING"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 5,
      "title": "DECK THE HALLS"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 6,
      "title": "THE FIRST NOEL"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 7,
      "title": "RUDOLPH THE RED-NOSED REINDEER"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 8,
      "title": "SILENT NIGHT"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 9,
      "title": "JINGLE BELLS"
    },
    {
      "name": "Xmas Poll 2025",
      "serial_no": 10,
      "title": "JINGLE BELL ROCK"
    }
  ],
  "hasMore": false,
  "limit": 25,
  "offset": 0,
  "count": 10,
  "links": [
    {
      "rel": "self",
      "href": "https://oracleapex.com/ords/teochewthunder/polls/poll/1"
    },
    {
      "rel": "edit",
      "href": "https://oracleapex.com/ords/teochewthunder/polls/poll/1"
    },
    {
      "rel": "describedby",
      "href": "https://oracleapex.com/ords/teochewthunder/metadata-catalog/polls/poll/item"
    },
    {
      "rel": "first",
      "href": "https://oracleapex.com/ords/teochewthunder/polls/poll/1"
    }
  ]
}


With that in mind, we'll want to send that data to the view if called successfully. If unsuccessful, we'll want to flash a notice.

app/controllers/poll_controller.rb
def index
      response = HTTParty.get(
          ORDS_API_URL
      )

    if response.code == 200

    else
        flash.now[:alert] = "Error fetching data:"

        @api_data = {}
    end
end


We can actually test this. Deliberately sabotage the endpoint like this.

app/controllers/poll_controller.rb
response = HTTParty.get(
    ORDS_API_URL + "/test"
)


Here you go, a negative result!


Let's just style the notices. We want them to be a slim bar at the top, thus we set the position property to absolute. Let's have a nice green color scheme for a general success message and a red color scheme for errors.

app/assets/stylesheets/application.css
button:hover
{
  background-color: rgb(155, 0, 0);
}

.notice, .alert
{
  width: 100%;
  position: absolute;
  font-size: 0.85em;
  font-weight: bold;
  padding: 0.5em;
  text-align: center;
  left: 0;
  top: 0;
}

.notice
{
  background-color: rgba(100, 255, 100, 0.2);
  color: rgb(0, 255, 0);
}

.alert
{
  background-color: rgba(255, 100, 100, 0.2);
  color: rgb(255, 0, 0);
}


There it is!


Now un-sabotage the API endpoint. We want it to be correct. Make sure that the parsed_response property of response is assigned to api_data. Note that the "@" denotes api_data as an instance variable that can be used in the corresponding view.

app/controllers/poll_controller.rb
def index
    response = HTTParty.get(
        ORDS_API_URL
    )

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


Then we'll work on the view. Remember api_data and what the returned JSON looked like? Well, if we want the Poll Title, we just need to use the first element of items, and get the name property.

app/views/poll/index.html.erb
<h1><%= @api_data["items"][0]["name"] %></h1>
<div class="pollform">
  <%= form_with url: submit_poll_form_path, local: true do |form| %>

  <% end %>
</div>


In the CSS, add a styling for h1 tags.

app/assets/stylesheets/application.css
body
{
  font-family: Verdana;
  font-size: 14px;
  background-color: rgb(100, 0, 0);
}

h1
{
  text-align: center;
  color: rgba(255, 255, 255, 0.5);
}


.pollform
{
  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;
}


And here is the header!


Now we'll want to display the rest of api_data. Let's have a table in there, with these headers.

app/views/poll/index.html.erb
<h1><%= @api_data["items"][0]["name"] %></h1>
<div class="pollform">
  <%= form_with url: submit_poll_form_path, local: true do |form| %>
    <table>
      <tr>
        <td width="300px"><b>Carol</b></td>
        <td width="600px"><b>Rating (1 = lowest, 5 = highest)</b></td>
      </tr>
    </table>

    <button>Send</button>
  <% end %>
</div>


So far so good...


Then we use the each keyword on items, to iterate through it. Each instance of items will be known as question within this loop. And for each instance, we will have a HTML table row and two columns.

app/views/poll/index.html.erb
<h1><%= @api_data["items"][0]["name"] %></h1>
<div class="pollform">
  <%= form_with url: submit_poll_form_path, local: true do |form| %>
    <table>
      <tr>
        <td width="300px"><b>Carol</b></td>
        <td width="600px"><b>Rating (1 = lowest, 5 = highest)</b></td>
      </tr>
      <% @api_data["items"].each do |question| %>
        <tr>
          <td></td>
          <td></td>
        </tr>
      <% end %>

    </table>
    <button>Send</button>
  <% end %>
</div>


Here, we'll reflect the question property value for title.

app/views/poll/index.html.erb
<h1><%= @api_data["items"][0]["name"] %></h1>
<div class="pollform">
  <%= form_with url: submit_poll_form_path, local: true do |form| %>
    <table>
      <tr>
        <td width="300px"><b>Carol</b></td>
        <td width="600px"><b>Rating (1 = lowest, 5 = highest)</b></td>
      </tr>
      <% @api_data["items"].each do |question| %>
        <tr>
          <td><%= form.label nil, "#{question['title']}" %></td>
          <td></td>
        </tr>
      <% end %>
    </table>
    <button>Send</button>
  <% end %>
</div>


And here all the questions from the poll, the names of the carols, are displayed!


Next we want to display a series of radio buttons. The values will be from 1 to 5. To that end, we should define rating_options as a collection containing values 1, 2, 3 4 and 5. Then we use the to_a() method to convert it to an array.

app/views/poll/index.html.erb
<h1><%= @api_data["items"][0]["name"] %></h1>
<div class="pollform">
  <%= form_with url: submit_poll_form_path, local: true do |form| %>
    <% rating_options = (1..5).to_a %>
    <table>
      <tr>
        <td width="300px"><b>Carol</b></td>
        <td width="600px"><b>Rating (1 = lowest, 5 = highest)</b></td>
      </tr>
      
      <% @api_data["items"].each do |question| %>
        <tr>
          <td><%= form.label nil, "#{question['title']}" %></td>
          <td></td>
        </tr>
      <% end %>
    </table>
    <button>Send</button>
  <% end %>
</div>


In here, we want to iterate through rating_options using each. Each element will be referred to as option.
app/views/poll/index.html.erb
<tr>
  <td><%= form.label nil, "#{question['title']}" %></td>
  <td>
    <% rating_options.each do |option| %>

    <% end %>

  </td>
</tr>


In here, we use the form helper object radio_button. We pass in option as its value. Since it's a form element, it would be proper to give it name and id properties as well, which we will base on the value of the property serial_no, which is unique in each poll. The default value checked is always 3. You may notice ":none", passed in as the first argument. You can't omit this; I've tried and the server complains. This is actually meant to be the name of the argument, but I've overwritten it later as you can see, and now ":none" is just a placeholder value.

app/views/poll/index.html.erb
<tr>
  <td><%= form.label nil, "#{question['title']}" %></td>
  <td>
    <% rating_options.each do |option| %>
      <%= form.radio_button :none,
        option,
        name: "answers[#{question['serial_no']}]",
        id: "answer_#{question['serial_no']}_#{option}",
        checked: option == 3
      %>

    <% end %>
  </td>
</tr>


And now we have a label which displays option as the text, and preferences the element with the id specified, e.g, answer_2_1.

app/views/poll/index.html.erb
<tr>
  <td><%= form.label nil, "#{question['title']}" %></td>
  <td>
    <% rating_options.each do |option| %>
      <%= form.radio_button :none,
        option,
        name: "answers[#{question['serial_no']}]",
        id: "answer_#{question['serial_no']}_#{option}",
        checked: option == 3
      %>
      <%= form.label "answer_#{question['serial_no']}_#{option}", option %>
         
    <% end %>
  </td>
</tr>


So there, you see we have a bunch of radio buttons and labels!

Believe it or not, this is actually the easy part. Brace yourselves!

Next

Handling form submission.

Wednesday, 17 December 2025

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

Merry Christmas season to all!

It's been years since I got my hands dirty with Ruby On Rails, and seeing as I got time, why not now? Ruby, despite me never having had an opportunity to use it professionally, is probably one of my favorite programming languages that I ever picked up for no goddamn practical reason. And, well, this Christmas season, I wanna go back to my guilty pleasure.

Today, we will be creating a form in Ruby On Rails, to submit answers to a poll. This will be a simple Christmas Carol Poll, where the user rates all presented Chsristmas Carols on a scale of 1 to 5.

Normally, a Ruby On Rails project would have its own built-in server capabilities, but I wanted to see how Oracle APEX worked with Rails. Pretty darn well, actually. Thus, what I did was first log onto my Oracle APEX workspace and create some new tables.

The first, obviously, would be a POLLS table. This is the PL/SQL statement for the table, though you can use the UI to create it. It basically is just an auto-increment integer column, ID, and a string, NAME.
CREATE TABLE "POLLS" (
    "ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
    "NAME" VARCHAR2(100 CHAR) NOT NULL ENABLE,
    CONSTRAINT "POLLS_PK" PRIMARY KEY ("ID") USING INDEX ENABLE
);


Far easier to just copy this to clipboard, go to SQL > SQL Command and paste into the box, then run it.


There, you have the POLLS table. Ignore the other tables; they're remnants of my last Oracle APEX project.


I'm just going to enter the name of the poll here, manually.


The next table is POLL_QUESTIONS. Here's the SQL. This one is just a bit more complicated. We don't have an ID field here; instead uniqueness is determined by the combination of POLL_ID (which is a foreign key to the POLLS table) and NO. NO is an integer that denotes the serial number of that question within a poll. TITLE is a string which displays the question, and ANSWERS is a comma-separated string of possible values.
CREATE TABLE "POLL_QUESTIONS" (
    "NO" NUMBER(2,0) NOT NULL ENABLE,
    "TITLE" VARCHAR2(100 CHAR),
    "POLL_ID" NUMBER NOT NULL ENABLE,
    "ANSWERS" VARCHAR2(200),
    CONSTRAINT "POLL_QUESTIONS_PK" PRIMARY KEY ("NO", "POLL_ID") USING INDEX ENABLE
);


The easy thing to do would be to run SQL Command with this query.


Again, I'm going to do some manual data entry here. Notice that POLL_ID is always 1, because we only have the one entry in POLLS. For the column ANSWERS, it is a JSON-encoded array of the values 1 to 5.


Finally, we have POLL_RESULTS. ID is a primary key just like in POLLS. Each row will have two foreign keys - POLL_ID and QUESTION_NO which reference POLL_QUESTIONS. RESULT will store the answer for the specific question referenced by the last two fields. POLL_ID and QUESTION_NO.
CREATE TABLE "POLL_RESULTS" (
    "ID" NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY MINVALUE 1 MAXVALUE 9999999999999999999999999999 INCREMENT BY 1 START WITH 1 CACHE 20 NOORDER NOCYCLE NOKEEP NOSCALE NOT NULL ENABLE,
    "POLL_ID" NUMBER NOT NULL ENABLE,
    "QUESTION_NO" NUMBER NOT NULL ENABLE,
    "RESULT" VARCHAR2(100 CHAR) NOT NULL ENABLE,
    CONSTRAINT "POLL_RESULTS_PK" PRIMARY KEY ("ID") USING INDEX ENABLE
);


We can create the table the easy way here. We will not be populating this table manually. No, the entire point is to populate this table by means of submission through the Ruby On Rails form.


Time to determine API endpoints!

We've populated the POLLS and POLL_QUESTIONS table because we want to use this data to populate the form. In order for that to happen, we'll need to provide a means of retrieving that data. And that will be REST API endpoints. For now, we will do just a GET endpoint.

Go to SQL Workshop, then go to RESTful Services. Create a new Module and let's call it "polls". The endpoint here will be "/polls/".


Congrats! You have a Module! Now you need to add Templates to this Module. Click on that button, Create Template, near the bottom right side of the screen.

Create two Templates. The first one is poll/:id, and the second is poll/:id/results/.



Go to the first Template you created, poll/:id. You'll need to add a GET handler to it. To do that, click on the Create Handler button somewhere near the bottom right area of the screen. We haven't forgotten about the second Template, but we'll get to it later.


This is the SQL code that will be in that box. It's a simple join between POLLS and POLL_QUESTIONS.
SELECT p.NAME, pq.SERIAL_NO, pq.TITLE FROM POLLS p
LEFT JOIN POLL_QUESTIONS pq ON pq.POLL_ID = p.ID
WHERE p.ID = :id
ORDER BY pq.SERIAL_NO


This is a good place to stop. We've set up some stuff on Oracle APEX that will be used in the next part of this web tutorial.

Next

The Ruby On Rails setup.

Friday, 12 December 2025

Handle Your Imposter Syndrome

Imposter Syndrome. A curious term. It speaks, professionally, of the persistent doubt that ones current position is undeserved, that we do not have the talent to warrant what we currently have, and that one day we will be found out for the frauds that we are.

And among software developers, it's an all-too-common trope. I've struggled with it myself, and for good reason. There have been times when I doubted my own abilities as a software dev, after witnessing the rapid changes in the industry, being outright told I wasn't good enough, and so on. Sometimes interviewers tried to use it as a negotiation tactic, before tragically discovering that His Teochewness does not negotiate.

Never good enough.

I'm here today, however, not to tell you to be strong, or that you're better than you think. No, I'm going to do you one better - I'm going to explain why it doesn't matter even if your deepest darkest fears are true.

It's an unforgiving industry

First, let's establish a fact: there are probably tons of people in the industry significantly more talented and experienced than you are. (If that's not true, if you're at the top of the pile, there's no good reason for you to be reading this, so...)

Even these talented people are currently wandering around without a job thanks to layoffs in Big Tech such as Microsoft, Google and the like. The software industry is savage, man. All employers need is a hint that they don't exactly need you, and they'll be rid of you, just like that. Did these people deserve to be axed? I'd say no, but what do I know, eh?

Given the axe.

With that in mind, this is not to say that you absolutely deserve to be where you are. But statistically, if you didn't, chances are you'd already have been out of a job. Thus the chances of you at least somewhat deserving your current position, are pretty decent.

Also, let's agree that deserving to be in your position does not necessarily just mean you have the technical competence to do your job. You impressed the right people. You made the right connections. Those make you just as deserving, if not more so. Even if the interviewer at the job interview happened to be an idiot who was easily impressed, it's not like you just sat there and did jackshit.

So give yourself some credit. Not a whole lot of it, but at least just a little bit. Because my next couple points are about to annihilate everything I just said.

Deserving has nothing to do with it

You'll notice that I've been using the term "deserve" a lot. That was to help me make my earlier point - that you're not necessarily undeserving of what you have, and probably pretty deserving of it, at that. That's the good news.

The bad news is, what you think you deserve, does not matter. I've said as much previously. The world is much bigger than you and what you think you deserve, or even what you actually deserve.

Global forces
at work.

Have all the Imposter Syndrome you want. It changes nothing. You have less control over your own destiny than you think. There's certainly a lot that's within your control, but as those laid-off techies found out, there are forces beyond their ken. I'd say they deserved a lot more than what they ultimately got; but the fact is that they got what they got, and there ain't nothing me or anyone else can do about it, son. Them's the breaks. It's the way of the world.

We didn't ask for A.I to come for our jobs. We didn't cause the interest rates to fluctuate and the Big Tech company boards to jettison their headcount. We certainly didn't ask for COVID-19, which was partly what caused much of the overhiring in the first place. But it all happened; and you know what, deal with it.

No pressure

Recently, Singapore held her General Elections, and as usual, on Social Media, there was a lot of self-righteous, self-aggrandizing caterwauling about "voting wisely" and "looking at the big picture". Dear Lord, the utter cringe of it all.

Anyway, my position was this.

No doubt your vote is important. Your contribution to nation-building should be appreciated. But at the same time, buddy, you're casting a vote, not curing cancer. Sit your ass down before you give yourself a heart attack.

Not curing cancer.

I take a similar attitude to work. Is your work important? Sure it is. If it wasn't, why should anyone pay you to do it?

But at the same time, is anyone going to die because you didn't do your job properly? If so, then yes, you should take your Imposter Syndrome seriously. Otherwise...

What are the consequences of you being unqualified to do your job? Do you rescue people from burning buildings? Fly planes? Arrest violent criminals? Build bridges? Some of us do those things, yes. But for the vast majority of us, it's not that vital that you're qualified for your job, and therefore you don't need to worry too much over whether you're good enough to do it.

You won't be killing anyone with your incompetence. At most, you'll severely inconvenience some people. Or several. In other words, get over yourself.

Conclusion

The question is not whether you've earned your place. The question is really - why does it matter? It could all be gone the next day, through no real fault of your own. You don't have to deserve something in order to get it, or even keep it. Sure, it helps, but that's all it does. The world is unfair like that.

Your imposter boy for programming,
T___T

Sunday, 7 December 2025

Idempotence in CRUD operations

Hello, readers. Today we're going to talk about idempotence.

The concept of idempotence is paramount in database security. By saying that, of course, I feel compelled to explain what idempotence is. That term tends to come up a lot with regard to data transactions. The official definition is as follows, with regard to data transactions.
Idempotency is the property of an operation where performing it multiple times produces the same result as performing it once.


What that means is that if you apply an operation over and over, and it makes no difference to the database after the first time, that operation is idempotent.

With regard to CRUD (CREATE, READ, UPDATE, DELETE) operations, it's important to understand which ones are idempotent, and which ones run the risk of undesirable results if accidentally executed multiple times. 

Please note that code examples are in MySQL.

CREATE

Idempotence: No (except in special circumstances)
CREATE operations are not idempotent by default. If you run a CREATE operation multiple times, you are going to get multiple created rows.

INSERT INTO categories (name, description) VALUES ("Cat A", "test")


Running this three times will result in these three rows being added.
id name description
1 Cat A test
2 Cat A test
3 Cat A test


What if the design of the table was structured this way? What if name was supposed to be unique?
CREATE table categories (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100) UNIQUE,
  description TEXT
)


Being unique.

In that case, that particular CREATE operation would be idempotent because the database simply wouldn't allow subsequent CREATE operations with the same values in the name column.
id name description
1 Cat A test


Also, if you defined an UPSERT operation instead of a CREATE, the record would only get inserted the first time. Subsequent times, the record would already exist, and therefore it would get updated... with the same values. Thus, making it idempotent. There is an exception to this exception, but that will be discussed in the UPDATE operation further down.
INSERT INTO categories (name, description)
VALUES ("Cat A", "test")
ON DUPLICATE KEY UPDATE
description = VALUES(description);


READ

Idempotence: Yes
This is the most straightforward operation in the sense that it is always idempotent. The first READ request makes no change to the database. Subsequent READ requests also make no change.

SELECT * FROM categories WHERE name = "Cat A"

READ operations are not
about changing data.

No exceptions to the rule, no nothing. This is as open-and-shut a case of idempotence as you're ever going to get in the world of computer science.

UPDATE

Idempotence: Yes (except in special circumstances)
In most cases, running an UPDATE operation on a specific record multiple times, has no effect beyond the first time. It will end up updating that record to the same values. In the query below, row id 1's name and description values would just keep being updated to "Cat A" and "test", which is basically no change.

UPDATE categories
name = "Cat A",
description = "test"
WHERE id = 1


Thus, this is idempotent. Unless...

What if the table had an Audit Field that tracked when the record was updated?
CREATE table categories (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(100),
  description TEXT,
  updated DATETIME
)


And what if the UPDATE operation was like this? Then the updated field would be a different value every time this operation was run, and therefore it would no longer be idempotent!

Time being tracked.


UPDATE categories
name = "Cat A",
description = "test"
updated = now()
WHERE id = 1


Also, what if the UPDATE operation wasn't on a specific row? This query updates all rows where the name field fits a particular condition. Kind of like dropping a bomb on an entire country instead of surgically taking out one terrorist. (Boy, this got dark)

UPDATE categories
name = "Cat A",
description = "test"
WHERE name like "%Cat%"


What if, just after the UPDATE operation above was run, a row was coincidentally inserted?
INSERT INTO categories (name, description) VALUES ("Cat B", "test")


Then we'd have two rows like this...
id name description
1 Cat A test
2 Cat B test


...but if the UPDATE operation was run again, this would be the result!
id name description
1 Cat A test
2 Cat A test


So, to conclude, in principle, the UPDATE operation is idempotent. But only if the values updated are absolute, and only if a very specific record is being updated.

DELETE

Idempotence: Yes (except in special circumstances)
In principle, DELETE operations are idempotent. After all, you can only delete a record successfully, once. After that, it no longer exists, so rerunning the same DELETE operation, achieves nothing. To employ a grim analogy, you can't kill someone twice.

You can only kill
someone once.

DELETE FROM categories WHERE id = 1


However, the concerns that exist for the UPDATE operation also exist for the DELETE operation. What if the DELETE operation wasn't on a specific row? This deletes all rows where the name field fits a certain filter.
DELETE FROM categories WHERE name LIKE "%CAT%"


And if, after the first DELETE operation, a record was added like so, rerunning the above DELETE operation would remove this row!
INSERT INTO categories (name, description) VALUES ("Cat B", "test")


In conclusion

A lot of what I've outlined is context. In simple cases, the answer of idempotence are similarly straightforward. But this is software development, where things are rarely that simple.

Wishing you a categorically good day,
T___T

Thursday, 4 December 2025

Film Review: War of the Worlds (2025)

Following in the footsteps of screen marvels like Unfriended, Searching and Missing, comes a new tech thriller in the same vein. Well, "new" might be stretching the truth a wee bit. War of the Worlds was produced back in 2020 during the COVID-19 pandemic, and it must have seemed like a good idea at the time, what with people being made to stay at home and all. A movie that requires very little face-to-face interaction between its actors? Holy guacamole pandemic loopholes, Batman!


Except that sadly, while the idea was decent, execution was found rather wanting. I watched this movie out of sheer obligation rather than because I truly wanted to, and this was made more apparent the more it went on.

This movie is in screenlife format, where the entire story takes place on TV monitors and computer screens. I kept calling it the "found footage format" in the past, but I guess this has kind of evolved into its own sub-genre by this point.

Warning - spoilers, I guess...

I don't even know why I bother telling you this. There are no surprises here (barring how dumb this movie is) that me saying it out here would even count as much of a spoiler. Still, you've been forewarned, if not forearmed.

The Premise

William Radford is a Government tech agent (with high security clearance and all) who is in his office when an alien invasion begins. What ensues is a series of catastrophic events as he shuttles back and forth between his job, helping to repel the invasion and keeping his family safe... all from behind the keyboard and monitor.

The Characters

Ice Cube as William Radford, a techie employed in a Government tech surveillance program. It's almost impossible to overstate how much I love watching Ice Cube on screen. Next to Samuel L Jackson and Denzel Washington, he might just be the coolest black actor ever. I'm biased, of course. From XXX: State of the Union to 22 Jump Street, his gruff angry black man persona is a joy to watch. However, even Ice Cube has his limits and his inclusion in this movie exposed them cruelly. The screenlife format of this movie basically means that for a large part of the show, we're reduced to watching Ice Cube emote close up... basically underreacting and overreacting to everything. Not his best role, that's for sure.

Eva Longoria appears in the movie as Dr Sandra Salas, who seems to be pretty useless, all things considered. She's just here to deliver footage and exposition to Radford (and us, by extension) and does nothing to make us care for her character in the slightest. Honestly, I didn't even remember the character's name, that's how little she mattered to the plot. Eva Longoria has had a long and storied career, and let's just say that this role is not something she will be remembered fondly for, if at all.

Henry Hunter Hall as Radford's son David, who's apparently a game reviewer. Radford thinks his son is a huge disappointment and loser... until he discovers that he's actually the hacker known as The Disruptor. Hall is not very interesting in that role, I gotta say. My reaction to the reveal was an overwhelming "meh".

Iman Benson as daughter Faith. Happens to be a biologist who somehow discovers how to synthesize a virus to kill off the aliens. She's pregnant during the events of this film and mostly spends her time either looking terrified or constipated. I don't know if it's Benson's acting or just the shitty writing (maybe both?) but she just doesn't come across as a particularly competent scientist... at least not at the level that would result in producing a virus that would take down the aliens.

Devon Bostick as son-in-law Mark Goodman, whom Radford might consider an even bigger loser than his son. He's an Amazon delivery guy, but even he has a role to play in defeating the aliens, though it's possible just to add some product placement since it is being featured on Amazon Prime.

Clark Gregg is the unscrupulous Director Donald Briggs, whose insane plan to spy on every living person on Earth is the cause of all this mess. It's nice to see Gregg again after his stint in the Marvel Cinemetic Universe, but at the same time really sad to see that he's reduced to this.

Michael O'Neill makes a couple appearances as US Secretary of Defense. A very one-note role.

Andrea Savage is FBI Agent Sheila Jeffries, a role so colorless I mistook her for Eva Longoria's character a few times.

The Mood

They try hard to convey the end-of-the-world high-stakes terror of an alien invasion using the screenlife format. And it falls flat a lot. Even when Ice Cube swears right at the screen, it comes across as hilarious rather than scary. The alien robots are meant to inspire dread, but the whole effect is cheesy. I get what they were trying to do, but it just fell flat.

What I liked

The whole bio-mechanical nature of the aliens is kind of cool. Tentacles within tripod-shaped domes? Tiny bugs that are equal parts blood and silicon chips? Corny but also intriguing.

The whole thing about the homeless guy accepting the bribe of an Amazon Gift Card was hysterical. It was so stupid (and shameless) I couldn't help but laugh. Kudos!

The biblical poetry of a hacker named David fighting a Government surveillance program named Goliath, is divine.

What I didn't

In general, the plot is just silly. I'll elaborate why as we go along.

Radford types a lot of textspeak, but his textspeak doesn't make a lick of sense. He types things like "Where are U" which is just weird. I can understand, even though I loathe it, the propensity to substitute "U" for "you". What I don't understand is, if you're going to be lazy enough to do that, why would you make the effort to properly captalize the "w" in "where"? While we're at it, why would you capitalize the "u"? Actually, why not just type "where r u"? Indeed, why not "wru"?

The aliens invade Earth because they want to eat our data. Read that again, until the sheer stupidity of that statement sinks in. Data is basically just 1s and 0s. The image of a cat is data. The recording of a song is data. A hastily-typed text message is also data. A YouTube video that's been corrupted, is still data. A random sequence of 1s and 0s? You guessed it, data. If the aliens had the tech to invade Earth, they definitely had the tech to create whatever data they needed, for consumption, rather than go to the trouble of invading Earth for it. I feel like whoever wrote this story doesn't know a whole lot about tech, and just thinks it's some kind of witchcraft.

The tentacles that make an appearance when the aliens attack, are goofy AF.

Faith was injured due to being stabbed by a piece of debris. Later on, she's seen removing it in order to "stop the bleeding". Wait, what? I could understand that if Faith Radford was supposed to be a clueless layperson. But a professional biologist?! This is some Grade A shit writing right there.

Amazon really got their claws in where product placement was concerned. Not only was one of the characters an Amazon employee, the script also had an extended sequence of Radford placing an order and having it shipped by drone. So cringe, OMG.

Conclusion

Was this movie a huge disappointment? Not really; that would have required that I had sizeable expectations for it. The fact that it disappointed me even a bit considering how forgiving as an audience I am, is testament to how little value it has. This is probably the most useless movie of 2025... and I say that both as an Ice Cube fan and someone who absolutely loves the screenlife format.

My Rating

2.5 / 10

This movie alienated me!
T___T