§2023-02-28
- Experimenting with Ruby, Sinatra and PostgreSQL: a Message Board App by [László Harri Németh]( http://nlharri.hu http://github.nlharri.hu hello@nlharri.hu)
- Create Project dir
$ mkdir messgage-board && cd $_
$ bundler init
- Gemfile as
# frozen_string_literal: true
source "https://rubygems.org"
# gem "rails"
gem "sinatra"
gem "thin"
- then bundle install
- Let's see, hello-world-sinatra.rb as
require 'sinatra'
# Listen on all interfaces in the development environment
# This is needed when we run from Cloud 9 environment
# source: https://gist.github.com/jhabdas/5945768
set :bind, '0.0.0.0'
set :port, 8080
get '/' do
'hello world!'
end
- run
ruby ruby-hello-world-sinatra.rb
$ ruby hello-world-sinatra.rb
== Sinatra (v3.0.4) has taken the stage on 8080 for development with backup from Thin
2023-02-28 10:16:02 +0800 Thin web server (v1.8.1 codename Infinite Smoothie)
2023-02-28 10:16:02 +0800 Maximum connections set to 1024
2023-02-28 10:16:02 +0800 Listening on 0.0.0.0:8080, CTRL+C to stop
127.0.0.1 - - [28/Feb/2023:10:16:22 +0800] "GET / HTTP/1.1" 200 12 0.0122
127.0.0.1 - - [28/Feb/2023:10:16:22 +0800] "GET /favicon.ico HTTP/1.1" 404 469 0.0028
...
- Start PostgreSQL server
[alexlai@n2MnJaro ~]$ sudo systemctl status postgresql
● postgresql.service - PostgreSQL database server
Loaded: loaded (/usr/lib/systemd/system/postgresql.service; disabled; preset: disabled)
Active: active (running) since Tue 2023-02-28 10:35:14 CST; 6s ago
Process: 53732 ExecStartPre=/usr/bin/postgresql-check-db-dir ${PGROOT}/data (code=exited, status=0/SUCCESS)
Main PID: 53734 (postgres)
Tasks: 6 (limit: 4120)
Memory: 23.0M
CPU: 282ms
CGroup: /system.slice/postgresql.service
├─53734 /usr/bin/postgres -D /var/lib/postgres/data
├─53735 "postgres: checkpointer "
├─53736 "postgres: background writer "
├─53738 "postgres: walwriter "
├─53739 "postgres: autovacuum launcher "
└─53740 "postgres: logical replication launcher "
Feb 28 10:35:13 n2MnJaro postgres[53734]: 2023-02-28 10:35:13.410 CST [53734] LOG: listening on Unix socket "/run/postgresql/.s.PGSQL.5432"
- Install
rerun
gem and,
$ bundle exec rerun hello-world-sinatra.rb
[DEPRECATED] `Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` (called at /opt/home/alexlai/.frum/versions/3.2.1/lib/ruby/gems/3.2.0/gems/rerun-0.14.0/lib/rerun/notification.rb:74)
10:47:20 [rerun] Message-board launched
10:47:20 [rerun] Rerun (53975) running Message-board (53993)
== Sinatra (v3.0.4) has taken the stage on 8080 for development with backup from Thin
2023-02-28 10:47:21 +0800 Thin web server (v1.8.1 codename Infinite Smoothie)
2023-02-28 10:47:21 +0800 Maximum connections set to 1024
2023-02-28 10:47:21 +0800 Listening on 0.0.0.0:8080, CTRL+C to stop
- app.rb
require 'sinatra'
# Listen on all interfaces in the development environment
# This is needed when we run from Cloud 9 environment
# source: https://gist.github.com/jhabdas/5945768
set :bind, '0.0.0.0'
set :port, 8080
get '/' do
t_msg = [
{ nick: "Harri", msg: "Hello World!" },
{ nick: "Ioana", msg: "Buna dimineata" },
{ nick: "Alexandru", msg: "sunt fericit" }
]
erb :v_message, :layout => :l_main, :locals => {:t_msg => t_msg}
end
- ~/views/l_main.erb
<!doctype html>
<html lang=en>
<head>
<link rel="stylesheet" type="text/css" href="/stylesheets/main.css">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<meta charset=utf-8>
<title>Message Board</title>
</head>
<body>
<div class="pageheader">
<h1>Message Board</h1>
</div>
<div class="speech-bubble">
<form action="/new" id="newmessage">
<p class="nick"><input type="text" placeholder="Nickname" name="nickname"></p>
<p class="message"><textarea placeholder="Message" name="message" form="newmessage"></textarea></p>
<p class="submit"><button class="submit" type="submit" form="newmessage" value="Submit">I say this!</button></p>
</form>
</div>
<%= yield %>
</body>
</html>
- ~/.views/v_message.erb
<% t_msg.each do |msg| %>
<div class="speech-bubble">
<p class="nick"><%= msg[:nick] %> says:</p>
<p class="message"> "<%= msg[:msg] %>"</p>
</div>
<% end %>
2nd step: Read messages from the db
- Create a system user
messageboarduser
(FRUM) [alexlai@n2MnJaro message-board]$ sudo adduser messageboarduser
[sudo] password for alexlai:
Login name for new user: messageboarduser
User ID ('UID') [ defaults to next available ]:
egrep: warning: egrep is obsolescent; using grep -E
egrep: warning: egrep is obsolescent; using grep -E
Initial group [ users ]:
Additional UNIX groups:
Users can belong to additional UNIX groups on the system.
For local users using graphical desktop login managers such
as XDM/KDM, users may need to be members of additional groups
to access the full functionality of removable media devices.
* Security implications *
Please be aware that by adding users to additional groups may
potentially give access to the removable media of other users.
If you are creating a new user for remote shell access only,
users do not need to belong to any additional groups as standard,
so you may press ENTER at the next prompt.
Press ENTER to continue without adding any additional groups
Or press the UP arrow key to add/select/edit additional groups
:
Home directory [ /home/messageboarduser ]
Shell [ /bin/bash ]
Expiry date (YYYY-MM-DD) []:
New account will be created as follows:
---------------------------------------
Login name.......: messageboarduser
UID..............: [ Next available ]
Initial group....: users
Additional groups: [ None ]
Home directory...: /home/messageboarduser
Shell............: /bin/bash
Expiry date......: [ Never ]
This is it... if you want to bail out, hit Control-C. Otherwise, press
ENTER to go ahead and make the account.
Creating new account...
Changing finger information for messageboarduser.
Name []: MessageBoardUser
Office []:
Office Phone []:
Home Phone []:
Finger information changed.
New password: messageboarduser
Retype new password: messageboarduser
passwd: password updated successfully
Account setup complete.
$ id messageboarduser
uid=1027(messageboarduser) gid=984(users) groups=984(users)
- Create a new user and database in PostgreSQL
(FRUM) [alexlai@n2MnJaro message-board]$ sudo -iu postgres
[postgres@n2MnJaro ~]$ psql
psql (15.1)
Type "help" for help.
postgres=# CREATE USER messageboarduser WITH PASSWORD 'messageboarduser';
CREATE ROLE
postgres=# CREATE DATABASE messageboard OWNER messageboarduser;
CREATE DATABASE
postgres=# \l
List of databases
Name | Owner | Encoding | Collate | Ctype | ICU Locale | Locale Provider | Access privileges
---------------+------------------+----------+-------------+-------------+------------+-----------------+-----------------------
messageboard | messageboarduser | UTF8 | en_US.UTF-8 | en_US.UTF-8 | | libc |
pgroonga_test | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | | libc |
postgres | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | | libc |
template0 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | | libc | =c/postgres +
| | | | | | | postgres=CTc/postgres
template1 | postgres | UTF8 | en_US.UTF-8 | en_US.UTF-8 | | libc | =c/postgres +
| | | | | | | postgres=CTc/postgres
(5 rows)
- Sign into postgreSQL as messageboarduser and define tables
(FRUM) [alexlai@n2MnJaro message-board]$ sudo -iu messageboarduser
[messageboarduser@n2MnJaro ~]$ psql messageboard
psql (15.1)
Type "help" for help.
messageboard=> CREATE TABLE messageboardmessages (
message_id varchar(36) PRIMARY KEY,
nickname varchar(30) NOT NULL,
message varchar(200) NOT NULL,
timestamp varchar(50) NOT NULL
);
CREATE TABLE
messageboard=> \d
List of relations
Schema | Name | Type | Owner
--------+----------------------+-------+------------------
public | messageboardmessages | table | messageboarduser
(1 row)
messageboard=> \d messageboardmessages
Table "public.messageboardmessages"
Column | Type | Collation | Nullable | Default
------------+------------------------+-----------+----------+---------
message_id | character varying(36) | | not null |
nickname | character varying(30) | | not null |
message | character varying(200) | | not null |
timestamp | character varying(50) | | not null |
Indexes:
"messageboardmessages_pkey" PRIMARY KEY, btree (message_id)
messageboard=> INSERT INTO messageboardmessages(message_id, nickname, message, timestamp)
VALUES ('201519e8-f3e7-4fcf-9349-3f81b94e908c', 'PostgreSQLUser', 'Hello from PostgreSQL', '2018-11-30 10:48:19 +0000');
INSERT 0 1
messageboard=> SELECT * FROM messageboardmessages;
message_id | nickname | message | timestamp
--------------------------------------+----------------+-----------------------+---------------------------
201519e8-f3e7-4fcf-9349-3f81b94e908c | PostgreSQLUser | Hello from PostgreSQL | 2018-11-30 10:48:19 +0000
(1 row)
- Enhance the app with reading from the db
- Gemfile as, then
bundler install
# frozen_string_literal: true
source "https://rubygems.org"
# gem "rails"
gem "sinatra"
gem "thin"
gem "rerun"
gem "pg"
- enhanced app.rb
require 'sinatra'
require 'pg'
# Listen on all interfaces in the development environment
# This is needed when we run from Cloud 9 environment
# source: https://gist.github.com/jhabdas/5945768
set :bind, '0.0.0.0'
set :port, 8080
get '/' do
t_msg = []
begin
# connect to the database
conection = PG.connect :dbname => 'messageboard', :user => 'messageboarduser', :password => 'messageboarduser'
# read data from the database
t_messages = conection.exec 'SELECT * FROM messageboardmessages'
# map data to t_msg, which is provided to the erb later
t_messages.each do |s_message|
t_msg.push({ nick: s_message['nickname'], msg: s_message['message'] })
end
rescue PG::Error => e
val_error = e.message
ensure
conection.close if conection
end
# call erb, pass parameters to it
erb :v_message, :layout => :l_main, :locals => {:t_msg => t_msg, :val_error => val_error}
end
3rd step: Enable posting of messages and save them in the database
- Generating unique identifiers
- Gemfile as, then bundler installer`
# frozen_string_literal: true
source "https://rubygems.org"
# gem "rails"
gem "sinatra"
gem "thin"
gem "rerun"
gem "pg"
gem 'uuidtools'
- Validation of input to avoid SQL injection
- we will skip this for I would like to enter japanese as inout string
- Please read Guides about SQL injections regarding Ruby can be found here:
- Generating time stamp