tarsier 0.1.0

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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +175 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +984 -0
  5. data/exe/tarsier +7 -0
  6. data/lib/tarsier/application.rb +336 -0
  7. data/lib/tarsier/cli/commands/console.rb +87 -0
  8. data/lib/tarsier/cli/commands/generate.rb +85 -0
  9. data/lib/tarsier/cli/commands/help.rb +50 -0
  10. data/lib/tarsier/cli/commands/new.rb +59 -0
  11. data/lib/tarsier/cli/commands/routes.rb +139 -0
  12. data/lib/tarsier/cli/commands/server.rb +123 -0
  13. data/lib/tarsier/cli/commands/version.rb +14 -0
  14. data/lib/tarsier/cli/generators/app.rb +528 -0
  15. data/lib/tarsier/cli/generators/base.rb +93 -0
  16. data/lib/tarsier/cli/generators/controller.rb +91 -0
  17. data/lib/tarsier/cli/generators/middleware.rb +81 -0
  18. data/lib/tarsier/cli/generators/migration.rb +109 -0
  19. data/lib/tarsier/cli/generators/model.rb +109 -0
  20. data/lib/tarsier/cli/generators/resource.rb +27 -0
  21. data/lib/tarsier/cli/loader.rb +18 -0
  22. data/lib/tarsier/cli.rb +46 -0
  23. data/lib/tarsier/controller.rb +282 -0
  24. data/lib/tarsier/database.rb +588 -0
  25. data/lib/tarsier/errors.rb +77 -0
  26. data/lib/tarsier/middleware/base.rb +47 -0
  27. data/lib/tarsier/middleware/compression.rb +113 -0
  28. data/lib/tarsier/middleware/cors.rb +101 -0
  29. data/lib/tarsier/middleware/csrf.rb +88 -0
  30. data/lib/tarsier/middleware/logger.rb +74 -0
  31. data/lib/tarsier/middleware/rate_limit.rb +110 -0
  32. data/lib/tarsier/middleware/stack.rb +143 -0
  33. data/lib/tarsier/middleware/static.rb +124 -0
  34. data/lib/tarsier/model.rb +590 -0
  35. data/lib/tarsier/params.rb +269 -0
  36. data/lib/tarsier/query.rb +495 -0
  37. data/lib/tarsier/request.rb +274 -0
  38. data/lib/tarsier/response.rb +282 -0
  39. data/lib/tarsier/router/compiler.rb +173 -0
  40. data/lib/tarsier/router/node.rb +97 -0
  41. data/lib/tarsier/router/route.rb +119 -0
  42. data/lib/tarsier/router.rb +272 -0
  43. data/lib/tarsier/version.rb +5 -0
  44. data/lib/tarsier/websocket.rb +275 -0
  45. data/lib/tarsier.rb +167 -0
  46. data/sig/tarsier.rbs +485 -0
  47. metadata +230 -0
data/README.md ADDED
@@ -0,0 +1,984 @@
1
+ # Tarsier
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/tarsier-rb/tarsier/main/assets/logo.png" alt="Tarsier Logo" width="200">
5
+ </p>
6
+
7
+ <p align="center">
8
+ A modern, high-performance Ruby web framework designed for speed, simplicity, and developer productivity.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.ruby-lang.org/"><img src="https://img.shields.io/badge/ruby-%3E%3D%203.3-red.svg" alt="Ruby"></a>
13
+ <a href="LICENSE.txt"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
14
+ </p>
15
+
16
+ ---
17
+
18
+ Tarsier delivers sub-millisecond routing through a compiled radix tree architecture while maintaining an intuitive API that feels natural to Ruby developers.
19
+
20
+ ---
21
+
22
+ ## Table of Contents
23
+
24
+ - [Features](#features)
25
+ - [Requirements](#requirements)
26
+ - [Installation](#installation)
27
+ - [Quick Start](#quick-start)
28
+ - [Command Line Interface](#command-line-interface)
29
+ - [Routing](#routing)
30
+ - [Controllers](#controllers)
31
+ - [Request and Response](#request-and-response)
32
+ - [Middleware](#middleware)
33
+ - [Database](#database)
34
+ - [WebSocket and SSE](#websocket-and-sse)
35
+ - [Models](#models)
36
+ - [Configuration](#configuration)
37
+ - [Testing](#testing)
38
+ - [Benchmarks](#benchmarks)
39
+ - [Contributing](#contributing)
40
+ - [License](#license)
41
+
42
+ ---
43
+
44
+ ## Features
45
+
46
+ - **High Performance**: Compiled radix tree router achieving 700,000+ routes per second
47
+ - **Zero Runtime Dependencies**: Core framework operates without external dependencies
48
+ - **Multi-Database Support**: Built-in support for SQLite, PostgreSQL, and MySQL
49
+ - **Modern Ruby**: Built for Ruby 3.3+ with YJIT and Fiber scheduler support
50
+ - **Async Native**: First-class support for Fiber-based concurrency patterns
51
+ - **Type Safety Ready**: Complete RBS type signatures for static analysis
52
+ - **Developer Experience**: Intuitive APIs, detailed error messages, minimal boilerplate
53
+ - **Production Ready**: Built-in middleware for CORS, CSRF, rate limiting, and compression
54
+
55
+ ---
56
+
57
+ ## Requirements
58
+
59
+ - Ruby 3.3 or higher
60
+ - Rack 3.0+ (for Rack compatibility layer)
61
+
62
+ ---
63
+
64
+ ## Installation
65
+
66
+ Add Tarsier to your application's Gemfile:
67
+
68
+ ```ruby
69
+ gem 'tarsier'
70
+ ```
71
+
72
+ Then execute:
73
+
74
+ ```bash
75
+ bundle install
76
+ ```
77
+
78
+ Or install globally for CLI access:
79
+
80
+ ```bash
81
+ gem install tarsier
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Quick Start
87
+
88
+ Create a new application:
89
+
90
+ ```bash
91
+ tarsier new my_app
92
+ cd my_app
93
+ bin/server
94
+ ```
95
+
96
+ Or create a minimal application manually:
97
+
98
+ ```ruby
99
+ # app.rb
100
+ require 'tarsier'
101
+
102
+ # Flask-style minimal syntax
103
+ app = Tarsier.app do
104
+ get('/') { { message: 'Hello, World!' } }
105
+
106
+ get('/users/:id') do |req|
107
+ { user_id: req.params[:id] }
108
+ end
109
+
110
+ post('/users') do |req, res|
111
+ res.status = 201
112
+ { created: true, name: req.params[:name] }
113
+ end
114
+ end
115
+
116
+ run app
117
+ ```
118
+
119
+ Start the server:
120
+
121
+ ```bash
122
+ rackup app.rb
123
+ ```
124
+
125
+ Visit `http://localhost:9292` to see your application running.
126
+
127
+ ---
128
+
129
+ ## Command Line Interface
130
+
131
+ Tarsier provides a comprehensive CLI for project scaffolding and development workflows.
132
+
133
+ ### Creating Applications
134
+
135
+ ```bash
136
+ # Standard application with views
137
+ tarsier new my_app
138
+
139
+ # API-only application (no view layer)
140
+ tarsier new my_api --api
141
+
142
+ # Minimal application structure
143
+ tarsier new my_app --minimal
144
+
145
+ # Skip optional setup steps
146
+ tarsier new my_app --skip-git --skip-bundle
147
+ ```
148
+
149
+ ### Code Generation
150
+
151
+ ```bash
152
+ # Generate a controller with actions
153
+ tarsier generate controller users index show create update destroy
154
+
155
+ # Generate a model with attributes
156
+ tarsier generate model user name:string email:string created_at:datetime
157
+
158
+ # Generate a complete resource (model + controller + routes)
159
+ tarsier generate resource post title:string body:text published:boolean
160
+
161
+ # Generate custom middleware
162
+ tarsier generate middleware authentication
163
+
164
+ # Generate a database migration
165
+ tarsier generate migration create_users name:string email:string
166
+ tarsier generate migration add_role_to_users
167
+ ```
168
+
169
+ Shorthand aliases are available:
170
+
171
+ ```bash
172
+ tarsier g controller users index show
173
+ tarsier g model user name:string
174
+ ```
175
+
176
+ ### Development Server
177
+
178
+ ```bash
179
+ # Start with defaults (port 7827)
180
+ tarsier server
181
+
182
+ # Custom port
183
+ tarsier server -p 4000
184
+
185
+ # Specify environment
186
+ tarsier server -e production
187
+
188
+ # Bind to specific host
189
+ tarsier server -b 127.0.0.1
190
+ ```
191
+
192
+ ### Interactive Console
193
+
194
+ ```bash
195
+ # Development console
196
+ tarsier console
197
+
198
+ # Production console
199
+ tarsier console -e production
200
+ ```
201
+
202
+ ### Route Inspection
203
+
204
+ ```bash
205
+ # Display all routes
206
+ tarsier routes
207
+
208
+ # Filter routes by pattern
209
+ tarsier routes -g users
210
+
211
+ # Output as JSON
212
+ tarsier routes --json
213
+ ```
214
+
215
+ ### Version Information
216
+
217
+ ```bash
218
+ tarsier version
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Routing
224
+
225
+ Tarsier uses a compiled radix tree router optimized for high-throughput applications.
226
+
227
+ ### Basic Routes
228
+
229
+ ```ruby
230
+ Tarsier.routes do
231
+ get '/users', to: 'users#index'
232
+ post '/users', to: 'users#create'
233
+ get '/users/:id', to: 'users#show'
234
+ put '/users/:id', to: 'users#update'
235
+ delete '/users/:id', to: 'users#destroy'
236
+ end
237
+ ```
238
+
239
+ ### Route Parameters
240
+
241
+ ```ruby
242
+ Tarsier.routes do
243
+ # Required parameters
244
+ get '/users/:id', to: 'users#show'
245
+
246
+ # Multiple parameters
247
+ get '/posts/:post_id/comments/:id', to: 'comments#show'
248
+
249
+ # Wildcard parameters
250
+ get '/files/*path', to: 'files#show'
251
+
252
+ # Parameter constraints
253
+ get '/posts/:id', to: 'posts#show', constraints: { id: /\d+/ }
254
+ end
255
+ ```
256
+
257
+ ### RESTful Resources
258
+
259
+ ```ruby
260
+ Tarsier.routes do
261
+ # Full resource routes
262
+ resources :posts
263
+
264
+ # Nested resources
265
+ resources :posts do
266
+ resources :comments
267
+ end
268
+
269
+ # Limit actions
270
+ resources :posts, only: [:index, :show]
271
+ resources :posts, except: [:destroy]
272
+
273
+ # Singular resource
274
+ resource :profile
275
+ end
276
+ ```
277
+
278
+ ### Namespaces and Scopes
279
+
280
+ ```ruby
281
+ Tarsier.routes do
282
+ # Namespace (affects path and controller lookup)
283
+ namespace :admin do
284
+ resources :users
285
+ end
286
+ # Routes: /admin/users -> Admin::UsersController
287
+
288
+ # Scope (affects path only)
289
+ scope path: 'api/v1' do
290
+ resources :users
291
+ end
292
+ # Routes: /api/v1/users -> UsersController
293
+ end
294
+ ```
295
+
296
+ ### Named Routes
297
+
298
+ ```ruby
299
+ Tarsier.routes do
300
+ get '/login', to: 'sessions#new', as: :login
301
+ get '/logout', to: 'sessions#destroy', as: :logout
302
+
303
+ root to: 'home#index'
304
+ end
305
+
306
+ # Generate paths
307
+ app.path_for(:login) # => "/login"
308
+ app.path_for(:user, id: 123) # => "/users/123"
309
+ ```
310
+
311
+ ---
312
+
313
+ ## Controllers
314
+
315
+ Controllers handle request processing with support for filters, parameter validation, and multiple response formats.
316
+
317
+ ### Basic Controller
318
+
319
+ ```ruby
320
+ class UsersController < Tarsier::Controller
321
+ def index
322
+ users = User.all
323
+ render json: users
324
+ end
325
+
326
+ def show
327
+ user = User.find(params[:id])
328
+ render json: user
329
+ end
330
+
331
+ def create
332
+ user = User.create(params.permit(:name, :email))
333
+ render json: user, status: 201
334
+ end
335
+ end
336
+ ```
337
+
338
+ ### Filters
339
+
340
+ ```ruby
341
+ class UsersController < Tarsier::Controller
342
+ before_action :authenticate
343
+ before_action :load_user, only: [:show, :update, :destroy]
344
+ after_action :log_request
345
+
346
+ def show
347
+ render json: @user
348
+ end
349
+
350
+ private
351
+
352
+ def authenticate
353
+ head 401 unless current_user
354
+ end
355
+
356
+ def load_user
357
+ @user = User.find(params[:id])
358
+ end
359
+
360
+ def log_request
361
+ Logger.info("Processed #{action_name}")
362
+ end
363
+ end
364
+ ```
365
+
366
+ ### Parameter Validation
367
+
368
+ ```ruby
369
+ class UsersController < Tarsier::Controller
370
+ params do
371
+ requires :name, type: String, min: 2, max: 100
372
+ requires :email, type: String, format: URI::MailTo::EMAIL_REGEXP
373
+ optional :role, type: String, in: %w[user admin], default: 'user'
374
+ optional :age, type: Integer, greater_than: 0
375
+ end
376
+
377
+ def create
378
+ # validated_params contains coerced and validated parameters
379
+ user = User.create(validated_params)
380
+ render json: user, status: 201
381
+ end
382
+ end
383
+ ```
384
+
385
+ ### Exception Handling
386
+
387
+ ```ruby
388
+ class ApplicationController < Tarsier::Controller
389
+ rescue_from ActiveRecord::RecordNotFound, with: :not_found
390
+ rescue_from ValidationError, with: :unprocessable
391
+
392
+ private
393
+
394
+ def not_found(exception)
395
+ render json: { error: 'Resource not found' }, status: 404
396
+ end
397
+
398
+ def unprocessable(exception)
399
+ render json: { errors: exception.errors }, status: 422
400
+ end
401
+ end
402
+ ```
403
+
404
+ ### Response Streaming
405
+
406
+ ```ruby
407
+ class ReportsController < Tarsier::Controller
408
+ def export
409
+ response.stream do |out|
410
+ User.find_each do |user|
411
+ out << user.to_csv
412
+ out << "\n"
413
+ end
414
+ end
415
+ end
416
+ end
417
+ ```
418
+
419
+ ---
420
+
421
+ ## Request and Response
422
+
423
+ ### Request Object
424
+
425
+ The request object provides immutable access to request data with lazy parsing for optimal performance.
426
+
427
+ ```ruby
428
+ # HTTP method and path
429
+ request.method # => :GET
430
+ request.path # => '/users/123'
431
+ request.url # => 'https://example.com/users/123'
432
+
433
+ # Parameters
434
+ request.params # Combined route, query, and body params
435
+ request.query_params # Query string parameters only
436
+ request.body_params # Parsed body parameters
437
+ request.route_params # Parameters from route matching
438
+
439
+ # Headers and metadata
440
+ request.headers # All HTTP headers
441
+ request.header('Authorization') # Specific header
442
+ request.content_type # Content-Type header
443
+ request.accept # Accept header
444
+ request.user_agent # User-Agent header
445
+
446
+ # Client information
447
+ request.ip # Client IP address
448
+ request.cookies # Parsed cookies
449
+
450
+ # Request type checks
451
+ request.get? # => true/false
452
+ request.post? # => true/false
453
+ request.json? # Content-Type includes application/json
454
+ request.xhr? # X-Requested-With: XMLHttpRequest
455
+ request.secure? # HTTPS request
456
+ ```
457
+
458
+ ### Response Object
459
+
460
+ ```ruby
461
+ # Set status and headers
462
+ response.status = 201
463
+ response.set_header('X-Custom-Header', 'value')
464
+ response.content_type = 'application/json'
465
+
466
+ # Response helpers
467
+ response.json({ data: users })
468
+ response.html('<h1>Hello</h1>')
469
+ response.text('Plain text response')
470
+ response.redirect('/new-location', status: 302)
471
+
472
+ # Cookies
473
+ response.set_cookie('session', token,
474
+ http_only: true,
475
+ secure: true,
476
+ same_site: 'Strict',
477
+ max_age: 86400
478
+ )
479
+ response.delete_cookie('session')
480
+
481
+ # Streaming
482
+ response.stream do |out|
483
+ out << 'chunk 1'
484
+ out << 'chunk 2'
485
+ end
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Middleware
491
+
492
+ Tarsier includes production-ready middleware and supports custom middleware development.
493
+
494
+ ### Built-in Middleware
495
+
496
+ ```ruby
497
+ app = Tarsier.application do
498
+ # Request logging
499
+ use Tarsier::Middleware::Logger
500
+
501
+ # CORS handling
502
+ use Tarsier::Middleware::CORS,
503
+ origins: ['https://example.com'],
504
+ methods: %w[GET POST PUT DELETE],
505
+ credentials: true
506
+
507
+ # CSRF protection
508
+ use Tarsier::Middleware::CSRF,
509
+ skip: ['/api/webhooks']
510
+
511
+ # Rate limiting
512
+ use Tarsier::Middleware::RateLimit,
513
+ limit: 100,
514
+ window: 60
515
+
516
+ # Response compression
517
+ use Tarsier::Middleware::Compression,
518
+ min_size: 1024
519
+
520
+ # Static file serving
521
+ use Tarsier::Middleware::Static,
522
+ root: 'public',
523
+ cache_control: 'public, max-age=31536000'
524
+ end
525
+ ```
526
+
527
+ ### Custom Middleware
528
+
529
+ ```ruby
530
+ class TimingMiddleware < Tarsier::Middleware::Base
531
+ def call(request, response)
532
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
533
+
534
+ @app.call(request, response)
535
+
536
+ duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
537
+ response.set_header('X-Response-Time', "#{(duration * 1000).round(2)}ms")
538
+
539
+ response
540
+ end
541
+ end
542
+ ```
543
+
544
+ ### Middleware Stack Management
545
+
546
+ ```ruby
547
+ app.middleware_stack.insert_before(Tarsier::Middleware::Logger, CustomMiddleware)
548
+ app.middleware_stack.insert_after(Tarsier::Middleware::CORS, AnotherMiddleware)
549
+ app.middleware_stack.delete(Tarsier::Middleware::Static)
550
+ app.middleware_stack.swap(OldMiddleware, NewMiddleware)
551
+ ```
552
+
553
+ ---
554
+
555
+ ## Database
556
+
557
+ Tarsier includes a lightweight database layer with support for SQLite, PostgreSQL, and MySQL.
558
+
559
+ ### Connecting to a Database
560
+
561
+ ```ruby
562
+ # SQLite
563
+ Tarsier.db :sqlite, 'db/app.db'
564
+ Tarsier.db :sqlite, ':memory:' # In-memory database
565
+
566
+ # PostgreSQL
567
+ Tarsier.db :postgres, 'postgres://localhost/myapp'
568
+ Tarsier.db :postgres, host: 'localhost', database: 'myapp', username: 'user'
569
+
570
+ # MySQL
571
+ Tarsier.db :mysql, host: 'localhost', database: 'myapp', username: 'root'
572
+ ```
573
+
574
+ ### Raw Queries
575
+
576
+ ```ruby
577
+ # Execute queries
578
+ Tarsier.database.execute('SELECT * FROM users WHERE active = ?', true)
579
+
580
+ # Get single result
581
+ user = Tarsier.database.get('SELECT * FROM users WHERE id = ?', 1)
582
+
583
+ # Insert and get ID
584
+ id = Tarsier.database.insert(:users, name: 'John', email: 'john@example.com')
585
+
586
+ # Update records
587
+ Tarsier.database.update(:users, { active: false }, id: 1)
588
+
589
+ # Delete records
590
+ Tarsier.database.delete(:users, id: 1)
591
+ ```
592
+
593
+ ### Transactions
594
+
595
+ ```ruby
596
+ Tarsier.database.transaction do
597
+ Tarsier.database.insert(:accounts, user_id: 1, balance: 100)
598
+ Tarsier.database.update(:users, { has_account: true }, id: 1)
599
+ end
600
+ # Automatically rolls back on error
601
+ ```
602
+
603
+ ### Migrations
604
+
605
+ Generate a migration:
606
+
607
+ ```bash
608
+ tarsier generate migration create_users name:string email:string
609
+ ```
610
+
611
+ Migration file:
612
+
613
+ ```ruby
614
+ class CreateUsers < Tarsier::Database::Migration
615
+ def up
616
+ create_table :users do |t|
617
+ t.string :name
618
+ t.string :email, null: false
619
+ t.boolean :active, default: true
620
+ t.timestamps
621
+ end
622
+
623
+ add_index :users, :email, unique: true
624
+ end
625
+
626
+ def down
627
+ drop_table :users
628
+ end
629
+ end
630
+ ```
631
+
632
+ Run migrations:
633
+
634
+ ```ruby
635
+ Tarsier.database.migrate # Run pending migrations
636
+ Tarsier.database.migrate(:down) # Rollback migrations
637
+ ```
638
+
639
+ ### Supported Column Types
640
+
641
+ | Type | SQL Type |
642
+ |------|----------|
643
+ | string | VARCHAR(255) |
644
+ | text | TEXT |
645
+ | integer | INTEGER |
646
+ | bigint | BIGINT |
647
+ | float | REAL |
648
+ | decimal | DECIMAL |
649
+ | boolean | BOOLEAN |
650
+ | date | DATE |
651
+ | datetime | DATETIME |
652
+ | timestamp | TIMESTAMP |
653
+ | time | TIME |
654
+ | binary | BLOB |
655
+ | json | JSON |
656
+
657
+ ---
658
+
659
+ ## WebSocket and SSE
660
+
661
+ ### WebSocket Support
662
+
663
+ ```ruby
664
+ class ChatSocket < Tarsier::WebSocket
665
+ on :connect do
666
+ subscribe "room:#{params[:room_id]}"
667
+ broadcast "room:#{params[:room_id]}", { type: 'join', user: current_user }
668
+ end
669
+
670
+ on :message do |data|
671
+ broadcast "room:#{params[:room_id]}", {
672
+ type: 'message',
673
+ user: current_user,
674
+ content: data
675
+ }
676
+ end
677
+
678
+ on :close do
679
+ broadcast "room:#{params[:room_id]}", { type: 'leave', user: current_user }
680
+ end
681
+ end
682
+ ```
683
+
684
+ ### Server-Sent Events
685
+
686
+ ```ruby
687
+ class EventsController < Tarsier::Controller
688
+ def stream
689
+ sse = Tarsier::SSE.new(request, response)
690
+ sse.start
691
+
692
+ loop do
693
+ sse.send({ time: Time.now.iso8601 }, event: 'tick', id: SecureRandom.uuid)
694
+ sleep 1
695
+ end
696
+ rescue IOError
697
+ sse.close
698
+ end
699
+ end
700
+ ```
701
+
702
+ ---
703
+
704
+ ## Models
705
+
706
+ Tarsier includes a lightweight ORM with Active Record-style patterns. Models integrate seamlessly with the database layer.
707
+
708
+ ### Model Definition
709
+
710
+ ```ruby
711
+ class User < Tarsier::Model
712
+ table :users
713
+
714
+ attribute :name, :string
715
+ attribute :email, :string
716
+ attribute :active, :boolean, default: true
717
+ attribute :created_at, :datetime
718
+
719
+ validates :name, presence: true, length: { minimum: 2, maximum: 100 }
720
+ validates :email, presence: true, format: /@/
721
+
722
+ has_many :posts
723
+ has_one :profile
724
+ belongs_to :organization
725
+ end
726
+ ```
727
+
728
+ ### CRUD Operations
729
+
730
+ ```ruby
731
+ # Create
732
+ user = User.create(name: 'John', email: 'john@example.com')
733
+ user = User.new(name: 'Jane')
734
+ user.save
735
+
736
+ # Read
737
+ User.find(1)
738
+ User.find!(1) # Raises RecordNotFoundError if not found
739
+ User.find_by(email: 'john@example.com')
740
+ User.all
741
+
742
+ # Update
743
+ user.update(name: 'John Doe')
744
+ user.name = 'Johnny'
745
+ user.save
746
+
747
+ # Delete
748
+ user.destroy
749
+ ```
750
+
751
+ ### Query Interface
752
+
753
+ ```ruby
754
+ # Chainable query methods
755
+ User.where(active: true)
756
+ .where_not(role: 'guest')
757
+ .order(created_at: :desc)
758
+ .limit(10)
759
+ .offset(20)
760
+
761
+ # Range queries
762
+ User.where(age: 18..65)
763
+
764
+ # IN queries
765
+ User.where(status: ['active', 'pending'])
766
+
767
+ # Select specific columns
768
+ User.select(:id, :name, :email)
769
+
770
+ # Pluck values
771
+ User.where(active: true).pluck(:email)
772
+ User.pluck(:id) # Same as User.ids
773
+
774
+ # Joins
775
+ User.joins(:posts, on: 'users.id = posts.user_id')
776
+
777
+ # Eager loading
778
+ User.includes(:posts, :profile)
779
+
780
+ # Aggregations
781
+ User.count
782
+ User.where(active: true).count
783
+ User.exists?(email: 'test@example.com')
784
+
785
+ # Batch operations
786
+ User.where(inactive: true).update_all(archived: true)
787
+ User.where(archived: true).delete_all
788
+
789
+ # Find or create
790
+ User.where(email: 'new@example.com').find_or_create_by({ email: 'new@example.com' })
791
+ User.where(email: 'new@example.com').find_or_initialize_by({ email: 'new@example.com' })
792
+ ```
793
+
794
+ ### Validations
795
+
796
+ ```ruby
797
+ class Post < Tarsier::Model
798
+ attribute :title, :string
799
+ attribute :body, :text
800
+ attribute :status, :string
801
+
802
+ validates :title, presence: true, length: { minimum: 5, maximum: 200 }
803
+ validates :status, inclusion: { in: %w[draft published archived] }
804
+ end
805
+
806
+ post = Post.new(title: 'Hi')
807
+ post.valid? # => false
808
+ post.errors # => { title: ["is too short (minimum 5)"] }
809
+ ```
810
+
811
+ ### Associations
812
+
813
+ ```ruby
814
+ class Post < Tarsier::Model
815
+ belongs_to :user
816
+ has_many :comments
817
+ end
818
+
819
+ class Comment < Tarsier::Model
820
+ belongs_to :post
821
+ belongs_to :user
822
+ end
823
+
824
+ # Access associations
825
+ post = Post.find(1)
826
+ post.user # => User instance
827
+ post.comments # => Array of Comment instances
828
+ ```
829
+
830
+ ---
831
+
832
+ ## Configuration
833
+
834
+ ```ruby
835
+ Tarsier.application do
836
+ configure do
837
+ # Security
838
+ self.secret_key = ENV.fetch('SECRET_KEY')
839
+ self.force_ssl = true
840
+
841
+ # Server
842
+ self.host = '0.0.0.0'
843
+ self.port = 3000
844
+
845
+ # Application
846
+ self.log_level = :info
847
+ self.default_format = :json
848
+ self.static_files = true
849
+ end
850
+ end
851
+ ```
852
+
853
+ ### Environment-Specific Configuration
854
+
855
+ ```ruby
856
+ Tarsier.application do
857
+ configure do
858
+ case Tarsier.env
859
+ when 'production'
860
+ self.force_ssl = true
861
+ self.log_level = :warn
862
+ when 'development'
863
+ self.log_level = :debug
864
+ when 'test'
865
+ self.log_level = :error
866
+ end
867
+ end
868
+ end
869
+ ```
870
+
871
+ ---
872
+
873
+ ## Testing
874
+
875
+ Tarsier applications are designed for testability with RSpec integration.
876
+
877
+ ### Setup
878
+
879
+ ```ruby
880
+ # spec/spec_helper.rb
881
+ require 'tarsier'
882
+ require 'rack/test'
883
+
884
+ RSpec.configure do |config|
885
+ config.include Rack::Test::Methods
886
+
887
+ def app
888
+ Tarsier.app
889
+ end
890
+ end
891
+ ```
892
+
893
+ ### Controller Tests
894
+
895
+ ```ruby
896
+ RSpec.describe UsersController do
897
+ describe 'GET /users' do
898
+ it 'returns users list' do
899
+ get '/users'
900
+
901
+ expect(last_response.status).to eq(200)
902
+ expect(JSON.parse(last_response.body)).to have_key('users')
903
+ end
904
+ end
905
+
906
+ describe 'POST /users' do
907
+ it 'creates a user' do
908
+ post '/users', { name: 'John', email: 'john@example.com' }.to_json,
909
+ 'CONTENT_TYPE' => 'application/json'
910
+
911
+ expect(last_response.status).to eq(201)
912
+ end
913
+ end
914
+ end
915
+ ```
916
+
917
+ ### Running Tests
918
+
919
+ ```bash
920
+ bundle exec rspec
921
+ bundle exec rspec spec/controllers
922
+ bundle exec rspec --format documentation
923
+ ```
924
+
925
+ ---
926
+
927
+ ## Benchmarks
928
+
929
+ Run the included benchmark suite:
930
+
931
+ ```bash
932
+ bundle exec rake benchmark
933
+ bundle exec rake benchmark:router
934
+ bundle exec rake benchmark:request
935
+ ```
936
+
937
+ ### Performance Characteristics
938
+
939
+ | Operation | Throughput |
940
+ |-----------|------------|
941
+ | Static route matching | 700,000+ req/sec |
942
+ | Parameterized routes | 250,000+ req/sec |
943
+ | Nested resources | 115,000+ req/sec |
944
+ | Request parsing | 700,000+ req/sec |
945
+
946
+ Benchmarks performed on Ruby 3.3 with YJIT enabled.
947
+
948
+ ---
949
+
950
+ ## Contributing
951
+
952
+ 1. Fork the repository
953
+ 2. Create a feature branch (`git checkout -b feature/improvement`)
954
+ 3. Commit your changes (`git commit -am 'Add new feature'`)
955
+ 4. Push to the branch (`git push origin feature/improvement`)
956
+ 5. Create a Pull Request
957
+
958
+ ### Development Setup
959
+
960
+ ```bash
961
+ git clone https://github.com/tarsier-rb/tarsier.git
962
+ cd tarsier
963
+ bundle install
964
+ bundle exec rspec
965
+ ```
966
+
967
+ ### Code Quality
968
+
969
+ ```bash
970
+ bundle exec rubocop
971
+ bundle exec yard doc
972
+ ```
973
+
974
+ ---
975
+
976
+ ## License
977
+
978
+ Tarsier is released under the [MIT License](LICENSE.txt).
979
+
980
+ ---
981
+
982
+ ## Acknowledgments
983
+
984
+ Tarsier draws inspiration from the Ruby web framework ecosystem, particularly Rails, Sinatra, and Roda, while pursuing its own vision of performance and simplicity.