rasti-web 2.1.0 → 2.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 250a8b04a93b51665f10b49206453b490ffc83c81b8684ca466d4259ea23e75a
4
- data.tar.gz: 9c32ed8ab0506cb4a4b407bb50fa6f12dc19bafc457e05181f18ba16ad9c5032
3
+ metadata.gz: 1541343e4a1c27d8f1679115b89a127f4821f586d33d42e6b2f8233b888f31c5
4
+ data.tar.gz: 212b77d8585d2838f6c04c88398630ea8262285d951e3d491e7761d09242bc90
5
5
  SHA512:
6
- metadata.gz: f30ddbfed0f369d8eb9bcabe5e43b2a9891b9a53d3be55bbe3b4d492cf1724984e25b6a1ca91285879bedc14b8ec691525a345bf62dff48cc72f2d47eab80a8f
7
- data.tar.gz: 120e466b7433332064adcc6ebfaf8cfc337433221954fddeb867a91cc801e53e8856a1b00a331c6867a1845d3c248f9febe1ac24a4e1864d3cd08ec511d3326d
6
+ metadata.gz: 144031ae6422daf22ced72d427f131f234ff5fa4ee7fbfa61d09082ec2bab7ded8bc06911a33046e19b4277acad99753e63a3a18b666785f3c6f4d73b4e02925
7
+ data.tar.gz: 84f51ba02b4f959a0cad86d11986ee55bc977650fb3641df87a5d86b14aebbc36a63def74a801867af89eb604ac4e49f32a671f671a85b0b3f140cd9324c08ef
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ '**' ]
6
+ pull_request:
7
+ branches: [ '**' ]
8
+
9
+ jobs:
10
+ test:
11
+
12
+ name: Tests
13
+ runs-on: ubuntu-20.04
14
+ strategy:
15
+ matrix:
16
+ ruby-version: ['2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2', 'jruby-9.2.9.0']
17
+
18
+ steps:
19
+ - uses: actions/checkout@v3
20
+ - name: Set up Ruby
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: ${{ matrix.ruby-version }}
24
+ bundler-cache: true
25
+ - name: Run tests
26
+ run: bundle exec rake
data/README.md CHANGED
@@ -1,11 +1,26 @@
1
1
  # Rasti::Web
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rasti-web.svg)](https://rubygems.org/gems/rasti-web)
4
- [![Build Status](https://travis-ci.org/gabynaiman/rasti-web.svg?branch=master)](https://travis-ci.org/gabynaiman/rasti-web)
5
- [![Coverage Status](https://coveralls.io/repos/gabynaiman/rasti-web/badge.svg?branch=master)](https://coveralls.io/r/gabynaiman/rasti-web?branch=master)
6
- [![Code Climate](https://codeclimate.com/github/gabynaiman/rasti-web.svg)](https://codeclimate.com/github/gabynaiman/rasti-web)
4
+ [![CI](https://github.com/gabynaiman/rasti-web/actions/workflows/ci.yml/badge.svg)](https://github.com/gabynaiman/rasti-web/actions/workflows/ci.yml)
5
+ [![Coverage Status](https://coveralls.io/repos/github/gabynaiman/rasti-web/badge.svg?branch=master)](https://coveralls.io/github/gabynaiman/rasti-web?branch=master)
7
6
 
8
- Web blocks to build robust applications
7
+ **Rasti::Web** is a lightweight, modular web framework built on top of [Rack](https://github.com/rack/rack). It provides essential building blocks for creating robust web applications without imposing too much structure.
8
+
9
+ ## Key Features
10
+
11
+ - **Simplicity**: Minimalist and easy-to-understand API
12
+ - **Flexibility**: Direct access to Rack primitives for full control over the request/response cycle
13
+ - **Powerful rendering**: Support for multiple formats (HTML, JSON, JavaScript, CSS, files, etc.)
14
+ - **Advanced routing**: Static, parameterized, optional, and wildcard routes
15
+ - **Template system**: ERB integration for views, layouts, and partials
16
+ - **Error handling**: Hooks system and rescue_from for exception management
17
+
18
+ ## Use Cases
19
+
20
+ - Building REST APIs
21
+ - Full web applications
22
+ - Microservices
23
+ - Applications requiring fine-grained control over the request/response cycle
9
24
 
10
25
  ## Installation
11
26
 
@@ -23,36 +38,206 @@ Or install it yourself as:
23
38
 
24
39
  ## Usage
25
40
 
41
+ ### Application
42
+
43
+ #### Basic definition
44
+
45
+ ```ruby
46
+ class WebApp < Rasti::Web::Application
47
+
48
+ get '/' do |request, response, render|
49
+ render.html 'Welcome'
50
+ end
51
+
52
+ end
53
+ ```
54
+
55
+ #### Middleware
56
+
57
+ ```ruby
58
+ class WebApp < Rasti::Web::Application
59
+
60
+ use Rack::Session::Cookie, secret: 'my_secret'
61
+ use SomeCustomMiddleware
62
+
63
+ get '/private' do |request, response, render|
64
+ render.html 'Private content'
65
+ end
66
+
67
+ end
68
+ ```
69
+
70
+ #### Mounting sub-applications
71
+
72
+ ```ruby
73
+ class ApiApp < Rasti::Web::Application
74
+ get '/resource/:id' do |request, response, render|
75
+ render.json id: request.params['id'].to_i
76
+ end
77
+ end
78
+
79
+ class WebApp < Rasti::Web::Application
80
+ map '/api', ApiApp
81
+
82
+ get '/' do |request, response, render|
83
+ render.html 'Home'
84
+ end
85
+ end
86
+ ```
87
+
88
+ #### Custom not_found handler
89
+
90
+ ```ruby
91
+ class WebApp < Rasti::Web::Application
92
+
93
+ get '/' do |request, response, render|
94
+ render.html 'Home'
95
+ end
96
+
97
+ not_found do |request, response, render|
98
+ render.status 404, 'Page not found'
99
+ end
100
+
101
+ end
102
+ ```
103
+
104
+ #### Listing routes
105
+
106
+ ```ruby
107
+ WebApp.all_routes
108
+ # => {'GET' => ['/'], 'POST' => ['/users']}
109
+ ```
110
+
26
111
  ### Routing
27
112
 
113
+ #### HTTP Verbs
114
+
115
+ Rasti::Web supports all standard HTTP verbs:
116
+
28
117
  ```ruby
29
- # app.rb
30
118
  class WebApp < Rasti::Web::Application
31
119
 
32
- use SomeMiddleware
120
+ get '/resources' do |request, response, render|
121
+ render.json resources: Resource.all
122
+ end
33
123
 
34
- get '/hello' do |request, response, render|
35
- render.text 'world'
124
+ post '/resources' do |request, response, render|
125
+ resource = Resource.create(request.params)
126
+ render.json resource, 201
36
127
  end
37
128
 
38
- put '/users/:id', UsersController >> :update
129
+ put '/resources/:id' do |request, response, render|
130
+ resource = Resource.update(request.params[:id], request.params)
131
+ render.json resource
132
+ end
133
+
134
+ patch '/resources/:id' do |request, response, render|
135
+ resource = Resource.patch(request.params[:id], request.params)
136
+ render.json resource
137
+ end
138
+
139
+ delete '/resources/:id' do |request, response, render|
140
+ Resource.delete(request.params[:id])
141
+ render.status 204
142
+ end
143
+
144
+ head '/resources/:id' do |request, response, render|
145
+ render.status Resource.exists?(request.params[:id]) ? 200 : 404
146
+ end
147
+
148
+ options '/resources' do |request, response, render|
149
+ render.status 200, 'Allow' => 'GET, POST, OPTIONS'
150
+ end
39
151
 
40
152
  end
153
+ ```
41
154
 
42
- # configu.ru
43
- require_relative 'app'
44
- run WebApp
155
+ #### Static routes
156
+
157
+ ```ruby
158
+ get '/about' do |request, response, render|
159
+ render.html 'About page'
160
+ end
161
+ ```
162
+
163
+ #### Parameterized routes
164
+
165
+ ```ruby
166
+ get '/users/:id' do |request, response, render|
167
+ user = User.find(request.params[:id])
168
+ render.json user
169
+ end
170
+
171
+ get '/posts/:post_id/comments/:id' do |request, response, render|
172
+ comment = Comment.find(request.params[:id], post_id: request.params[:post_id])
173
+ render.json comment
174
+ end
175
+ ```
176
+
177
+ #### Optional parameters
178
+
179
+ ```ruby
180
+ # Matches: /resource, /resource/123, /resource/123/edit
181
+ get '/:resource(/:id(/:action))' do |request, response, render|
182
+ render.json(
183
+ resource: request.params[:resource],
184
+ id: request.params[:id],
185
+ action: request.params[:action]
186
+ )
187
+ end
188
+ ```
189
+
190
+ #### Wildcard routes
191
+
192
+ ```ruby
193
+ # Wildcard at the beginning: /*/files/download
194
+ get '/*/files/download' do |request, response, render|
195
+ path = request.params[:wildcard] # "users/documents"
196
+ render.file File.join(path, 'file.zip')
197
+ end
198
+
199
+ # Wildcard in the middle: /files/*/download
200
+ get '/files/*/download' do |request, response, render|
201
+ path = request.params[:wildcard]
202
+ render.file File.join('files', path, 'download.zip')
203
+ end
204
+
205
+ # Wildcard at the end: /files/*
206
+ get '/files/*' do |request, response, render|
207
+ path = request.params[:wildcard]
208
+ render.file File.join('files', path)
209
+ end
210
+
211
+ # Wildcard with parameters: /files/*/download/:id
212
+ get '/files/*/download/:id' do |request, response, render|
213
+ render.json(
214
+ path: request.params[:wildcard],
215
+ id: request.params[:id]
216
+ )
217
+ end
45
218
  ```
46
219
 
47
220
  ### Controllers
48
221
 
222
+ #### Basic controller
223
+
49
224
  ```ruby
50
225
  class UsersController < Rasti::Web::Controller
51
226
 
227
+ def index
228
+ users = User.all
229
+ render.view 'users/list', users: users
230
+ end
231
+
232
+ def show
233
+ user = User.find(params[:id])
234
+ render.view 'users/show', user: user
235
+ end
236
+
52
237
  def update
53
238
  user = User.find(params[:id])
54
239
  if user.update_attributes(params[:user])
55
- render.view 'users/list', users: User.all
240
+ render.view 'users/show', user: user
56
241
  else
57
242
  render.view 'users/edit', user: user
58
243
  end
@@ -61,36 +246,505 @@ class UsersController < Rasti::Web::Controller
61
246
  end
62
247
  ```
63
248
 
64
- ### Hooks
249
+ #### Using controllers in routes
250
+
251
+ ```ruby
252
+ class WebApp < Rasti::Web::Application
253
+
254
+ get '/users', UsersController >> :index
255
+ get '/users/:id', UsersController >> :show
256
+ put '/users/:id', UsersController >> :update
257
+
258
+ end
259
+ ```
260
+
261
+ #### Hooks
262
+
263
+ ##### Before hooks
65
264
 
66
265
  ```ruby
67
266
  class UsersController < Rasti::Web::Controller
68
267
 
268
+ # Runs before all actions
69
269
  before_action do |action_name|
270
+ authenticate_user!
271
+ end
272
+
273
+ # Runs before a specific action
274
+ before_action :update do
275
+ verify_permissions!
70
276
  end
71
277
 
72
- before_action :action_name do
278
+ def update
279
+ # action code
73
280
  end
74
281
 
282
+ end
283
+ ```
284
+
285
+ ##### After hooks
286
+
287
+ ```ruby
288
+ class UsersController < Rasti::Web::Controller
289
+
290
+ # Runs after all actions
75
291
  after_action do |action_name|
292
+ log_action(action_name)
76
293
  end
77
294
 
78
- after_action :action_name do
295
+ # Runs after a specific action
296
+ after_action :create do
297
+ send_notification
298
+ end
299
+
300
+ def create
301
+ # action code
79
302
  end
80
303
 
81
304
  end
82
305
  ```
83
306
 
84
- ### Error handling
307
+ #### Error handling
85
308
 
86
309
  ```ruby
87
310
  class UsersController < Rasti::Web::Controller
88
311
 
89
- rescue_from StandardError do |ex|
90
- render.status 500, 'Unexpected error'
312
+ # Catch a specific exception
313
+ rescue_from UserNotFound do |ex|
314
+ render.status 404, ex.message
315
+ end
316
+
317
+ # Catch by class hierarchy (catches IOError and its subclasses like EOFError)
318
+ rescue_from IOError do |ex|
319
+ render.status 500, ex.message
320
+ end
321
+
322
+ # Multiple rescue_from
323
+ rescue_from ValidationError do |ex|
324
+ render.json({errors: ex.errors}, 422)
91
325
  end
92
326
 
327
+ rescue_from AuthenticationError do |ex|
328
+ render.status 401, 'Unauthorized'
329
+ end
330
+
331
+ def show
332
+ user = User.find(params[:id]) # may raise UserNotFound
333
+ render.json user
334
+ end
335
+
336
+ end
337
+ ```
338
+
339
+ ### Endpoints
340
+
341
+ Endpoints can be blocks or controller actions:
342
+
343
+ ```ruby
344
+ # Endpoint as a block
345
+ endpoint = Rasti::Web::Endpoint.new do |request, response, render|
346
+ render.text 'Hello world'
93
347
  end
348
+
349
+ # Endpoint from a controller
350
+ endpoint = UsersController.action :index
351
+
352
+ # Endpoints are Rack-compatible
353
+ status, headers, response = endpoint.call(env)
354
+ ```
355
+
356
+ ### Request
357
+
358
+ #### Accessing parameters
359
+
360
+ ```ruby
361
+ get '/search' do |request, response, render|
362
+ # Route parameters
363
+ user_id = request.params[:user_id]
364
+
365
+ # Query string parameters (?q=ruby&page=1)
366
+ query = request.params['q']
367
+ page = request.params[:page]
368
+
369
+ # Parameters are accessible with strings or symbols
370
+ render.json query: query, page: page
371
+ end
372
+ ```
373
+
374
+ #### Form parameters
375
+
376
+ ```ruby
377
+ post '/users' do |request, response, render|
378
+ # Form parameters (POST/PUT)
379
+ name = request.params[:name]
380
+ email = request.params['email']
381
+
382
+ user = User.create(name: name, email: email)
383
+ render.json user
384
+ end
385
+ ```
386
+
387
+ #### JSON body parameters
388
+
389
+ ```ruby
390
+ post '/api/users' do |request, response, render|
391
+ # Content-Type: application/json automatically parsed
392
+ if request.json?
393
+ user = User.create(request.params)
394
+ render.json user, 201
395
+ else
396
+ render.status 400, 'Expected JSON content type'
397
+ end
398
+ end
399
+ ```
400
+
401
+ ### Rendering
402
+
403
+ The `render` object provides multiple methods for different response formats:
404
+
405
+ #### Status codes
406
+
407
+ ```ruby
408
+ # Status code only
409
+ render.status 404
410
+
411
+ # Status code and body
412
+ render.status 500, 'Internal server error'
413
+
414
+ # Status code and headers
415
+ render.status 201, 'Content-Type' => 'application/json'
416
+
417
+ # Status code, body and headers
418
+ render.status 403, 'Forbidden', 'Content-Type' => 'text/html'
419
+ ```
420
+
421
+ #### Text
422
+
423
+ ```ruby
424
+ # Plain text
425
+ render.text 'Hello world'
426
+
427
+ # With status code
428
+ render.text 'Not found', 404
429
+
430
+ # With headers
431
+ render.text 'Encoded text', 'Content-Encoding' => 'gzip'
432
+
433
+ # With status code and headers
434
+ render.text 'Error', 500, 'Content-Encoding' => 'gzip'
435
+ ```
436
+
437
+ #### HTML
438
+
439
+ ```ruby
440
+ # Basic HTML
441
+ render.html '<h1>Welcome</h1>'
442
+
443
+ # With status code
444
+ render.html '<h1>Error</h1>', 500
445
+
446
+ # With headers
447
+ render.html '<p>Content</p>', 'Content-Encoding' => 'gzip'
448
+
449
+ # With status code and headers
450
+ render.html '<h1>Not found</h1>', 404, 'Custom-Header' => 'value'
451
+ ```
452
+
453
+ #### JSON
454
+
455
+ ```ruby
456
+ # Object serialized to JSON
457
+ render.json id: 123, name: 'John'
458
+
459
+ # Direct JSON string
460
+ render.json '{"x":1,"y":2}'
461
+
462
+ # With status code
463
+ render.json {error: 'Invalid'}, 422
464
+
465
+ # With headers
466
+ render.json {data: []}, 'Content-Encoding' => 'gzip'
467
+
468
+ # With status code and headers
469
+ render.json {error: 'Invalid'}, 422, 'X-Error-Code' => 'VAL001'
470
+ ```
471
+
472
+ #### JavaScript
473
+
474
+ ```ruby
475
+ # JavaScript code
476
+ render.js 'alert("hello");'
477
+
478
+ # With status code
479
+ render.js 'console.log("loaded");', 206
480
+
481
+ # With headers
482
+ render.js 'alert("hello");', 'Content-Encoding' => 'gzip'
483
+
484
+ # With status code and headers
485
+ render.js 'alert("hello");', 206, 'Cache-Control' => 'no-cache'
486
+ ```
487
+
488
+ #### CSS
489
+
490
+ ```ruby
491
+ # CSS code
492
+ render.css 'body{margin:0}'
493
+
494
+ # With status code
495
+ render.css 'body{margin:0}', 206
496
+
497
+ # With headers
498
+ render.css 'body{margin:0}', 'Content-Encoding' => 'gzip'
499
+
500
+ # With status code and headers
501
+ render.css 'body{margin:0}', 206, 'Cache-Control' => 'public'
502
+ ```
503
+
504
+ #### File downloads
505
+
506
+ ```ruby
507
+ # File download
508
+ render.file '/path/to/file.zip'
509
+ # Content-Type and Content-Disposition are set automatically
510
+
511
+ # With status code
512
+ render.file '/path/to/file.pdf', 206
513
+
514
+ # With custom headers
515
+ render.file '/path/to/file.zip', 'Content-Disposition' => 'attachment; filename=custom.zip'
516
+
517
+ # With status code and headers
518
+ render.file '/path/to/file.zip', 206, 'Content-Disposition' => 'attachment; filename=custom.zip'
519
+ ```
520
+
521
+ #### Data with headers
522
+
523
+ ```ruby
524
+ # Data without Content-Type
525
+ render.data 'Raw content'
526
+
527
+ # With status code
528
+ render.data 'Content', 206
529
+
530
+ # With headers (useful with Rasti::Web::Headers.for_file)
531
+ render.data file_content, Rasti::Web::Headers.for_file('document.txt')
532
+
533
+ # With status code and headers
534
+ render.data content, 206, Rasti::Web::Headers.for_file('file.txt')
535
+ ```
536
+
537
+ ### Templates
538
+
539
+ Rasti::Web uses ERB for templates. By default, it looks for templates in `views/`.
540
+
541
+ #### Configuring views directory
542
+
543
+ ```ruby
544
+ # Configure template directory (default: 'views')
545
+ Rasti::Web::Template.template_path = '/path/to/templates'
546
+ ```
547
+
548
+ #### Partials
549
+
550
+ Partials are templates without layout:
551
+
552
+ ```ruby
553
+ # views/users/_user_info.erb
554
+ <h1><%= title %></h1>
555
+ <div><%= text %></div>
556
+
557
+ # In your endpoint/controller:
558
+ render.partial 'users/user_info', title: 'Welcome', text: 'Hello world'
559
+ # => <h1>Welcome</h1><div>Hello world</div>
560
+ ```
561
+
562
+ #### Layouts
563
+
564
+ ```ruby
565
+ # views/layout.erb
566
+ <html><body><%= yield %></body></html>
567
+
568
+ # Layout with content
569
+ render.layout { 'Page content' }
570
+ # => <html><body>Page content</body></html>
571
+
572
+ # Empty layout
573
+ render.layout
574
+ # => <html><body></body></html>
575
+ ```
576
+
577
+ #### Custom layouts
578
+
579
+ ```ruby
580
+ # views/custom_layout.erb
581
+ <html><body class="custom"><%= yield %></body></html>
582
+
583
+ # Use custom layout
584
+ render.layout('custom_layout') { 'Page content' }
585
+ # => <html><body class="custom">Page content</body></html>
586
+ ```
587
+
588
+ #### Views
589
+
590
+ Views combine a template with a layout:
591
+
592
+ ```ruby
593
+ # views/users/profile.erb
594
+ <h1><%= title %></h1>
595
+ <div><%= text %></div>
596
+
597
+ # views/layout.erb
598
+ <html><body><%= yield %></body></html>
599
+
600
+ # With default layout (layout.erb)
601
+ render.view 'users/profile', title: 'Welcome', text: 'Hello world'
602
+ # => <html><body><h1>Welcome</h1><div>Hello world</div></body></html>
603
+
604
+ # With custom layout
605
+ render.view 'users/profile', {title: 'Welcome', text: 'Hello'}, 'custom_layout'
606
+ ```
607
+
608
+ #### Context methods and local variables
609
+
610
+ ```ruby
611
+ # Module with context methods
612
+ module ViewHelpers
613
+ def format_date(date)
614
+ date.strftime('%Y-%m-%d')
615
+ end
616
+ end
617
+
618
+ # views/posts/show.erb using context method
619
+ <h1><%= format_date(post.created_at) %></h1>
620
+
621
+ # Render with context
622
+ class PostContext
623
+ include ViewHelpers
624
+ end
625
+
626
+ render.partial 'posts/show', PostContext.new, post: post
627
+
628
+ # Or use local variables directly
629
+ render.partial 'posts/show', title: 'My Post', text: 'Content'
630
+ ```
631
+
632
+ ## Advanced Usage
633
+
634
+ ### Complete example
635
+
636
+ ```ruby
637
+ # app.rb
638
+ class AuthController < Rasti::Web::Controller
639
+
640
+ def login
641
+ user = User.authenticate(params[:email], params[:password])
642
+ if user
643
+ session[:user_id] = user.id
644
+ render.json user
645
+ else
646
+ render.status 401, 'Invalid credentials'
647
+ end
648
+ rescue AuthenticationError => ex
649
+ render.status 401, ex.message
650
+ end
651
+
652
+ end
653
+
654
+ class UsersController < Rasti::Web::Controller
655
+
656
+ before_action do |action_name|
657
+ authenticate_user!
658
+ end
659
+
660
+ rescue_from UserNotFound do |ex|
661
+ render.status 404, ex.message
662
+ end
663
+
664
+ def index
665
+ users = User.all
666
+ render.view 'users/index', users: users
667
+ end
668
+
669
+ def show
670
+ user = User.find(params[:id])
671
+ render.view 'users/show', user: user
672
+ end
673
+
674
+ private
675
+
676
+ def authenticate_user!
677
+ unless session[:user_id]
678
+ render.status 401, 'Unauthorized'
679
+ end
680
+ end
681
+
682
+ end
683
+
684
+ class WebApp < Rasti::Web::Application
685
+
686
+ use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
687
+
688
+ # Public routes
689
+ post '/login', AuthController >> :login
690
+
691
+ # Protected routes
692
+ get '/users', UsersController >> :index
693
+ get '/users/:id', UsersController >> :show
694
+
695
+ # API mount
696
+ map '/api', ApiApp
697
+
698
+ # Custom not found
699
+ not_found do |request, response, render|
700
+ render.view '404', path: request.path_info
701
+ end
702
+
703
+ end
704
+
705
+ # config.ru
706
+ require_relative 'app'
707
+ run WebApp
708
+ ```
709
+
710
+ ## Running Tests
711
+
712
+ Run all tests:
713
+
714
+ ```bash
715
+ rake spec
716
+ # or simply
717
+ rake
718
+ ```
719
+
720
+ Run tests from a specific directory:
721
+
722
+ ```bash
723
+ DIR=spec/web rake spec
724
+ ```
725
+
726
+ Run a specific test file:
727
+
728
+ ```bash
729
+ TEST=spec/endpoint_spec.rb rake spec
730
+ ```
731
+
732
+ Run tests from a specific line in a file:
733
+
734
+ ```bash
735
+ TEST=spec/endpoint_spec.rb:45 rake spec
736
+ ```
737
+
738
+ Run tests matching a name pattern:
739
+
740
+ ```bash
741
+ NAME=render rake spec
742
+ ```
743
+
744
+ You can combine options:
745
+
746
+ ```bash
747
+ DIR=spec/web NAME=controller rake spec
94
748
  ```
95
749
 
96
750
  ## Contributing
@@ -7,7 +7,7 @@ module Rasti
7
7
  hash.update self.GET
8
8
  hash.update self.POST
9
9
  hash.update env[ROUTE_PARAMS] if env.key? ROUTE_PARAMS
10
- hash.update JSON.parse(body_text) if json? && body_text
10
+ hash.update JSON.parse(body_text) if json? && body_text && !body_text.empty?
11
11
  end
12
12
  end
13
13
 
@@ -4,7 +4,7 @@ module Rasti
4
4
 
5
5
  def self.render(template, context=nil, locals={}, &block)
6
6
  files = Web.template_engines.map { |e| File.join Web.views_path, "#{template}.#{e}" }
7
- template_file = files.detect { |f| File.exists? f }
7
+ template_file = files.detect { |f| File.exist? f }
8
8
 
9
9
  raise "Missing template #{template} [#{files.join(', ')}]" unless template_file
10
10
 
@@ -1,5 +1,5 @@
1
1
  module Rasti
2
2
  module Web
3
- VERSION = '2.1.0'
3
+ VERSION = '2.1.2'
4
4
  end
5
5
  end
data/rasti-web.gemspec CHANGED
@@ -26,12 +26,12 @@ Gem::Specification.new do |spec|
26
26
  spec.add_dependency 'content-type', '~> 0.0'
27
27
  spec.add_dependency 'class_ancestry_sort', '~> 0.1'
28
28
 
29
- spec.add_development_dependency 'rake', '~> 11.0'
29
+ spec.add_development_dependency 'rake', '~> 13.0'
30
30
  spec.add_development_dependency 'minitest', '~> 5.0', '< 5.11'
31
31
  spec.add_development_dependency 'minitest-colorin', '~> 0.1'
32
32
  spec.add_development_dependency 'minitest-line', '~> 0.6'
33
33
  spec.add_development_dependency 'simplecov', '~> 0.12'
34
34
  spec.add_development_dependency 'coveralls', '~> 0.8'
35
- spec.add_development_dependency 'pry-nav', '~> 0.2'
35
+ spec.add_development_dependency 'pry-nav', '~> 1.0'
36
36
  spec.add_development_dependency 'rack-test', '~> 0.6'
37
37
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rasti-web
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriel Naiman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-08 00:00:00.000000000 Z
11
+ date: 2026-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -114,14 +114,14 @@ dependencies:
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: '11.0'
117
+ version: '13.0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: '11.0'
124
+ version: '13.0'
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: minitest
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -204,14 +204,14 @@ dependencies:
204
204
  requirements:
205
205
  - - "~>"
206
206
  - !ruby/object:Gem::Version
207
- version: '0.2'
207
+ version: '1.0'
208
208
  type: :development
209
209
  prerelease: false
210
210
  version_requirements: !ruby/object:Gem::Requirement
211
211
  requirements:
212
212
  - - "~>"
213
213
  - !ruby/object:Gem::Version
214
- version: '0.2'
214
+ version: '1.0'
215
215
  - !ruby/object:Gem::Dependency
216
216
  name: rack-test
217
217
  requirement: !ruby/object:Gem::Requirement
@@ -234,10 +234,10 @@ extensions: []
234
234
  extra_rdoc_files: []
235
235
  files:
236
236
  - ".coveralls.yml"
237
+ - ".github/workflows/ci.yml"
237
238
  - ".gitignore"
238
239
  - ".ruby-gemset"
239
240
  - ".ruby-version"
240
- - ".travis.yml"
241
241
  - Gemfile
242
242
  - LICENSE.txt
243
243
  - README.md
@@ -277,7 +277,7 @@ homepage: https://github.com/gabynaiman/rasti-web
277
277
  licenses:
278
278
  - MIT
279
279
  metadata: {}
280
- post_install_message:
280
+ post_install_message:
281
281
  rdoc_options: []
282
282
  require_paths:
283
283
  - lib
@@ -292,8 +292,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
292
292
  - !ruby/object:Gem::Version
293
293
  version: '0'
294
294
  requirements: []
295
- rubygems_version: 3.0.6
296
- signing_key:
295
+ rubygems_version: 3.0.9
296
+ signing_key:
297
297
  specification_version: 4
298
298
  summary: Web blocks to build robust applications
299
299
  test_files:
data/.travis.yml DELETED
@@ -1,20 +0,0 @@
1
- language: ruby
2
-
3
- rvm:
4
- - 2.0
5
- - 2.1
6
- - 2.2
7
- - 2.3
8
- - 2.4
9
- - 2.5
10
- - 2.6
11
- - 2.7
12
- - jruby-9.2.9.0
13
- - ruby-head
14
- - jruby-head
15
-
16
- matrix:
17
- fast_finish: true
18
- allow_failures:
19
- - rvm: ruby-head
20
- - rvm: jruby-head