tina4ruby 3.10.40 → 3.10.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +56 -583
- data/lib/tina4/orm.rb +6 -3
- data/lib/tina4/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9ce1c810129bd38cc0cb779f3f9dd1c63914e9cccbf9e5c704b85502da5e8255
|
|
4
|
+
data.tar.gz: 19dd3a30fc0a16c65824867829448a96a75e3eccdda4548540dcd84c7b8b18a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 410b5805f04b18011d4a7b4bd4cf09c66f457c837d3a65ebe7b71ef849f4b089b0900072fd0a0172c8c93757cce6306db1c535ec558a71dd28148f211fc62b38
|
|
7
|
+
data.tar.gz: 539f1b4dc7e0aa29c4ae3a5a9ef3dea49f9bb91381772ccbd490e7a254d33550247f2a17177f7caabc8ec72dbdda8648e24c576388aef574404553da4e8cba84
|
data/README.md
CHANGED
|
@@ -1,635 +1,112 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<img src="https://tina4.com/logo.svg" alt="Tina4" width="200">
|
|
3
3
|
</p>
|
|
4
|
-
|
|
5
4
|
<h1 align="center">Tina4 Ruby</h1>
|
|
6
5
|
<h3 align="center">This Is Now A 4Framework</h3>
|
|
7
|
-
|
|
8
|
-
<p align="center">
|
|
9
|
-
Laravel joy. Ruby speed. 10x less code. Zero third-party dependencies.
|
|
10
|
-
</p>
|
|
11
|
-
|
|
6
|
+
<p align="center">54 built-in features. Zero runtime dependencies. One require, everything works.</p>
|
|
12
7
|
<p align="center">
|
|
13
|
-
<a href="https://rubygems.org/gems/
|
|
14
|
-
<img src="https://img.shields.io/badge/tests-1%
|
|
15
|
-
<img src="https://img.shields.io/badge/features-
|
|
8
|
+
<a href="https://rubygems.org/gems/tina4ruby"><img src="https://img.shields.io/gem/v/tina4ruby?color=7b1fa2&label=RubyGems" alt="RubyGems"></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/tests-1%2C793%20passing-brightgreen" alt="Tests">
|
|
10
|
+
<img src="https://img.shields.io/badge/features-54-blue" alt="Features">
|
|
16
11
|
<img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero Deps">
|
|
17
12
|
<a href="https://tina4.com"><img src="https://img.shields.io/badge/docs-tina4.com-7b1fa2" alt="Docs"></a>
|
|
18
13
|
</p>
|
|
19
14
|
|
|
20
|
-
<p align="center">
|
|
21
|
-
<a href="https://tina4.com">Documentation</a> •
|
|
22
|
-
<a href="#getting-started">Getting Started</a> •
|
|
23
|
-
<a href="#features">Features</a> •
|
|
24
|
-
<a href="#cli-reference">CLI Reference</a> •
|
|
25
|
-
<a href="https://tina4.com">tina4.com</a>
|
|
26
|
-
</p>
|
|
27
|
-
|
|
28
15
|
---
|
|
29
16
|
|
|
30
17
|
## Quick Start
|
|
31
18
|
|
|
32
|
-
```bash
|
|
33
|
-
# Install the Tina4 CLI
|
|
34
|
-
cargo install tina4 # or download binary from https://github.com/tina4stack/tina4/releases
|
|
35
|
-
|
|
36
|
-
# Create a project
|
|
37
|
-
tina4 init ruby ./my-app
|
|
38
|
-
|
|
39
|
-
# Run it
|
|
40
|
-
cd my-app && tina4 serve
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
Open http://localhost:7147 — your app is running.
|
|
44
|
-
|
|
45
|
-
<details>
|
|
46
|
-
<summary><strong>Without the Tina4 CLI</strong></summary>
|
|
47
|
-
|
|
48
|
-
```bash
|
|
49
|
-
# 1. Create project
|
|
50
|
-
mkdir my-app && cd my-app
|
|
51
|
-
echo 'source "https://rubygems.org"' > Gemfile
|
|
52
|
-
echo 'gem "tina4-ruby", "~> 3.0"' >> Gemfile
|
|
53
|
-
bundle install
|
|
54
|
-
|
|
55
|
-
# 2. Create entry point
|
|
56
|
-
cat > app.rb << 'EOF'
|
|
57
|
-
require "tina4"
|
|
58
|
-
Tina4.initialize!(__dir__)
|
|
59
|
-
app = Tina4::RackApp.new
|
|
60
|
-
Tina4::WebServer.new(app, host: "0.0.0.0", port: 7147).start
|
|
61
|
-
EOF
|
|
62
|
-
|
|
63
|
-
# 3. Create .env
|
|
64
|
-
echo 'TINA4_DEBUG=true' > .env
|
|
65
|
-
echo 'TINA4_LOG_LEVEL=ALL' >> .env
|
|
66
|
-
|
|
67
|
-
# 4. Create route directory
|
|
68
|
-
mkdir -p src/routes
|
|
69
|
-
|
|
70
|
-
# 5. Run
|
|
71
|
-
bundle exec ruby app.rb
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Open http://localhost:7147
|
|
75
|
-
|
|
76
|
-
</details>
|
|
77
|
-
|
|
78
|
-
---
|
|
79
|
-
|
|
80
|
-
## What's Included
|
|
81
|
-
|
|
82
|
-
Every feature is built from scratch -- no gem install, no node_modules, no third-party runtime dependencies in core.
|
|
83
|
-
|
|
84
|
-
| Category | Features |
|
|
85
|
-
|----------|----------|
|
|
86
|
-
| **HTTP** | Rack 3 server, block routing, path params (`{id:int}`, `{p:path}`), middleware pipeline, CORS, rate limiting, graceful shutdown |
|
|
87
|
-
| **Templates** | Frond engine (Twig-compatible), inheritance, partials, 35+ filters, macros, fragment caching, sandboxing |
|
|
88
|
-
| **ORM** | Active Record, typed fields with validation, soft delete, relationships (`has_one`/`has_many`/`belongs_to`), scopes, result caching, multi-database |
|
|
89
|
-
| **Database** | SQLite, PostgreSQL, MySQL, MSSQL, Firebird -- unified adapter interface, query caching (TINA4_DB_CACHE=true for 4x speedup) |
|
|
90
|
-
| **Auth** | Zero-dep JWT (HS256/RS256), sessions (file/Redis/Valkey/MongoDB/database), password hashing, form tokens |
|
|
91
|
-
| **API** | Swagger/OpenAPI auto-generation, GraphQL with ORM auto-schema and GraphiQL IDE, WSDL/SOAP with auto WSDL |
|
|
92
|
-
| **Background** | Queue (SQLite/RabbitMQ/Kafka/MongoDB) with priority, delayed jobs, retry, batch processing |
|
|
93
|
-
| **Real-time** | Native WebSocket (RFC 6455), per-path routing, connection manager |
|
|
94
|
-
| **Frontend** | tina4-css (~24 KB), frond.js helper, SCSS compiler, live reload, CSS hot-reload |
|
|
95
|
-
| **DX** | Dev admin dashboard, error overlay, request inspector, AI tool integration, Carbonah green benchmarks |
|
|
96
|
-
| **Data** | Migrations with rollback, 50+ fake data generators, ORM and table seeders |
|
|
97
|
-
| **Other** | REST client, localization (6 languages), cache (memory/Redis/file), event system, inline testing, messenger (.env driven), configurable error pages |
|
|
98
|
-
|
|
99
|
-
**1,578 tests across 38 built-in features. Zero dependencies. All Carbonah benchmarks rated A+.**
|
|
100
|
-
|
|
101
|
-
For full documentation visit **[tina4.com](https://tina4.com)**.
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Install
|
|
106
|
-
|
|
107
19
|
```bash
|
|
108
20
|
gem install tina4ruby
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### Optional database drivers
|
|
112
|
-
|
|
113
|
-
Install only what you need:
|
|
114
|
-
|
|
115
|
-
```bash
|
|
116
|
-
gem install pg # PostgreSQL
|
|
117
|
-
gem install mysql2 # MySQL / MariaDB (driver name: mysql)
|
|
118
|
-
gem install tiny_tds # Microsoft SQL Server
|
|
119
|
-
gem install fb # Firebird
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## Getting Started
|
|
125
|
-
|
|
126
|
-
### 1. Create a project
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
21
|
tina4ruby init my-app
|
|
130
|
-
cd my-app
|
|
22
|
+
cd my-app && tina4ruby serve
|
|
131
23
|
```
|
|
132
24
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
```
|
|
136
|
-
my-app/
|
|
137
|
-
├── app.rb # Entry point
|
|
138
|
-
├── .env # Configuration
|
|
139
|
-
├── Gemfile
|
|
140
|
-
├── src/
|
|
141
|
-
│ ├── routes/ # API + page routes (auto-discovered)
|
|
142
|
-
│ ├── orm/ # Database models
|
|
143
|
-
│ ├── app/ # Service classes and shared helpers
|
|
144
|
-
│ ├── templates/ # Frond/Twig templates
|
|
145
|
-
│ ├── seeds/ # Database seeders
|
|
146
|
-
│ ├── scss/ # SCSS (auto-compiled to public/css/)
|
|
147
|
-
│ └── public/ # Static assets served at /
|
|
148
|
-
├── migrations/ # SQL migration files
|
|
149
|
-
└── tests/ # RSpec tests
|
|
150
|
-
```
|
|
25
|
+
Open http://localhost:7147
|
|
151
26
|
|
|
152
|
-
|
|
27
|
+
---
|
|
153
28
|
|
|
154
|
-
|
|
29
|
+
## Code Examples
|
|
155
30
|
|
|
156
31
|
```ruby
|
|
157
32
|
Tina4.get "/api/hello" do |request, response|
|
|
158
|
-
response.
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
Tina4.get "/api/hello/{name}" do |request, response|
|
|
162
|
-
response.json({ message: "Hello, #{request.params["name"]}!" })
|
|
33
|
+
response.call({ message: "Hello from Tina4!" }, Tina4::HTTP_OK)
|
|
163
34
|
end
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Visit `http://localhost:7147/api/hello` -- routes are auto-discovered, no requires needed.
|
|
167
|
-
|
|
168
|
-
### 3. Add a database
|
|
169
|
-
|
|
170
|
-
Edit `.env`:
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
DATABASE_URL=sqlite://data/app.db
|
|
174
|
-
DATABASE_USERNAME=
|
|
175
|
-
DATABASE_PASSWORD=
|
|
176
|
-
```
|
|
177
35
|
|
|
178
|
-
Create and run a migration:
|
|
179
|
-
|
|
180
|
-
```bash
|
|
181
|
-
tina4ruby migrate --create "create users table"
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Edit the generated SQL:
|
|
185
|
-
|
|
186
|
-
```sql
|
|
187
|
-
CREATE TABLE IF NOT EXISTS users (
|
|
188
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
189
|
-
name TEXT NOT NULL,
|
|
190
|
-
email TEXT NOT NULL,
|
|
191
|
-
created_at TEXT DEFAULT CURRENT_TIMESTAMP
|
|
192
|
-
);
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
```bash
|
|
196
|
-
tina4ruby migrate
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### 4. Create an ORM model
|
|
200
|
-
|
|
201
|
-
Create `src/orm/user.rb`:
|
|
202
|
-
|
|
203
|
-
```ruby
|
|
204
36
|
class User < Tina4::ORM
|
|
205
37
|
integer_field :id, primary_key: true, auto_increment: true
|
|
206
|
-
string_field
|
|
207
|
-
string_field
|
|
208
|
-
datetime_field :created_at
|
|
38
|
+
string_field :name
|
|
39
|
+
string_field :email
|
|
209
40
|
end
|
|
210
|
-
```
|
|
211
41
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
Create `src/routes/users.rb`:
|
|
215
|
-
|
|
216
|
-
```ruby
|
|
217
|
-
Tina4.get "/api/users" do |request, response|
|
|
218
|
-
response.json(User.all(limit: 100).map(&:to_hash))
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
Tina4.get "/api/users/{id}" do |request, response|
|
|
222
|
-
user = User.find(request.params["id"])
|
|
223
|
-
if user
|
|
224
|
-
response.json(user.to_hash)
|
|
225
|
-
else
|
|
226
|
-
response.json({ error: "Not found" }, 404)
|
|
227
|
-
end
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
Tina4.post "/api/users", auth: false do |request, response|
|
|
231
|
-
user = User.create(request.json_body)
|
|
232
|
-
response.json(user.to_hash, 201)
|
|
233
|
-
end
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
### 6. Add a template
|
|
237
|
-
|
|
238
|
-
Create `src/templates/base.twig`:
|
|
239
|
-
|
|
240
|
-
```twig
|
|
241
|
-
<!DOCTYPE html>
|
|
242
|
-
<html>
|
|
243
|
-
<head>
|
|
244
|
-
<title>{% block title %}My App{% endblock %}</title>
|
|
245
|
-
<link rel="stylesheet" href="/css/tina4.min.css">
|
|
246
|
-
{% block stylesheets %}{% endblock %}
|
|
247
|
-
</head>
|
|
248
|
-
<body>
|
|
249
|
-
{% block content %}{% endblock %}
|
|
250
|
-
<script src="/js/frond.js"></script>
|
|
251
|
-
{% block javascripts %}{% endblock %}
|
|
252
|
-
</body>
|
|
253
|
-
</html>
|
|
42
|
+
db = Tina4::Database.new("sqlite://app.db")
|
|
254
43
|
```
|
|
255
44
|
|
|
256
|
-
Create `src/templates/pages/home.twig`:
|
|
257
|
-
|
|
258
|
-
```twig
|
|
259
|
-
{% extends "base.twig" %}
|
|
260
|
-
{% block content %}
|
|
261
|
-
<div class="container mt-4">
|
|
262
|
-
<h1>{{ title }}</h1>
|
|
263
|
-
<ul>
|
|
264
|
-
{% for user in users %}
|
|
265
|
-
<li>{{ user.name }} -- {{ user.email }}</li>
|
|
266
|
-
{% endfor %}
|
|
267
|
-
</ul>
|
|
268
|
-
</div>
|
|
269
|
-
{% endblock %}
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
Render it from a route:
|
|
273
|
-
|
|
274
|
-
```ruby
|
|
275
|
-
Tina4.get "/" do |request, response|
|
|
276
|
-
users = User.all(limit: 20).map(&:to_hash)
|
|
277
|
-
response.render("pages/home.twig", { title: "Users", users: users })
|
|
278
|
-
end
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### 7. Seed, test, deploy
|
|
282
|
-
|
|
283
|
-
```bash
|
|
284
|
-
tina4ruby seed # Run seeders from src/seeds/
|
|
285
|
-
tina4ruby test # Run test suite
|
|
286
|
-
tina4ruby build # Build distributable
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
For the complete step-by-step guide, visit **[tina4.com](https://tina4.com)**.
|
|
290
|
-
|
|
291
45
|
---
|
|
292
46
|
|
|
293
|
-
##
|
|
294
|
-
|
|
295
|
-
### Routing
|
|
296
|
-
|
|
297
|
-
```ruby
|
|
298
|
-
Tina4.get "/api/items" do |request, response| # Public by default
|
|
299
|
-
response.json({ items: [] })
|
|
300
|
-
end
|
|
301
|
-
|
|
302
|
-
Tina4.post "/api/webhook", auth: false do |request, response| # Make a write route public
|
|
303
|
-
response.json({ ok: true })
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
Tina4.secure_get "/api/admin/stats" do |request, response| # Protect a GET route
|
|
307
|
-
response.json({ secret: true })
|
|
308
|
-
end
|
|
309
|
-
```
|
|
310
|
-
|
|
311
|
-
Path parameter types: `{id}` (string), `{id:int}`, `{price:float}`, `{path:path}` (greedy).
|
|
312
|
-
|
|
313
|
-
### ORM
|
|
314
|
-
|
|
315
|
-
Active Record with typed fields, validation, soft delete, relationships, scopes, and multi-database support.
|
|
316
|
-
|
|
317
|
-
```ruby
|
|
318
|
-
class User < Tina4::ORM
|
|
319
|
-
integer_field :id, primary_key: true, auto_increment: true
|
|
320
|
-
string_field :name, nullable: false, length: 100
|
|
321
|
-
string_field :email, length: 255
|
|
322
|
-
string_field :role, default: "user"
|
|
323
|
-
integer_field :age, default: 0
|
|
324
|
-
end
|
|
325
|
-
|
|
326
|
-
# CRUD
|
|
327
|
-
user = User.new(name: "Alice", email: "alice@example.com")
|
|
328
|
-
user.save
|
|
329
|
-
user = User.find(1)
|
|
330
|
-
user.delete
|
|
331
|
-
|
|
332
|
-
# Relationships
|
|
333
|
-
orders = user.has_many("Order", "user_id")
|
|
334
|
-
profile = user.has_one("Profile", "user_id")
|
|
335
|
-
|
|
336
|
-
# Soft delete, scopes, caching
|
|
337
|
-
user.soft_delete
|
|
338
|
-
active_admins = User.scope("active").scope("admin").select
|
|
339
|
-
users = User.cached("SELECT * FROM users", ttl: 300)
|
|
340
|
-
|
|
341
|
-
# Multi-database
|
|
342
|
-
Tina4.database = Tina4::Database.new("sqlite://app.db") # Default
|
|
343
|
-
Tina4.database("audit", Tina4::Database.new("sqlite://audit.db")) # Named
|
|
344
|
-
|
|
345
|
-
class AuditLog < Tina4::ORM
|
|
346
|
-
self.db_name = "audit" # Uses named connection
|
|
347
|
-
end
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Database
|
|
351
|
-
|
|
352
|
-
Unified interface across 5 engines:
|
|
353
|
-
|
|
354
|
-
```ruby
|
|
355
|
-
db = Tina4::Database.new("sqlite://data/app.db")
|
|
356
|
-
db = Tina4::Database.new("postgres://localhost:5432/mydb", username: "user", password: "pass")
|
|
357
|
-
db = Tina4::Database.new("mysql://localhost:3306/mydb", username: "user", password: "pass")
|
|
358
|
-
db = Tina4::Database.new("mssql://localhost:1433/mydb", username: "sa", password: "pass")
|
|
359
|
-
db = Tina4::Database.new("firebird://localhost:3050/path/to/db", username: "SYSDBA", password: "masterkey")
|
|
360
|
-
|
|
361
|
-
result = db.fetch("SELECT * FROM users WHERE age > ?", [18], limit: 20, offset: 0)
|
|
362
|
-
row = db.fetch_one("SELECT * FROM users WHERE id = ?", [1])
|
|
363
|
-
db.insert("users", { name: "Alice", email: "alice@test.com" })
|
|
364
|
-
db.commit
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
### Middleware
|
|
368
|
-
|
|
369
|
-
```ruby
|
|
370
|
-
Tina4.before("/protected") do |request, response|
|
|
371
|
-
unless request.headers["authorization"]
|
|
372
|
-
return request, response.json({ error: "Unauthorized" }, 401)
|
|
373
|
-
end
|
|
374
|
-
end
|
|
375
|
-
|
|
376
|
-
Tina4.get "/protected" do |request, response|
|
|
377
|
-
response.json({ secret: true })
|
|
378
|
-
end
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### JWT Authentication
|
|
382
|
-
|
|
383
|
-
```ruby
|
|
384
|
-
token = Tina4::Auth.get_token({ user_id: 42 })
|
|
385
|
-
result = Tina4::Auth.valid_token(token)
|
|
386
|
-
payload = Tina4::Auth.get_payload(token)
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
POST/PUT/PATCH/DELETE routes require `Authorization: Bearer <token>` by default. Use `auth: false` to make public, `secure_get` to protect GET routes.
|
|
390
|
-
|
|
391
|
-
### Sessions
|
|
392
|
-
|
|
393
|
-
```ruby
|
|
394
|
-
request.session["user_id"] = 42
|
|
395
|
-
user_id = request.session["user_id"]
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
Backends: file (default), Redis, MongoDB. Set via `TINA4_SESSION_HANDLER` in `.env`.
|
|
399
|
-
|
|
400
|
-
### Queues
|
|
401
|
-
|
|
402
|
-
```ruby
|
|
403
|
-
queue = Tina4::Queue.new(topic: "emails")
|
|
404
|
-
queue.produce("emails", { to: "alice@example.com" })
|
|
405
|
-
|
|
406
|
-
queue.consume("emails") { |msg| send_email(msg.payload) }
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### GraphQL
|
|
410
|
-
|
|
411
|
-
```ruby
|
|
412
|
-
gql = Tina4::GraphQL.new
|
|
413
|
-
gql.schema.from_orm(User)
|
|
414
|
-
gql.register_route("/graphql") # GET = GraphiQL IDE, POST = queries
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
### WebSocket
|
|
418
|
-
|
|
419
|
-
```ruby
|
|
420
|
-
ws = Tina4::WebSocketManager.new
|
|
421
|
-
|
|
422
|
-
ws.route "/ws/chat" do |connection, message|
|
|
423
|
-
ws.broadcast("/ws/chat", "User said: #{message}")
|
|
424
|
-
end
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
### Swagger / OpenAPI
|
|
428
|
-
|
|
429
|
-
Auto-generated at `/swagger`:
|
|
430
|
-
|
|
431
|
-
```ruby
|
|
432
|
-
Tina4.get "/api/users", swagger_meta: {
|
|
433
|
-
summary: "Get all users",
|
|
434
|
-
tags: ["users"]
|
|
435
|
-
} do |request, response|
|
|
436
|
-
response.json(User.all.map(&:to_hash))
|
|
437
|
-
end
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
### Event System
|
|
441
|
-
|
|
442
|
-
```ruby
|
|
443
|
-
Tina4.on("user.created", priority: 10) do |user|
|
|
444
|
-
send_notification("New user: #{user[:name]}")
|
|
445
|
-
end
|
|
446
|
-
|
|
447
|
-
Tina4.emit("user.created", { name: "Alice" })
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
### Template Engine (Frond)
|
|
451
|
-
|
|
452
|
-
Twig-compatible, 35+ filters, macros, inheritance, fragment caching, sandboxing:
|
|
453
|
-
|
|
454
|
-
```twig
|
|
455
|
-
{% extends "base.twig" %}
|
|
456
|
-
{% block content %}
|
|
457
|
-
<h1>{{ title | upper }}</h1>
|
|
458
|
-
{% for item in items %}
|
|
459
|
-
<p>{{ item.name }} -- {{ item.price | number_format(2) }}</p>
|
|
460
|
-
{% endfor %}
|
|
461
|
-
|
|
462
|
-
{% cache "sidebar" 300 %}
|
|
463
|
-
{% include "partials/sidebar.twig" %}
|
|
464
|
-
{% endcache %}
|
|
465
|
-
{% endblock %}
|
|
466
|
-
```
|
|
467
|
-
|
|
468
|
-
### CRUD Scaffolding
|
|
469
|
-
|
|
470
|
-
```ruby
|
|
471
|
-
Tina4.get "/admin/users" do |request, response|
|
|
472
|
-
response.json(Tina4::CRUD.to_crud(request, {
|
|
473
|
-
sql: "SELECT id, name, email FROM users",
|
|
474
|
-
title: "User Management",
|
|
475
|
-
primary_key: "id"
|
|
476
|
-
}))
|
|
477
|
-
end
|
|
478
|
-
```
|
|
479
|
-
|
|
480
|
-
### REST Client
|
|
481
|
-
|
|
482
|
-
```ruby
|
|
483
|
-
api = Tina4::API.new("https://api.example.com", headers: {
|
|
484
|
-
"Authorization" => "Bearer xyz"
|
|
485
|
-
})
|
|
486
|
-
result = api.get("/users/42")
|
|
487
|
-
```
|
|
488
|
-
|
|
489
|
-
### Data Seeder
|
|
490
|
-
|
|
491
|
-
```ruby
|
|
492
|
-
fake = Tina4::FakeData.new
|
|
493
|
-
fake.name # "Alice Johnson"
|
|
494
|
-
fake.email # "alice.johnson@example.com"
|
|
495
|
-
|
|
496
|
-
Tina4.seed_orm(User, count: 50)
|
|
497
|
-
```
|
|
498
|
-
|
|
499
|
-
### Email / Messenger
|
|
500
|
-
|
|
501
|
-
```ruby
|
|
502
|
-
mail = Tina4::Messenger.new
|
|
503
|
-
mail.send(to: "user@test.com", subject: "Welcome", body: "<h1>Hi!</h1>", html: true)
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
### In-Memory Cache
|
|
507
|
-
|
|
508
|
-
```ruby
|
|
509
|
-
cache = Tina4::Cache.new
|
|
510
|
-
cache.set("key", "value", ttl: 300)
|
|
511
|
-
cache.tag("users").flush
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
### SCSS, Localization, Inline Testing
|
|
515
|
-
|
|
516
|
-
- **SCSS**: Drop `.scss` in `src/scss/` -- auto-compiled to CSS. Variables, nesting, mixins, `@import`, `@extend`.
|
|
517
|
-
- **i18n**: JSON translation files, 6 languages (en, fr, af, zh, ja, es), placeholder interpolation.
|
|
518
|
-
- **Inline tests**: `test_method :add, assert_equal: [[5, 3], 8]` on any method.
|
|
519
|
-
|
|
520
|
-
---
|
|
521
|
-
|
|
522
|
-
## Dev Mode
|
|
523
|
-
|
|
524
|
-
Set `TINA4_DEBUG=true` in `.env` to enable:
|
|
47
|
+
## What's Included
|
|
525
48
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
49
|
+
| Category | Features |
|
|
50
|
+
|----------|----------|
|
|
51
|
+
| **Core HTTP** (7) | Router with path params (`{id:int}`, `{p:path}`), Server, Request/Response, Middleware pipeline, Static file serving, CORS |
|
|
52
|
+
| **Database** (6) | SQLite, PostgreSQL, MySQL, MSSQL, Firebird — unified adapter, connection pooling, query cache, transactions, race-safe ID generation, SQL dialect translation |
|
|
53
|
+
| **ORM** (7) | Active Record with typed fields, relationships (`has_one`/`has_many`/`belongs_to`), soft delete, QueryBuilder + MongoDB support, Auto-CRUD generator, migrations with rollback |
|
|
54
|
+
| **Auth & Security** (5) | JWT (HS256/RS256), password hashing (PBKDF2-SHA256), API key validation, rate limiting, CSRF form tokens |
|
|
55
|
+
| **Templating** (3) | Frond engine (Twig/Jinja2-compatible, pre-compiled 2.8× faster), SCSS auto-compilation, built-in CSS (~24 KB) |
|
|
56
|
+
| **API & Integration** (5) | HTTP client (zero-dep), GraphQL with ORM auto-schema + GraphiQL IDE, WSDL/SOAP with auto WSDL, WebSocket (RFC 6455) + Redis backplane, MCP server (24 dev tools) |
|
|
57
|
+
| **Background** (3) | Job queue (File/RabbitMQ/Kafka/MongoDB) with priority, delay, retry, dead letters — service runner — event system (on/emit/once/off) |
|
|
58
|
+
| **Data & Storage** (4) | Session (File/Redis/Valkey/MongoDB/DB), response cache (LRU, TTL), seeder + 50+ fake data generators, messenger (SMTP/IMAP) |
|
|
59
|
+
| **Developer Tools** (7) | Dev dashboard (11 tabs), dev toolbar, error overlay (Catppuccin Mocha), dev mailbox, hot reload + CSS hot-reload, code metrics (complexity, coupling, maintainability), AI context installer (7 tools) |
|
|
60
|
+
| **Utilities** (7) | DI container (transient + singleton), HtmlElement builder, inline testing (`@tests` decorator), i18n (6 languages), Swagger/OpenAPI auto-generation, CLI scaffolding (`generate model/route/migration/middleware`), structured logging |
|
|
61
|
+
|
|
62
|
+
**1,793 tests. Zero runtime dependencies. Full parity across Python, PHP, Ruby, and Node.js.**
|
|
530
63
|
|
|
531
64
|
---
|
|
532
65
|
|
|
533
66
|
## CLI Reference
|
|
534
67
|
|
|
535
68
|
```bash
|
|
536
|
-
tina4ruby
|
|
537
|
-
tina4ruby
|
|
538
|
-
tina4ruby
|
|
539
|
-
tina4ruby
|
|
540
|
-
tina4ruby
|
|
541
|
-
tina4ruby migrate --rollback # Rollback last batch
|
|
542
|
-
tina4ruby generate model <name> # Generate ORM model scaffold
|
|
543
|
-
tina4ruby generate route <name> # Generate route scaffold
|
|
544
|
-
tina4ruby generate migration <d> # Generate migration file
|
|
545
|
-
tina4ruby generate middleware <n># Generate middleware scaffold
|
|
546
|
-
tina4ruby seed # Run seeders from src/seeds/
|
|
547
|
-
tina4ruby routes # List all registered routes
|
|
548
|
-
tina4ruby test # Run test suite
|
|
549
|
-
tina4ruby build # Build distributable gem
|
|
550
|
-
tina4ruby ai [--all] # Detect AI tools and install context
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
### Production Server Auto-Detection
|
|
554
|
-
|
|
555
|
-
`tina4 serve` automatically detects and uses the best available production server:
|
|
556
|
-
|
|
557
|
-
- **Ruby**: Puma (if installed), otherwise WEBrick -- Puma gives 2.8x improvement
|
|
558
|
-
- Use `tina4ruby serve --production` to auto-install Puma
|
|
559
|
-
|
|
560
|
-
### Scaffolding with `tina4 generate`
|
|
561
|
-
|
|
562
|
-
Quickly scaffold new components:
|
|
563
|
-
|
|
564
|
-
```bash
|
|
565
|
-
tina4ruby generate model User # Creates src/orm/user.rb with field stubs
|
|
566
|
-
tina4ruby generate route users # Creates src/routes/users.rb with CRUD stubs
|
|
567
|
-
tina4ruby generate migration "add age" # Creates migration SQL file
|
|
568
|
-
tina4ruby generate middleware AuthLog # Creates middleware class
|
|
569
|
-
```
|
|
570
|
-
|
|
571
|
-
### ORM Relationships & Eager Loading
|
|
572
|
-
|
|
573
|
-
```ruby
|
|
574
|
-
# Relationships
|
|
575
|
-
orders = user.has_many("Order", "user_id")
|
|
576
|
-
profile = user.has_one("Profile", "user_id")
|
|
577
|
-
customer = order.belongs_to("Customer", "customer_id")
|
|
578
|
-
|
|
579
|
-
# Eager loading with include:
|
|
580
|
-
users = User.all(include: ["orders", "profile"])
|
|
69
|
+
tina4ruby serve [--port PORT]
|
|
70
|
+
tina4ruby migrate
|
|
71
|
+
tina4ruby seed
|
|
72
|
+
tina4ruby ai [--all]
|
|
73
|
+
tina4ruby generate model <name>
|
|
581
74
|
```
|
|
582
75
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
Enable query caching for up to 4x speedup on read-heavy workloads:
|
|
586
|
-
|
|
587
|
-
```bash
|
|
588
|
-
# .env
|
|
589
|
-
TINA4_DB_CACHE=true
|
|
590
|
-
```
|
|
76
|
+
---
|
|
591
77
|
|
|
592
|
-
|
|
78
|
+
## Performance
|
|
593
79
|
|
|
594
|
-
|
|
80
|
+
Benchmarked with `wrk` — 5,000 requests, 50 concurrent, median of 3 runs:
|
|
595
81
|
|
|
596
|
-
|
|
82
|
+
| Framework | JSON req/s | Deps | Features |
|
|
83
|
+
|-----------|-----------|------|----------|
|
|
84
|
+
| **Tina4 Ruby** | **10,243** | 0 | 54 |
|
|
85
|
+
| Sinatra | 9,548 | 5+ | ~4 |
|
|
597
86
|
|
|
598
|
-
|
|
87
|
+
Tina4 Ruby outperforms Sinatra while delivering **54 features vs ~4** — with zero runtime dependencies.
|
|
599
88
|
|
|
600
|
-
|
|
89
|
+
**Across all 4 Tina4 implementations:**
|
|
601
90
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
TINA4_DEBUG=true # Enable dev toolbar, error overlay
|
|
608
|
-
TINA4_LOG_LEVEL=ALL # ALL, DEBUG, INFO, WARNING, ERROR
|
|
609
|
-
TINA4_LOCALE=en # en, fr, af, zh, ja, es
|
|
610
|
-
TINA4_SESSION_HANDLER=SessionFileHandler
|
|
611
|
-
SWAGGER_TITLE=My API
|
|
612
|
-
```
|
|
91
|
+
| | Python | PHP | Ruby | Node.js |
|
|
92
|
+
|---|--------|-----|------|---------|
|
|
93
|
+
| **JSON req/s** | 6,508 | 29,293 | 10,243 | 84,771 |
|
|
94
|
+
| **Dependencies** | 0 | 0 | 0 | 0 |
|
|
95
|
+
| **Features** | 54 | 54 | 54 | 54 |
|
|
613
96
|
|
|
614
|
-
|
|
97
|
+
---
|
|
615
98
|
|
|
616
|
-
|
|
99
|
+
## Cross-Framework Parity
|
|
617
100
|
|
|
618
|
-
|
|
619
|
-
|-----------|-------------|-------|
|
|
620
|
-
| JSON Hello World | 0.000897 | A+ |
|
|
621
|
-
| Single DB Query | 0.000561 | A+ |
|
|
622
|
-
| Multiple DB Queries | 0.001402 | A+ |
|
|
623
|
-
| Template Rendering | 0.003351 | A+ |
|
|
624
|
-
| Large JSON Payload | 0.001019 | A+ |
|
|
625
|
-
| Plaintext Response | 0.000391 | A+ |
|
|
626
|
-
| CRUD Cycle | 0.000473 | A+ |
|
|
627
|
-
| Paginated Query | 0.001027 | A+ |
|
|
628
|
-
| Framework Startup | 0.00267 | A+ |
|
|
101
|
+
Tina4 ships identical features across four languages — same architecture, same conventions, same 54 features:
|
|
629
102
|
|
|
630
|
-
|
|
103
|
+
| | Python | PHP | Ruby | Node.js |
|
|
104
|
+
|---|--------|-----|------|---------|
|
|
105
|
+
| **Package** | `tina4-python` | `tina4stack/tina4php` | `tina4ruby` | `tina4-nodejs` |
|
|
106
|
+
| **Tests** | 2,066 | 1,427 | 1,793 | 1,950 |
|
|
107
|
+
| **Default port** | 7145 | 7146 | 7147 | 7148 |
|
|
631
108
|
|
|
632
|
-
|
|
109
|
+
**7,236 tests** across all 4 frameworks. See [tina4.com](https://tina4.com).
|
|
633
110
|
|
|
634
111
|
---
|
|
635
112
|
|
|
@@ -644,14 +121,10 @@ https://opensource.org/licenses/MIT
|
|
|
644
121
|
|
|
645
122
|
---
|
|
646
123
|
|
|
647
|
-
<p align="center"><b>Tina4</b> -- The framework that keeps out of the way of your coding.</p>
|
|
648
|
-
|
|
649
|
-
---
|
|
650
|
-
|
|
651
124
|
## Our Sponsors
|
|
652
125
|
|
|
653
126
|
**Sponsored with 🩵 by Code Infinity**
|
|
654
127
|
|
|
655
128
|
[<img src="https://codeinfinity.co.za/wp-content/uploads/2025/09/c8e-logo-github.png" alt="Code Infinity" width="100">](https://codeinfinity.co.za/about-open-source-policy?utm_source=github&utm_medium=website&utm_campaign=opensource_campaign&utm_id=opensource)
|
|
656
129
|
|
|
657
|
-
*Supporting open source communities
|
|
130
|
+
*Supporting open source communities • Innovate • Code • Empower*
|
data/lib/tina4/orm.rb
CHANGED
|
@@ -248,6 +248,11 @@ module Tina4
|
|
|
248
248
|
instances
|
|
249
249
|
end
|
|
250
250
|
|
|
251
|
+
def select_one(sql, params = [], include: nil)
|
|
252
|
+
results = select(sql, params, limit: 1, include: include)
|
|
253
|
+
results.first
|
|
254
|
+
end
|
|
255
|
+
|
|
251
256
|
def count(conditions = nil, params = [])
|
|
252
257
|
sql = "SELECT COUNT(*) as cnt FROM #{table_name}"
|
|
253
258
|
where_parts = []
|
|
@@ -346,9 +351,7 @@ module Tina4
|
|
|
346
351
|
if soft_delete
|
|
347
352
|
sql += " AND (#{soft_delete_field} IS NULL OR #{soft_delete_field} = 0)"
|
|
348
353
|
end
|
|
349
|
-
|
|
350
|
-
return nil unless result
|
|
351
|
-
from_hash(result)
|
|
354
|
+
select_one(sql, [id])
|
|
352
355
|
end
|
|
353
356
|
|
|
354
357
|
def find_by_filter(filter)
|
data/lib/tina4/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tina4ruby
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.10.
|
|
4
|
+
version: 3.10.41
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tina4 Team
|
|
@@ -262,7 +262,8 @@ dependencies:
|
|
|
262
262
|
- - "~>"
|
|
263
263
|
- !ruby/object:Gem::Version
|
|
264
264
|
version: '1.50'
|
|
265
|
-
description:
|
|
265
|
+
description: Tina4 for Ruby — 54 built-in features, zero dependencies. Full parity
|
|
266
|
+
with Python, PHP, and Node.js.
|
|
266
267
|
email:
|
|
267
268
|
- info@tina4.com
|
|
268
269
|
executables:
|
|
@@ -421,5 +422,5 @@ requirements: []
|
|
|
421
422
|
rubygems_version: 3.4.19
|
|
422
423
|
signing_key:
|
|
423
424
|
specification_version: 4
|
|
424
|
-
summary:
|
|
425
|
+
summary: Tina4 for Ruby — 54 built-in features, zero dependencies
|
|
425
426
|
test_files: []
|