├── README.md ├── app ├── controllers │ └── tests_controller.rb ├── models │ └── test.rb └── views │ └── tests │ ├── index.html.erb │ └── list.html.erb ├── config.ru ├── config ├── database.yml ├── environment.rb └── routes.rb ├── db └── test_guru.sqlite └── lib ├── simpler.rb └── simpler ├── application.rb ├── controller.rb ├── router.rb ├── router └── route.rb └── view.rb /README.md: -------------------------------------------------------------------------------- 1 | # Simpler 2 | 3 | **Simpler** is a little web framework written in [Ruby](https://www.ruby-lang.org) language. It's compatible with [Rack](https://rack.github.io) interface and intended to **learn** how web frameworks work in general. 4 | 5 | ## The application overview 6 | 7 | Simpler application is a singleton instance of the `Simpler::Application` class. For convenience it can be obtained by calling `Simpler.application` method. This instance holds all the routes and responds to `call` method which is required by the Rack interface. 8 | -------------------------------------------------------------------------------- /app/controllers/tests_controller.rb: -------------------------------------------------------------------------------- 1 | class TestsController < Simpler::Controller 2 | 3 | def index 4 | @time = Time.now 5 | end 6 | 7 | def create 8 | 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /app/models/test.rb: -------------------------------------------------------------------------------- 1 | # Simpler.application.db.create_table(:tests) do 2 | # primary_key :id 3 | # String :title, null: false 4 | # Integer :level, default: 0 5 | # end 6 | class Test < Sequel::Model 7 | 8 | end 9 | -------------------------------------------------------------------------------- /app/views/tests/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Index | Simpler application 6 | 7 | 8 |

Simpler framework at work!

9 | 10 |

<%= @time %>

11 | 12 | -------------------------------------------------------------------------------- /app/views/tests/list.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | List | Simpler application 6 | 7 | 8 |

Simpler framework at work!

9 | 10 |

<%= @time %>

11 | 12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require_relative 'config/environment' 2 | 3 | run Simpler.application 4 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | adapter: sqlite 2 | database: db/test_guru.sqlite 3 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | require_relative '../lib/simpler' 2 | 3 | Simpler.application.bootstrap! 4 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Simpler.application.routes do 2 | get '/tests', 'tests#index' 3 | post '/tests', 'tests#create' 4 | end 5 | -------------------------------------------------------------------------------- /db/test_guru.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psylone/simpler/c3d0a7863d0ef1e6a21bae73fe055ff130a045fa/db/test_guru.sqlite -------------------------------------------------------------------------------- /lib/simpler.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | require_relative 'simpler/application' 3 | 4 | module Simpler 5 | 6 | class << self 7 | def application 8 | Application.instance 9 | end 10 | 11 | def root 12 | Pathname.new(File.expand_path('..', __dir__)) 13 | end 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /lib/simpler/application.rb: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'singleton' 3 | require 'sequel' 4 | require_relative 'router' 5 | require_relative 'controller' 6 | 7 | module Simpler 8 | class Application 9 | 10 | include Singleton 11 | 12 | attr_reader :db 13 | 14 | def initialize 15 | @router = Router.new 16 | @db = nil 17 | end 18 | 19 | def bootstrap! 20 | setup_database 21 | require_app 22 | require_routes 23 | end 24 | 25 | def routes(&block) 26 | @router.instance_eval(&block) 27 | end 28 | 29 | def call(env) 30 | route = @router.route_for(env) 31 | controller = route.controller.new(env) 32 | action = route.action 33 | 34 | make_response(controller, action) 35 | end 36 | 37 | private 38 | 39 | def require_app 40 | Dir["#{Simpler.root}/app/**/*.rb"].each { |file| require file } 41 | end 42 | 43 | def require_routes 44 | require Simpler.root.join('config/routes') 45 | end 46 | 47 | def setup_database 48 | database_config = YAML.load_file(Simpler.root.join('config/database.yml')) 49 | database_config['database'] = Simpler.root.join(database_config['database']) 50 | @db = Sequel.connect(database_config) 51 | end 52 | 53 | def make_response(controller, action) 54 | controller.make_response(action) 55 | end 56 | 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/simpler/controller.rb: -------------------------------------------------------------------------------- 1 | require_relative 'view' 2 | 3 | module Simpler 4 | class Controller 5 | 6 | attr_reader :name, :request, :response 7 | 8 | def initialize(env) 9 | @name = extract_name 10 | @request = Rack::Request.new(env) 11 | @response = Rack::Response.new 12 | end 13 | 14 | def make_response(action) 15 | @request.env['simpler.controller'] = self 16 | @request.env['simpler.action'] = action 17 | 18 | set_default_headers 19 | send(action) 20 | write_response 21 | 22 | @response.finish 23 | end 24 | 25 | private 26 | 27 | def extract_name 28 | self.class.name.match('(?.+)Controller')[:name].downcase 29 | end 30 | 31 | def set_default_headers 32 | @response['Content-Type'] = 'text/html' 33 | end 34 | 35 | def write_response 36 | body = render_body 37 | 38 | @response.write(body) 39 | end 40 | 41 | def render_body 42 | View.new(@request.env).render(binding) 43 | end 44 | 45 | def params 46 | @request.params 47 | end 48 | 49 | def render(template) 50 | @request.env['simpler.template'] = template 51 | end 52 | 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/simpler/router.rb: -------------------------------------------------------------------------------- 1 | require_relative 'router/route' 2 | 3 | module Simpler 4 | class Router 5 | 6 | def initialize 7 | @routes = [] 8 | end 9 | 10 | def get(path, route_point) 11 | add_route(:get, path, route_point) 12 | end 13 | 14 | def post(path, route_point) 15 | add_route(:post, path, route_point) 16 | end 17 | 18 | def route_for(env) 19 | method = env['REQUEST_METHOD'].downcase.to_sym 20 | path = env['PATH_INFO'] 21 | 22 | @routes.find { |route| route.match?(method, path) } 23 | end 24 | 25 | private 26 | 27 | def add_route(method, path, route_point) 28 | route_point = route_point.split('#') 29 | controller = controller_from_string(route_point[0]) 30 | action = route_point[1] 31 | route = Route.new(method, path, controller, action) 32 | 33 | @routes.push(route) 34 | end 35 | 36 | def controller_from_string(controller_name) 37 | Object.const_get("#{controller_name.capitalize}Controller") 38 | end 39 | 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/simpler/router/route.rb: -------------------------------------------------------------------------------- 1 | module Simpler 2 | class Router 3 | class Route 4 | 5 | attr_reader :controller, :action 6 | 7 | def initialize(method, path, controller, action) 8 | @method = method 9 | @path = path 10 | @controller = controller 11 | @action = action 12 | end 13 | 14 | def match?(method, path) 15 | @method == method && path.match(@path) 16 | end 17 | 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/simpler/view.rb: -------------------------------------------------------------------------------- 1 | require 'erb' 2 | 3 | module Simpler 4 | class View 5 | 6 | VIEW_BASE_PATH = 'app/views'.freeze 7 | 8 | def initialize(env) 9 | @env = env 10 | end 11 | 12 | def render(binding) 13 | template = File.read(template_path) 14 | 15 | ERB.new(template).result(binding) 16 | end 17 | 18 | private 19 | 20 | def controller 21 | @env['simpler.controller'] 22 | end 23 | 24 | def action 25 | @env['simpler.action'] 26 | end 27 | 28 | def template 29 | @env['simpler.template'] 30 | end 31 | 32 | def template_path 33 | path = template || [controller.name, action].join('/') 34 | 35 | Simpler.root.join(VIEW_BASE_PATH, "#{path}.html.erb") 36 | end 37 | 38 | end 39 | end 40 | --------------------------------------------------------------------------------