From cfe3675469e6d9448f98579ee9224c8766c5f155 Mon Sep 17 00:00:00 2001 From: Ikbol Zulolov Date: Thu, 2 Oct 2025 14:00:37 +0300 Subject: [PATCH 1/7] Implementing render method functionality extension --- app/controllers/tests_controller.rb | 10 ++++++++++ config/routes.rb | 2 ++ 2 files changed, 12 insertions(+) diff --git a/app/controllers/tests_controller.rb b/app/controllers/tests_controller.rb index 1526a689..248316ea 100644 --- a/app/controllers/tests_controller.rb +++ b/app/controllers/tests_controller.rb @@ -1,5 +1,6 @@ class TestsController < Simpler::Controller + # HTML (шаблон). GET /tests def index @time = Time.now end @@ -8,4 +9,13 @@ def create end + # Для проверки plain. GET /tests/plain + def plain + render plain: "Plain text response" + end + + # Для проверки json. GET /tests/json + def json + render json: { message: "Hello from JSON", data: [1, 2, 3], time: Time.now } + end end diff --git a/config/routes.rb b/config/routes.rb index 4a751251..007470eb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Simpler.application.routes do get '/tests', 'tests#index' post '/tests', 'tests#create' + get '/tests/plain', 'tests#plain' + get '/tests/json', 'tests#json' end From 19d85243bd98201e8c365a934ec3049e1a52f739 Mon Sep 17 00:00:00 2001 From: Ikbol Zulolov Date: Fri, 3 Oct 2025 09:23:14 +0300 Subject: [PATCH 2/7] Implementing render method functionality extension 2.0 --- lib/simpler.rb | 3 +- lib/simpler/controller.rb | 42 +++++++++++---------- lib/simpler/response_renderer.rb | 64 ++++++++++++++++++++++++++++++++ lib/simpler/router.rb | 4 +- lib/simpler/router/route.rb | 6 ++- 5 files changed, 93 insertions(+), 26 deletions(-) create mode 100644 lib/simpler/response_renderer.rb diff --git a/lib/simpler.rb b/lib/simpler.rb index f9dfe3c4..e06d83f7 100644 --- a/lib/simpler.rb +++ b/lib/simpler.rb @@ -1,8 +1,8 @@ require 'pathname' require_relative 'simpler/application' +require_relative 'simpler/response_renderer' module Simpler - class << self def application Application.instance @@ -12,5 +12,4 @@ def root Pathname.new(File.expand_path('..', __dir__)) end end - end diff --git a/lib/simpler/controller.rb b/lib/simpler/controller.rb index 9383b035..2eb797e7 100644 --- a/lib/simpler/controller.rb +++ b/lib/simpler/controller.rb @@ -1,25 +1,27 @@ require_relative 'view' - +require_relative 'response_renderer' +require 'pry' 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 + @render_options = nil end def make_response(action) - @request.env['simpler.controller'] = self - @request.env['simpler.action'] = action - - set_default_headers + @env['simpler.controller'] = self + @env['simpler.action'] = action + @env['simpler.controller_binding'] = binding send(action) write_response @response.finish + binding.pry end private @@ -28,27 +30,27 @@ def extract_name self.class.name.match('(?.+)Controller')[:name].downcase end - def set_default_headers - @response['Content-Type'] = 'text/html' - end - - def write_response - body = render_body - + def write_response + renderer = ResponseRenderer.new(@response, @env) + + if @render_options + body = renderer.render(@render_options) + else + # Например, если в action-е index не указан render json: или render plain: + # то автоматический рендерится views/users/index.html.erb. Поэтому передаем nil + body = renderer.render(nil) + end + @response.write(body) - end - - def render_body - View.new(@request.env).render(binding) + binding.pry end def params @request.params end - def render(template) - @request.env['simpler.template'] = template + def render(options) + @render_options = options end - end end diff --git a/lib/simpler/response_renderer.rb b/lib/simpler/response_renderer.rb new file mode 100644 index 00000000..1fb14e21 --- /dev/null +++ b/lib/simpler/response_renderer.rb @@ -0,0 +1,64 @@ +require 'json' + +module Simpler + class ResponseRenderer + CONTENT_TYPES = { + html: 'text/html', + plain: 'text/plain', + json: 'application/json' + }.freeze + + def initialize(response, env) + @response = response + @env = env + end + + def render(render_options) + format = detect_format(render_options) + set_content_type(format) + generate_body(format, render_options) + end + + private + + def detect_format(options) + return :plain if options.is_a?(Hash) && options.key?(:plain) + return :json if options.is_a?(Hash) && options.key?(:json) + :html # по умалчанию, при nil + end + + def set_content_type(format) + @response['Content-Type'] = CONTENT_TYPES[format] + end + + def generate_body(format, options) + case format + when :plain + options[:plain].to_s + when :json + JSON.generate(options[:json]) + when :html + render_template(options) + end + end + + def render_template(options) + template = extract_template(options) + @env['simpler.template'] = template if template + + # Создаем и рендерим view только для HTML + View.new(@env).render(@env['simpler.controller_binding']) + end + + def extract_template(options) + case options + when String, Symbol + options.to_s + when Hash + options[:template] + else + nil + end + end + end +end diff --git a/lib/simpler/router.rb b/lib/simpler/router.rb index 14b3415c..e9e2f7dc 100644 --- a/lib/simpler/router.rb +++ b/lib/simpler/router.rb @@ -16,9 +16,9 @@ def post(path, route_point) end def route_for(env) - method = env['REQUEST_METHOD'].downcase.to_sym path = env['PATH_INFO'] - + method = env['REQUEST_METHOD'].downcase.to_sym + @routes.find { |route| route.match?(method, path) } end diff --git a/lib/simpler/router/route.rb b/lib/simpler/router/route.rb index 4c66b4b7..6850a412 100644 --- a/lib/simpler/router/route.rb +++ b/lib/simpler/router/route.rb @@ -1,8 +1,9 @@ +require 'pry' module Simpler class Router class Route - attr_reader :controller, :action + attr_reader :method, :path, :controller, :action def initialize(method, path, controller, action) @method = method @@ -12,7 +13,8 @@ def initialize(method, path, controller, action) end def match?(method, path) - @method == method && path.match(@path) + @method == method && @path == path + binding.pry end end From 25f407c7cd8bba0560cf84d5281a6e7492fe4df4 Mon Sep 17 00:00:00 2001 From: Ikbol Zulolov Date: Fri, 3 Oct 2025 10:36:39 +0300 Subject: [PATCH 3/7] if there is no page, a 404 error will be returned. --- lib/simpler/application.rb | 4 ++++ lib/simpler/controller.rb | 4 +--- lib/simpler/router/route.rb | 2 -- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/simpler/application.rb b/lib/simpler/application.rb index 711946a9..e406a1a4 100644 --- a/lib/simpler/application.rb +++ b/lib/simpler/application.rb @@ -28,6 +28,10 @@ def routes(&block) def call(env) route = @router.route_for(env) + # Если маршрут не найден то выдаём клиенту ошибку 404 + unless route + return [404, {'Content-Type' => 'text/plain'}, [' 404 Not Found']] + end controller = route.controller.new(env) action = route.action diff --git a/lib/simpler/controller.rb b/lib/simpler/controller.rb index 2eb797e7..b3b25efc 100644 --- a/lib/simpler/controller.rb +++ b/lib/simpler/controller.rb @@ -1,6 +1,6 @@ require_relative 'view' require_relative 'response_renderer' -require 'pry' + module Simpler class Controller attr_reader :name, :request, :response @@ -21,7 +21,6 @@ def make_response(action) write_response @response.finish - binding.pry end private @@ -42,7 +41,6 @@ def write_response end @response.write(body) - binding.pry end def params diff --git a/lib/simpler/router/route.rb b/lib/simpler/router/route.rb index 6850a412..8f5c06de 100644 --- a/lib/simpler/router/route.rb +++ b/lib/simpler/router/route.rb @@ -1,4 +1,3 @@ -require 'pry' module Simpler class Router class Route @@ -14,7 +13,6 @@ def initialize(method, path, controller, action) def match?(method, path) @method == method && @path == path - binding.pry end end From f5a9e1fc7dc04a6d92fea146f8789bc624b642dd Mon Sep 17 00:00:00 2001 From: Ikbol Zulolov Date: Fri, 3 Oct 2025 11:05:25 +0300 Subject: [PATCH 4/7] add status to controller --- app/controllers/tests_controller.rb | 9 ++++++++- config/routes.rb | 1 + lib/simpler/application.rb | 2 +- lib/simpler/controller.rb | 6 +++++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/controllers/tests_controller.rb b/app/controllers/tests_controller.rb index 248316ea..0c4ecf6b 100644 --- a/app/controllers/tests_controller.rb +++ b/app/controllers/tests_controller.rb @@ -5,8 +5,10 @@ def index @time = Time.now end + # curl.exe -X POST http://localhost:9292/tests def create - + status 201 # В логах будет статус 201, если не указать status то будет дефольтный 200-й + render plain: "Test created successfully!" end # Для проверки plain. GET /tests/plain @@ -18,4 +20,9 @@ def plain def json render json: { message: "Hello from JSON", data: [1, 2, 3], time: Time.now } end + + def server_error + status 500 # В логах будет статус 500 + render json: { error: "Internal server error", code: 500 } + end end diff --git a/config/routes.rb b/config/routes.rb index 007470eb..32d0e5d1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,4 +3,5 @@ post '/tests', 'tests#create' get '/tests/plain', 'tests#plain' get '/tests/json', 'tests#json' + get '/tests/server_error', 'tests#server_error' end diff --git a/lib/simpler/application.rb b/lib/simpler/application.rb index e406a1a4..ec36a774 100644 --- a/lib/simpler/application.rb +++ b/lib/simpler/application.rb @@ -28,7 +28,7 @@ def routes(&block) def call(env) route = @router.route_for(env) - # Если маршрут не найден то выдаём клиенту ошибку 404 + # Если маршрут не найден то выдаём клиенту ошибку 404 unless route return [404, {'Content-Type' => 'text/plain'}, [' 404 Not Found']] end diff --git a/lib/simpler/controller.rb b/lib/simpler/controller.rb index b3b25efc..3d2b96e9 100644 --- a/lib/simpler/controller.rb +++ b/lib/simpler/controller.rb @@ -22,7 +22,11 @@ def make_response(action) @response.finish end - + + def status(code) + @response.status = code + end + private def extract_name From 421d639711d3c368a94243398cfc8df08b0ae102 Mon Sep 17 00:00:00 2001 From: Ikbol Zulolov Date: Fri, 3 Oct 2025 11:28:01 +0300 Subject: [PATCH 5/7] added headers --- app/controllers/tests_controller.rb | 3 +++ lib/simpler/controller.rb | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/controllers/tests_controller.rb b/app/controllers/tests_controller.rb index 0c4ecf6b..f0729b43 100644 --- a/app/controllers/tests_controller.rb +++ b/app/controllers/tests_controller.rb @@ -7,12 +7,15 @@ def index # curl.exe -X POST http://localhost:9292/tests def create + headers['Content-Type'] = 'text/plain' + headers['X-Custom-Header'] = 'SimplerFramework' status 201 # В логах будет статус 201, если не указать status то будет дефольтный 200-й render plain: "Test created successfully!" end # Для проверки plain. GET /tests/plain def plain + headers['Content-Type'] = 'text/plain; charset=utf-8' render plain: "Plain text response" end diff --git a/lib/simpler/controller.rb b/lib/simpler/controller.rb index 3d2b96e9..668ff17d 100644 --- a/lib/simpler/controller.rb +++ b/lib/simpler/controller.rb @@ -22,11 +22,15 @@ def make_response(action) @response.finish end - + def status(code) @response.status = code end + def headers + @response.headers + end + private def extract_name From cd821a95a1f36c8731b5df9a6c9b646b315f4ec2 Mon Sep 17 00:00:00 2001 From: Ikbol Zulolov Date: Sun, 5 Oct 2025 00:56:51 +0300 Subject: [PATCH 6/7] add :id --- app/controllers/tests_controller.rb | 12 ++++++++++++ config/routes.rb | 1 + lib/simpler/controller.rb | 15 ++++++++++++++- lib/simpler/router.rb | 8 +++++++- lib/simpler/router/route.rb | 23 +++++++++++++++++++++-- 5 files changed, 55 insertions(+), 4 deletions(-) diff --git a/app/controllers/tests_controller.rb b/app/controllers/tests_controller.rb index f0729b43..b97f6ef8 100644 --- a/app/controllers/tests_controller.rb +++ b/app/controllers/tests_controller.rb @@ -1,3 +1,4 @@ +require 'pry' class TestsController < Simpler::Controller # HTML (шаблон). GET /tests @@ -28,4 +29,15 @@ def server_error status 500 # В логах будет статус 500 render json: { error: "Internal server error", code: 500 } end + + def show + test_id = params[:id] + @test = Test.all.find {|t| t.id == test_id } + + if @test + render json: {message: "Test with id #{test_id} is found! #{@test.title} - #{@test.level} "} + else + render json: {error: "Test with id #{test_id} is not found"} + end + end end diff --git a/config/routes.rb b/config/routes.rb index 32d0e5d1..ac1371ad 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,4 +4,5 @@ get '/tests/plain', 'tests#plain' get '/tests/json', 'tests#json' get '/tests/server_error', 'tests#server_error' + get '/tests/:id', 'tests#show' end diff --git a/lib/simpler/controller.rb b/lib/simpler/controller.rb index 668ff17d..6839711a 100644 --- a/lib/simpler/controller.rb +++ b/lib/simpler/controller.rb @@ -52,7 +52,20 @@ def write_response end def params - @request.params + request_params = @request.params || {} + route_params = @env['simpler.route_params'] || {} + + converted_route_params = route_params.transform_values do |value| + if value.is_a?(String) && value.match?(/^\d+$/) + # Переводим в integer, т.к Sequel ожидает integer + # Если не перевести то получим Sequel::Error: Invalid filter expression: "1" + value.to_i + else + value + end + end + + request_params.merge(converted_route_params) end def render(options) diff --git a/lib/simpler/router.rb b/lib/simpler/router.rb index e9e2f7dc..b45f3c0a 100644 --- a/lib/simpler/router.rb +++ b/lib/simpler/router.rb @@ -18,8 +18,14 @@ def post(path, route_point) def route_for(env) path = env['PATH_INFO'] method = env['REQUEST_METHOD'].downcase.to_sym + route = @routes.find { |route| route.match?(method, path) } # Найдем подходящий маршрут - @routes.find { |route| route.match?(method, path) } + if route + params = route.extract_params(path) # Извлекаем параметры из найденного маршрута + env['simpler.route_params'] = params + end + + route end private diff --git a/lib/simpler/router/route.rb b/lib/simpler/router/route.rb index 8f5c06de..07559d78 100644 --- a/lib/simpler/router/route.rb +++ b/lib/simpler/router/route.rb @@ -11,10 +11,29 @@ def initialize(method, path, controller, action) @action = action end - def match?(method, path) - @method == method && @path == path + def match?(method, path) # Проверяем совпадение маршрута + return false unless @method == method + + if @path.include?(':id') + pattern = @path.gsub(':id', '(\d+)') + path.match?(/^#{pattern}$/) + else + @path == path + end end + def extract_params(path) # Извлекаем параметры из маршрута + return {} unless @path.include?(':id') + + pattern = @path.gsub(':id', '(\d+)') + match = path.match(/^#{pattern}$/) + + if match + { id: match[1] } + else + {} + end + end end end end From ce763061021e09749a1a1cce22b0e3fe4e005010 Mon Sep 17 00:00:00 2001 From: Ikbol Zulolov Date: Sun, 5 Oct 2025 21:00:57 +0300 Subject: [PATCH 7/7] Add logger --- config.ru | 2 + lib/simpler/middleware/logger.rb | 72 ++++++++++++++++++++++++++++++++ log/app.log | 21 ++++++++++ 3 files changed, 95 insertions(+) create mode 100644 lib/simpler/middleware/logger.rb create mode 100644 log/app.log diff --git a/config.ru b/config.ru index 3060cc20..e1e7140a 100644 --- a/config.ru +++ b/config.ru @@ -1,3 +1,5 @@ require_relative 'config/environment' +require_relative 'lib/simpler/middleware/logger' +use Simpler::Middleware::Logger run Simpler.application diff --git a/lib/simpler/middleware/logger.rb b/lib/simpler/middleware/logger.rb new file mode 100644 index 00000000..2edb0225 --- /dev/null +++ b/lib/simpler/middleware/logger.rb @@ -0,0 +1,72 @@ +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) + 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) + end + + def log_request(env) + request = Rack::Request.new(env) + controller = env['simpler.controller'] + action = env['simpler.action'] + + url = build_full_url(env) + handler = controller && action ? "#{controller.class.name}##{action}" : 'Unknown' + parameters = collect_parameters(request, env) + + @logger.info("Request: #{env['REQUEST_METHOD']} #{url}") + @logger.info("Handler: #{handler}") + @logger.info("Parameters: #{parameters}") + end + + def log_response(status, headers, env) + status_text = Rack::Utils::HTTP_STATUS_CODES[status] || 'Unknown' + content_type = headers['Content-Type'] || 'unknown' + template = env['simpler.template'] || '' + template_part = template.empty? ? '' : " #{template}" + + @logger.info("Response: #{status} #{status_text} [#{content_type}]#{template_part}") + end + + def build_full_url(env) + path = env['PATH_INFO'] + query = env['QUERY_STRING'] + query.empty? ? path : "#{path}?#{query}" + end + + def collect_parameters(request, env) + params = {} + params.merge!(request.GET) + params.merge!(request.POST) if request.post? + params.merge!(env['simpler.route_params'] || {}) + params + end + end + end +end diff --git a/log/app.log b/log/app.log new file mode 100644 index 00000000..8206f9b5 --- /dev/null +++ b/log/app.log @@ -0,0 +1,21 @@ +# Logfile created on 2025-10-05 20:57:29 +0300 by logger.rb/v1.7.0 +I, [2025-10-05T20:57:43.079197 #29144] INFO -- : Request: GET /tests/1 +I, [2025-10-05T20:57:43.079329 #29144] INFO -- : Handler: TestsController#show +I, [2025-10-05T20:57:43.079394 #29144] INFO -- : Parameters: {:id=>"1"} +I, [2025-10-05T20:57:43.079452 #29144] INFO -- : Response: 200 OK [application/json] +I, [2025-10-05T20:57:51.627036 #29144] INFO -- : Request: GET /tests +I, [2025-10-05T20:57:51.627132 #29144] INFO -- : Handler: TestsController#index +I, [2025-10-05T20:57:51.627185 #29144] INFO -- : Parameters: {} +I, [2025-10-05T20:57:51.627234 #29144] INFO -- : Response: 200 OK [text/html] +I, [2025-10-05T20:57:55.587605 #29144] INFO -- : Request: GET /tests/json +I, [2025-10-05T20:57:55.587877 #29144] INFO -- : Handler: TestsController#json +I, [2025-10-05T20:57:55.587930 #29144] INFO -- : Parameters: {} +I, [2025-10-05T20:57:55.587973 #29144] INFO -- : Response: 200 OK [application/json] +I, [2025-10-05T20:58:01.923058 #29144] INFO -- : Request: GET /tests/plain +I, [2025-10-05T20:58:01.923272 #29144] INFO -- : Handler: TestsController#plain +I, [2025-10-05T20:58:01.923364 #29144] INFO -- : Parameters: {} +I, [2025-10-05T20:58:01.923446 #29144] INFO -- : Response: 200 OK [text/plain] +I, [2025-10-05T20:58:06.054542 #29144] INFO -- : Request: GET /tests/server_error +I, [2025-10-05T20:58:06.054698 #29144] INFO -- : Handler: TestsController#server_error +I, [2025-10-05T20:58:06.054784 #29144] INFO -- : Parameters: {} +I, [2025-10-05T20:58:06.054865 #29144] INFO -- : Response: 500 Internal Server Error [application/json]