Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

# Gemfile
ruby '3.3.3'
source 'https://rubygems.org'

gem 'puma'
gem 'rack'
gem 'rackup'
gem 'rubocop', require: false
gem 'sequel'
gem 'sqlite3'
52 changes: 52 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
GEM
remote: https://rubygems.org/
specs:
ast (2.4.2)
bigdecimal (3.2.0)
json (2.11.3)
nio4r (2.7.4)
parallel (1.23.0)
parser (3.2.2.1)
ast (~> 2.4.1)
puma (6.6.0)
nio4r (~> 2.0)
rack (3.1.15)
rackup (2.2.1)
rack (>= 3)
rainbow (3.1.1)
regexp_parser (2.10.0)
rexml (3.4.1)
rubocop (1.50.2)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.2.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.28.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.28.1)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
sequel (5.92.0)
bigdecimal
sqlite3 (1.7.3-arm64-darwin)
unicode-display_width (2.4.2)

PLATFORMS
arm64-darwin-24

DEPENDENCIES
puma
rack
rackup
rubocop
sequel
sqlite3

RUBY VERSION
ruby 3.3.3p89

BUNDLED WITH
2.6.9
12 changes: 11 additions & 1 deletion app/controllers/tests_controller.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
class TestsController < Simpler::Controller
# frozen_string_literal: true

class TestsController < Simpler::Controller
def index
@time = Time.now
end

def create
render json: { message: 'Created successfully' }, status: 201
end

def plain
headers['X-Time-Header'] = Time.now.to_s
render plain: "Time.now: #{Time.now}\n"
end

def show
@test_id = params[:id]
render plain: "Test ID: #{@test_id}"
end
end
3 changes: 2 additions & 1 deletion app/models/test.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# frozen_string_literal: true

# Simpler.application.db.create_table(:tests) do
# primary_key :id
# String :title, null: false
# Integer :level, default: 0
# end
class Test < Sequel::Model

end
5 changes: 4 additions & 1 deletion config.ru
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
require_relative 'config/environment'
# frozen_string_literal: true

require_relative './config/environment'

use Simpler::Middleware::Logger
run Simpler.application
3 changes: 3 additions & 0 deletions config/environment.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# frozen_string_literal: true

require_relative '../lib/simpler/middleware/logger'
require_relative '../lib/simpler'

Simpler.application.bootstrap!
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
# frozen_string_literal: true

Simpler.application.routes do
get '/tests', 'tests#index'
get '/tests/plain', 'tests#plain'
post '/tests', 'tests#create'
get '/tests/:id', 'tests#show'
end
4 changes: 2 additions & 2 deletions lib/simpler.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# frozen_string_literal: true

require 'pathname'
require_relative 'simpler/application'

module Simpler

class << self
def application
Application.instance
Expand All @@ -12,5 +13,4 @@ def root
Pathname.new(File.expand_path('..', __dir__))
end
end

end
28 changes: 22 additions & 6 deletions lib/simpler/application.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# frozen_string_literal: true

require 'rack'
require 'yaml'
require 'singleton'
require 'sequel'
Expand All @@ -6,7 +9,6 @@

module Simpler
class Application

include Singleton

attr_reader :db
Expand All @@ -28,10 +30,13 @@ def routes(&block)

def call(env)
route = @router.route_for(env)
controller = route.controller.new(env)
action = route.action

make_response(controller, action)
if route.nil?
not_found_response
else
controller = route.controller.new(env)
action = route.action
make_response(controller, action)
end
end

private
Expand All @@ -50,9 +55,20 @@ def setup_database
@db = Sequel.connect(database_config)
end

def not_found_response
build_error_response(404, 'Not Found')
end

def build_error_response(status, body)
Rack::Response.new do |res|
res.status = status
res['Content-Type'] = 'text/plain'
res.write(body)
end.finish
end

def make_response(controller, action)
controller.make_response(action)
end

end
end
68 changes: 58 additions & 10 deletions lib/simpler/controller.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
# frozen_string_literal: true

require 'json'
require_relative 'view'

module Simpler
class Controller

attr_reader :name, :request, :response

def initialize(env)
@name = extract_name
@request = Rack::Request.new(env)
@response = Rack::Response.new
@env = env
end

def make_response(action)
Expand All @@ -22,33 +25,78 @@ def make_response(action)
@response.finish
end

def headers
@response.headers
end

private

def extract_name
self.class.name.match('(?<name>.+)Controller')[:name].downcase
end

def set_default_headers
@response['Content-Type'] = 'text/html'
@response['Content-Type'] ||= 'text/html'
end

def write_response
body = render_body

@response.write(body)
@response.write(render_body)
end

def render_body
View.new(@request.env).render(binding)
return render_json if json_response?
return render_plain if plain_response?

render_template
end

def params
@request.params
def plain_response?
@env.key?('simpler.plain')
end

def json_response?
@env.key?('simpler.json')
end

def render(template)
@request.env['simpler.template'] = template
def render_plain
@response['Content-Type'] = 'text/plain'
@env['simpler.plain']
end

def render_json
@response['Content-Type'] = 'application/json'
JSON.generate(@env['simpler.json'])
end

def render_template
View.new(@env).render(binding)
end

def params
return @params if @params

@params = @request.params.transform_keys(&:to_sym)
route_params = @env['simpler.route_params']&.transform_keys(&:to_sym)
@params.merge!(route_params) if route_params
@params
end

def render(options)
case options
when String, Symbol
@env['simpler.template'] = options.to_s
when Hash
@response.status = options.delete(:status) if options.key?(:status)
if options[:plain]
@env['simpler.plain'] = options[:plain].to_s
elsif options[:json]
@env['simpler.json'] = options[:json]
else
raise ArgumentError, "Unknown render option: #{options.keys.join(', ')}"
end
else
raise ArgumentError, "Invalid argument for render: #{options.inspect}"
end
end
end
end
74 changes: 74 additions & 0 deletions lib/simpler/middleware/logger.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

require 'logger'
require 'fileutils'

module Simpler
module Middleware
class Logger
LOG_FILE = 'log/app.log'

def initialize(app)
@app = app
ensure_log_directory
@logger = setup_logger
end

def call(env)
start = Time.now
status, headers, body = @app.call(env)

log_request(env)
log_response(status, headers, env)

[status, headers, body]
end

private

def ensure_log_directory
FileUtils.mkdir_p(File.dirname(LOG_FILE))
end

def setup_logger
::Logger.new(LOG_FILE, 'daily')
end

def log_request(env)
request = Rack::Request.new(env)
controller = env['simpler.controller']
action = env['simpler.action']

message = []
message << "Request: #{env['REQUEST_METHOD']} #{env['PATH_INFO']}#{env['QUERY_STRING'].empty? ? '' : "?#{env['QUERY_STRING']}"}"
message << "Handler: #{controller.class.name}##{action}" if controller && action
message << "Parameters: #{collect_parameters(request, env)}"

@logger.info(message.join("\n"))
end

def log_response(status, headers, env)
message = "Response: #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
message << " [#{headers['Content-Type']}]"
message << " #{env['simpler.template']}" if env['simpler.template']

@logger.info(message)
end

def collect_parameters(request, env)
params = {}

# Query parameters
params.merge!(request.GET)

# POST parameters
params.merge!(request.POST) if request.post?

# Route parameters
params.merge!(env['simpler.route_params'] || {})

params
end
end
end
end
Loading