Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungWhy Claude Code keeps writing shell commands that fail on your Mac(20.09.2026 um 21:06 Uhr)
Sichere Programmierungllms.txt v2: What the Spec Says, and What 137,000 Domains Show(20.09.2026 um 21:17 Uhr)
Sicherheitslücken (CVE)NiceTryGPT: Less pattern matching. More actual hacking.(20.09.2026 um 21:19 Uhr)
IT Security VideoActivities BoF (kde2026)(20.09.2026 um 00:00 Uhr)
IT Security Toolsirdoc-app(20.09.2026 um 20:33 Uhr)
Sichere ProgrammierungWhy Claude Code keeps writing shell commands that fail on your Mac(20.09.2026 um 21:06 Uhr)
Sichere Programmierungllms.txt v2: What the Spec Says, and What 137,000 Domains Show(20.09.2026 um 21:17 Uhr)
Sicherheitslücken (CVE)NiceTryGPT: Less pattern matching. More actual hacking.(20.09.2026 um 21:19 Uhr)
IT Security VideoActivities BoF (kde2026)(20.09.2026 um 00:00 Uhr)
IT Security Toolsirdoc-app(20.09.2026 um 20:33 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

How to Develop an Air Quality Monitoring App with Ruby on Rails

Reagiere als Erste:r — dein Feedback zählt!

Prerequisites:

  • Ruby
  • sqlite3
  • Rails Installer

Install the app

rails new air_quality_app

serve the application

cd air_quality_app

bin/rails server

visit your browser with this link http://127.0.0.1:3000, you should have a similar display as below
Rails default information page

Create a controller

To create FeedsController and its index action, run the controller generator in the terminal.

bin/rails generate controller Feeds index 

install the Waqi Ruby SDK

Add this line to your application's Gemfile:

gem 'waqi-ruby'

And then execute:

bundle

Or install it yourself as:

gem install waqi-ruby

You need a token to conveniently access the Air QUality Open Data Platform API. Visit https://aqicn.org/data-platform/token/ to get a token.

Open your terminal to save the token

EDITOR=vim rails credentials:edit

You can change the editor to your preferred choice like nano.

Add the following line and save the file.

air_quality_data_token: <insert-the-api-token-here>

You should be able to access the token using the code: Rails.application.credentials[:air_quality_data_token]

Add some styling to the view

Use tailwind css for the styling. A tailwind ruby gem is available at https://github.com/rails/tailwindcss-rails, you can check it out for more details.

install tailwindcss

bundle add tailwindcss-rails
rails tailwindcss:install

The above commands will create files and folders necessary to kickstart designing with tailwind: app/views/layouts/application.html.erb, app/assets/builds/, config/tailwind.config.js, app/assets/stylesheets/application.tailwind.css, Procfile.dev, bin/dev.

Additionally, you can install foreman gem to run Tailwind in "watch" mode concurrently. Style changes are automatically reflected in the generated CSS output.

To configure tailwind to be applied in all template files, you can edit config/tailwind.config.js file.

Now serve the application: bin/dev
This will run both the rail server and tailwind watch.

Adjust your application layout to use nice UI. Update the body section of app/views/layouts/application.html.erb file with the code below:

...
<body class="flex flex-col justify-between">

    <header class="bg-gray-500 text-white shadow-lg">
        <nav class="mx-auto flex max-w-7xl items-center justify-between gap-x-6 p-6 lg:px-8" aria-label="Global">
            <h1 class="font-bold md:text-2xl m-0">
                WAQI Essentials
            </h1>
                <div class="flex flex-1 items-center justify-end gap-x-6">
                <p class="text-white text-sm font-semibold leading-6">City Feed</p>
                <p class="text-white text-sm font-semibold leading-6">Search</p>
            </div>
        </nav>
    </header>

    <main>
        <div class="max-w-7xl mx-auto p-6 lg:px-8 prose">
            <%= yield %>
        </div>
    </main>
</body>
...

Edit app/views/feeds/index.html.erb to use the parent layout that has the tailwind file configured.

<div class="text-center">
    <h1 class="text-3xl font-bold">City Feed</h1>
    <p>Get real-time Air Quality index for a given station.</p>
</div>
<form>   
    <label for="default-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300">Search</label>
    <div class="relative">
        <div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
            <svg class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
        </div>
        <input type="search" id="default-search" class="block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="enter name of city..." required>
        <button type="submit" class="text-white absolute right-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Search</button>
    </div>
</form>

You should have a result as below:

feeds index page with form field

Let's interact with api by submitting a city name and get a result.
First, modify the form tag to conform to rails code standard!

...
<%= form_with url: "/feeds/index", method: :get do |form| %>   
    <%= form.label :city, "Search", class:"mb-2 text-sm font-medium text-gray-900 sr-only dark:text-gray-300" %>
    <div class="relative">
        <div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
            <svg class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
        </div>

        <%= form.text_field :city, placeholder: "enter name of city...", autofocus: true, required: true, value: @city,
            class:"block p-4 pl-10 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
        %>

        <%= form.submit "Search", class: "text-white absolute right-2.5 bottom-2.5 bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-2 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800" %>
    </div>
<% end %>

<div class="text-center">
    <p><%= @feeds %></p>
</div>

You should notice the form_url tag has the url '/feeds/index'. After submitting the form the request goes to the index action on feeds_controller.

Modify the controller to accept the request and connect to Air Quality API to get a result:

# app/controllers/feeds_controller.rb

require 'waqi'

class FeedsController < ApplicationController
    before_action :set_client

    def index
        @city = request.query_parameters['city']

        if !@city.blank?
            @feeds = @waqi_client.get_city_feed(@city)
        end
    end

    private 

    def set_client
        @waqi_client = Waqi::Client::new(api_key: Rails.application.credentials[:air_quality_data_token])
    end
end

The above code does the following:

  1. Loads the https://github.com/waqi-dev-community/waqi-ruby-client at the top of the file.
  2. A before_action ruby method initialize set_client method. This method allows us to use the library in the code.
  3. Index method accepts the query parameter city to be used.

When you search for a city let's say new-york, you should get this result in your browser:

output raw result for city search

You can beautify your result and integrate a Google line chart to display daily forecast for Ozone and Particulate matter level:

Add a script tag to app/views/layouts/application.html.erb file

...

<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">
        google.charts.load('current', {'packages':['line']});
        var forecast = document.getElementById('line_top_x').getAttribute('data-forecast')
        if(forecast.length) {
            google.charts.setOnLoadCallback(drawChart);
        }

        function drawChart() {
            var data = new google.visualization.DataTable();
            data.addColumn('string', 'Date');
            data.addColumn('number', 'Ozone (o3)');
            data.addColumn('number', 'Particulate Matter 10 (pm10)');
            data.addColumn('number', 'Particulate Matter 25 (pm25)');
            data.addRows(JSON.parse(forecast));

            var options = {
                width: 900,
                height: 500,
                axes: {
                    x: {
                        0: {side: 'top'}
                    }
                }
            };

            var chart = new google.charts.Line(document.getElementById('line_top_x'));

            chart.draw(data, google.charts.Line.convertOptions(options));
        }
    </script>
</body>
</html>

The script loads the chart library and the forecast data is inserted by getting the data attribute on the page app/views/feeds/index.html.erb:

var forecast = document.getElementById('line_top_x').getAttribute('data-forecast')

Insert this code snippet below into the city_feed views page:

<% if @isError %>
    <div class="m-4 p-2 text-red-500">
        <span>Error: <%= @response["data"] %></span>
    </div>
<% else %>

    <div class="text-center grid grid-flow-row-dense grid-cols-4 grid-rows-4">

        <p><span class="font-black">City: </span> <%= @city_info["name"] %></p>
        <p><span class="font-black">Latitude: </span><%= @city_info["geo"][0] %></p>
        <p><span class="font-black">Longitude: </span><%= @city_info["geo"][1] %></p>

        <% if !@iaqi.blank? %>
            <% @iaqi.each do |iaqi|  %>
                <% extra = determineLabel(iaqi["name"]).to_h %>
                <p><span class="font-black"><%= extra["label"] %>: </span><%= iaqi["value"] %> <%= extra["unit"] %></p>
            <% end %>
        <% end %>
    </div>

    <div class="text-center">
        <h3> Daily forecast for Ozone, Particulate Matter 10 and 25</h3>
        <div id="line_top_x" style="margin-right: auto;margin-left: auto;display: table;" data-forecast="<%= @daily_forecast %>"></div>
    </div>

<% end %>

The code above handles the error if there's any and also display the city, latitude, longitude, and other air quality information for a particular station.

This line of code <% extra = determineLabel(iaqi["name"]).to_h %> is used to format the data with readable texts and provide more information to the user. You can update the app/helpers/application_helper.rb with the code below. This is a helper class.

module ApplicationHelper
    def determineLabel(name)
        case name
        when "co"
            { "label" => "Carbon monoxide", "unit" => "ppm" }
        when "no2"
            { "label" => "Nitrogen dioxide", "unit" => "ppb" }
        when "pm10"
            { "label" => "PM10", "unit" => "µg/m³" }
        when "pm25"
            { "label" => "PM25", "unit" => "µg/m³" }
        when "o3"
            { "label" => "Ozone", "unit" => "ppb" }
        when "w"
            { "label" => "Wind Speed", "unit" => "m/s" }
        when "t"
            { "label" => "Temperature", "unit" => "°C" }
        when "p"
            { "label" => "Air pressure", "unit" => "mb" }
        when "so2"
            { "label" => "Sulfur dioxide", "unit" => "ppb" }
        when "h"
            { "label" => "Humidity", "unit" => "%"} 
        when "dew"
            { "label" => "Dew", "unit" => "°C"}
        end
    end
end

Update the index method on FeedsController file:

def index
        @city = request.query_parameters['city']
        if !@city.blank?
            response = @waqi_client.get_city_feed(@city)

            if response["status"] == "ok"
                @response = response["data"]
                @city_info = response["data"]["city"]

                @iaqi = response["data"]["iaqi"].map do |key, value|
                      { "name" => key.to_s, "value" => value["v"] } 
                end

                @forecast = response["data"]["forecast"]["daily"]

                @daily_forecast = format_forecast_data(@forecast)

            elsif response["status"] == "error"
                @isError = true
                @response = response
            end
        end
    end

private 

... 

def format_forecast_data(air_quality_data)
        # Extract days from "o3", "pm10", and "pm25" arrays
        o3_days = air_quality_data["o3"].map { |data| data["day"] } || []
        pm10_days = air_quality_data["pm10"].map { |data| data["day"] } || []
        pm25_days = air_quality_data["pm25"].map { |data| data["day"] } || []

        # Find intersection of the three arrays
        identical_days = o3_days & pm10_days & pm25_days
        formatted_parameter_data = []

          # Extract required values and push into new array
        identical_days.each do |date|
            o3_index = air_quality_data["o3"].find_index { |entry| entry["day"] == date }
            pm10_index = air_quality_data["pm10"].find_index { |entry| entry["day"] == date }
            pm25_index = air_quality_data["pm25"].find_index { |entry| entry["day"] == date }

            formatted_parameter_data << [date, air_quality_data["o3"][o3_index]["avg"], air_quality_data["pm10"][pm10_index]["avg"], air_quality_data["pm25"][pm25_index]["avg"]]
        end
        formatted_parameter_data
    end

Image showing the result for abuja station with a display of line chart

You can explore more functionality with the ruby sdk such as Lat/Lng based Geolocalized feed, IP based Geolocalized feed, etc.

The complete source code can be found on github

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How to Develop an Air Quality Monitoring App with Ruby on Rails

Thematisch verwandte Begriffe: Develop, Quality, Monitoring, with · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-93956 | A flaw has been found in olivier-ls PHP-FTS up to 1.1.2. Affected by thi…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick