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
25 changes: 23 additions & 2 deletions app/controllers/tests_controller.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
class TestsController < Simpler::Controller

def index
@time = Time.now
end

def create
def plain
render plain: 'Это простой текстовый ответ'
end

def json
data = { message: 'Это JSON', time: Time.now }
render json: data
end

def html
render html: '<h1>Это HTML</h1>'
end

def xml
data = { message: 'Это XML', time: Time.now }
render xml: data
end

def show
render plain: "Запрошен тест с ID: #{params[:id]}"
end

def custom_status
status 201
render plain: 'Создано успешно!'
end
end
1 change: 0 additions & 1 deletion app/models/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
# Integer :level, default: 0
# end
class Test < Sequel::Model

end
8 changes: 8 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require_relative '../lib/simpler/middleware/logger'

class Application < Simpler::Application
def initialize
super
use Simpler::Middleware::Logger
end
end
7 changes: 6 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
Simpler.application.routes do
get '/tests', 'tests#index'
post '/tests', 'tests#create'
get '/tests/plain', 'tests#plain'
get '/tests/json', 'tests#json'
get '/tests/html', 'tests#html'
get '/tests/xml', 'tests#xml'
get '/tests/:id', 'tests#show'
post '/tests/status', 'tests#custom_status'
end
2 changes: 0 additions & 2 deletions lib/simpler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
require_relative 'simpler/application'

module Simpler

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

end
73 changes: 66 additions & 7 deletions lib/simpler/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@

module Simpler
class Application

include Singleton

attr_reader :db
DEFAULT_SEQUEL_EXTENSIONS = %i[pagination query_literals].freeze

attr_reader :db, :router, :middleware

def initialize
@router = Router.new
@middleware = []
@db = nil
end

Expand All @@ -23,36 +25,93 @@ def bootstrap!
end

def routes(&block)
@router.instance_eval(&block)
router.instance_eval(&block)
end

def call(env)
route = @router.route_for(env)
route = router.route_for(env)
env['simpler.route'] = route
controller = route.controller.new(env)
action = route.action

make_response(controller, action)
rescue Router::RouteNotFoundError => e
make_not_found_response(e)
rescue StandardError => e
make_error_response(e)
end

def use(middleware_class, *args)
middleware << proc { |app| middleware_class.new(app, *args) }
end

def to_app
return @app if @app

builder = Rack::Builder.new
apply_middleware(builder)
builder.run(self)
@app = builder.to_app
end

private

def require_app
Dir["#{Simpler.root}/app/**/*.rb"].each { |file| require file }
Dir[File.join(Simpler.root, 'app', '**', '*.rb')].sort.each { |file| require file }
end

def require_routes
require Simpler.root.join('config/routes')
end

def setup_database
database_config = YAML.load_file(Simpler.root.join('config/database.yml'))
database_config['database'] = Simpler.root.join(database_config['database'])
database_config = load_database_config
@db = Sequel.connect(database_config)
setup_database_extensions
end

def load_database_config
config = YAML.safe_load(
File.read(Simpler.root.join('config/database.yml')),
permitted_classes: [Symbol]
)
config['database'] = Simpler.root.join(config['database'])
config
end

def setup_database_extensions
DEFAULT_SEQUEL_EXTENSIONS.each { |ext| @db.extension(ext) }
end

def apply_middleware(builder)
middleware.each { |m| builder.use(m) }
end

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

def make_not_found_response(error)
[
404,
{ 'Content-Type' => 'text/plain' },
["404 Not Found\n\n#{error.message}"]
]
end

def make_error_response(error)
log_error(error)
[
500,
{ 'Content-Type' => 'text/plain' },
["500 Internal Server Error\n\n#{error.message}"]
]
end

def log_error(error)
logger = Logger.new(Simpler.root.join('log/errors.log'))
logger.error(error.message)
logger.error(error.backtrace.join("\n"))
end
end
end
100 changes: 87 additions & 13 deletions lib/simpler/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,127 @@

module Simpler
class Controller
class DoubleRenderError < StandardError; end

CONTENT_TYPES = {
plain: 'text/plain',
json: 'application/json',
xml: 'application/xml',
html: 'text/html'
}.freeze

attr_reader :name, :request, :response

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

def make_response(action)
@request.env['simpler.controller'] = self
@request.env['simpler.action'] = action

set_request_context(action)
set_default_headers
send(action)
write_response
process_action(action)
ensure_response_written

@response.finish
end

protected

def params
@params ||= begin
request_params = @request.params
route_params = @request.env['simpler.route']&.params || {}
request_params.merge(route_params)
end
end

def render(template_or_options)
ensure_not_rendered
handle_render_options(template_or_options)
write_response
end

def status(code)
@response.status = code.to_i
end

def headers
@response.headers
end

private

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

def set_request_context(action)
@request.env['simpler.controller'] = self
@request.env['simpler.action'] = action
end

def set_default_headers
@response['Content-Type'] = 'text/html'
headers['Content-Type'] = CONTENT_TYPES[:html]
end

def process_action(action)
send(action)
end

def ensure_response_written
write_response unless @render_performed
end

def ensure_not_rendered
raise DoubleRenderError, 'Cannot render or redirect more than once per action' if @render_performed
end

def handle_render_options(template_or_options)
case template_or_options
when String, Symbol
@request.env['simpler.template'] = template_or_options
when Hash
@render_options = template_or_options
end
end

def write_response
body = render_body
return if @render_performed

body = render_body
@response.write(body)
@render_performed = true
end

def render_body
View.new(@request.env).render(binding)
if @render_options
process_render_options
else
render_template
end
end

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

def render(template)
@request.env['simpler.template'] = template
def process_render_options
render_type, content = @render_options.first
return render_template unless CONTENT_TYPES.key?(render_type)

headers['Content-Type'] = CONTENT_TYPES[render_type]
format_response_body(render_type, content)
end

def format_response_body(type, content)
case type
when :json then content.to_json
when :xml then content.to_xml
else content.to_s
end
end
end
end
Loading