Skip to content

Commit

Permalink
Merge pull request #43 from aristotelesbr/development
Browse files Browse the repository at this point in the history
refactor: remove ApplicationBase classs and improve performance on response
  • Loading branch information
aristotelesbr authored Apr 3, 2024
2 parents 7c8fd28 + 97db38e commit 9a9eea8
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 88 deletions.
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ruby 3.3.0
21 changes: 21 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.4.4] - 2024-04-02

### Added

- Add `Lennarb::Router` module to manage the routes in the project. Now, the `Lennarb` class is the main class of the project.

## [0.4.3] - 2024-04-01

### Added

### Remove

- Remove `Lennarb::ApplicationBase` class from the project. Now, the `Lennarb` class is the main class of the project.

### Changed

- Improve performance of the RPS (Requests per second), memory and CPU usage.
- Change the `finish` method from `Lennarb` class to call `halt(@res.finish)` method to finish the response.

## [0.4.2] - 2024-08-02

### Added
Expand Down
45 changes: 43 additions & 2 deletions guides/getting-started/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,55 @@ end
run app
```

## Use `Lennarb::Router` module

You can use the `Lennarb::Router` module to define routes using the `route` method:

```ruby
# app.rb

require 'lennarb'

class App
include Lennarb::Router

route.get '/' do |_req, res|
res.status = 200
res.html 'Hello World'
end
end
```

The `route` method is a instance of `Lennarb`. So, you can use the following methods:

- `route.get`
- `route.post`
- `route.put`
- `route.delete`
- `route.patch`
- `route.options`
- `route.head`

Now you can use the `App` class in your `config.ru`:

```ruby
# config.ru

require_relative 'app'

run App.app
```

The `app` method freeze the routes and return a `Lennarb` instance.

## Parameters

You can get params from the request using `req.params`:
You can get `params` from the request using `req.params`:

```ruby
# app.rb

app.get '/hello/:name' do |req, res|
route.get '/hello/:name' do |req, res|
res.html "Hello #{req.params[:name]}"
end
```
Expand Down
2 changes: 1 addition & 1 deletion lennarb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'covered', '~> 0.25.1'
spec.add_development_dependency 'minitest', '~> 5.20'
spec.add_development_dependency 'puma', '~> 6.4'
spec.add_development_dependency 'rake', '~> 13.0'
spec.add_development_dependency 'rake', '~> 13.1'
spec.add_development_dependency 'rack-test', '~> 2.1'
spec.add_development_dependency 'rubocop', '~> 1.59'
spec.add_development_dependency 'rubocop-minitest', '~> 0.33.0'
Expand Down
110 changes: 42 additions & 68 deletions lib/lennarb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
require_relative 'lennarb/request'
require_relative 'lennarb/response'
require_relative 'lennarb/route_node'
require_relative 'lennarb/router'
require_relative 'lennarb/version'

class Lennarb
Expand All @@ -27,7 +28,6 @@ class LennarbError < StandardError; end
#
attr_reader :root

@routes = []
# Initialize the application
#
# @yield { ... } The application
Expand Down Expand Up @@ -55,32 +55,61 @@ def initialize
# @returns [Array] response
#
def call(env)
http_method = env.fetch('REQUEST_METHOD').to_sym
parts = SplitPath[env.fetch('PATH_INFO')]
http_method = env[Rack::REQUEST_METHOD].to_sym
parts = SplitPath[env[Rack::PATH_INFO]]

block, params = @root.match_route(parts, http_method)
return [404, { 'content-type' => 'text/plain' }, ['Not Found']] unless block

res = Response.new
req = Request.new(env, params)
instance_exec(req, res, &block)
@res = Response.new
req = Request.new(env, params)

res.finish
catch(:halt) do
instance_exec(req, @res, &block)
finish!
end
end

# Finish the request
#
# @returns [Array] Response
#
def finish! = halt(@res.finish)

# Immediately stops the request and returns `response`
# as per Rack's specification.
#
# halt([200, { "Content-Type" => "text/html" }, ["hello"]])
# halt([res.status, res.headers, res.body])
# halt(res.finish)
#
# @parameter [Array] response
#
# @returns [Array] response
#
def halt(response)
throw(:halt, response)
end

# Freeze the routes
#
# @returns [void]
#
def freeze! = @root.freeze

# Add a routes
#
# @parameter [String] path
# @parameter [Proc] block
#
# @returns [void]
#
def get(path, &block) = add_route(path, :GET, block)
def put(path, &block) = add_route(path, :PUT, block)
def post(path, &block) = add_route(path, :POST, block)
def head(path, &block) = add_route(path, :HEAD, block)
def patch(path, &block) = add_route(path, :PATCH, block)
def delete(path, &block) = add_route(path, :DELETE, block)
def get(path, &block) = add_route(path, :GET, block)
def put(path, &block) = add_route(path, :PUT, block)
def post(path, &block) = add_route(path, :POST, block)
def head(path, &block) = add_route(path, :HEAD, block)
def patch(path, &block) = add_route(path, :PATCH, block)
def delete(path, &block) = add_route(path, :DELETE, block)
def options(path, &block) = add_route(path, :OPTIONS, block)

private
Expand All @@ -97,59 +126,4 @@ def add_route(path, http_method, block)
parts = SplitPath[path]
@root.add_route(parts, http_method, block)
end

# Base class for Lennarb applications
#
class ApplicationBase < Lennarb
@routes = []

class << self
attr_reader :routes

# Add a route
#
# @parameter [String] path
# @parameter [Proc] block
#
# @returns [void]
#
%i[get post put patch delete].each do |http_method|
define_method(http_method) do |path, &block|
routes << { method: http_method, path:, proc: block }
end
end
end

# Inherited hook
#
# @parameter [Class] subclass
#
# @returns [void]
#
def self.inherited(subclass)
super
subclass.instance_variable_set(:@routes, routes.dup)
end

# Initialize the application
#
# @returns [ApplicationBase]
#
def initialize
super
setup_routes
end

private

# Setup the routes
#
# @returns [void]
#
def setup_routes
self.class.routes.each do |route_info|
__send__(route_info[:method], route_info[:path], &route_info[:proc])
end
end
end
end
33 changes: 33 additions & 0 deletions lib/lennarb/router.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2023-2024, by Aristóteles Coutinho.

class Lennarb
# This module must be included in the class that will use the router.
#
module Router
def self.included(base)
base.extend(ClassMethods)
base.instance_variable_set(:@router, Lennarb.new)
base.define_singleton_method(:router) do
base.instance_variable_get(:@router)
end
end

module ClassMethods
# Helper method to define a route
#
# returns [Lennarb] instance of the router
#
def route = router

# Use this in congi.ru to start the application
#
def app
router.freeze!
router
end
end
end
end
2 changes: 1 addition & 1 deletion lib/lennarb/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Copyright, 2023-2024, by Aristóteles Coutinho.

class Lennarb
VERSION = '0.4.2'
VERSION = '0.4.4'

public_constant :VERSION
end
31 changes: 31 additions & 0 deletions test/lib/lennarb/test_router.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2023-2024, by Aristóteles Coutinho.

require 'test_helper'

class Lennarb
class TestRouterTest < Minitest::Test
class MyApp
include Lennarb::Router
end

def test_included
assert_respond_to MyApp, :route
assert_respond_to MyApp, :app
end

def test_ancestors
assert_includes MyApp.ancestors, Lennarb::Router
end

def test_router
assert_kind_of Lennarb, MyApp.router
end

def test_app
assert_kind_of Lennarb, MyApp.app
end
end
end
41 changes: 25 additions & 16 deletions test/lib/test_lennarb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,40 @@ def test_initialize_with_block
assert_respond_to app, :call
end

class TestApp < Lennarb::ApplicationBase
get '/' do |_req, res|
class TestApp
include Lennarb::Router

route.get '/' do |_req, res|
res.status = 200
res.html('GET Response')
end

post '/' do |_req, res|
route.post '/' do |_req, res|
res.status = 201
res.html('POST Response')
end
end

def test_subclass_has_http_methods
assert_respond_to TestApp, :get
assert_respond_to TestApp, :post
assert_respond_to TestApp, :put
assert_respond_to TestApp, :delete
assert_respond_to TestApp, :patch
def use_app(app) = Rack::Test::Session.new(app)

def test_router
app = use_app(TestApp.app)

app.get '/'

assert_equal 200, app.last_response.status
assert_equal 'GET Response', app.last_response.body
assert_equal 'text/html', app.last_response.headers[Rack::CONTENT_TYPE]
end

def test_routes_are_defined
refute_empty TestApp.routes
assert_equal 2, TestApp.routes.count
assert_equal :get, TestApp.routes.first[:method]
assert_equal '/', TestApp.routes.first[:path]
assert_equal :post, TestApp.routes.last[:method]
assert_equal '/', TestApp.routes.last[:path]
def test_router_post
app = use_app(TestApp.app)

app.post '/'

assert_equal 201, app.last_response.status
assert_equal 'POST Response', app.last_response.body
assert_equal 'text/html', app.last_response.headers[Rack::CONTENT_TYPE]
end

def app
Expand Down

0 comments on commit 9a9eea8

Please sign in to comment.