Rails 写了一个只有 100 行的 mini 版的 Rails

crazyjin · May 26, 2015 · Last by chenge replied at May 28, 2015 · 3582 hits

只有一百行代码。rack based. 模板引擎是 slim. 这里有个demo. 仅供一乐,欢迎吐槽。

#encoding utf-8
require 'rubygems'
require 'bundler/setup'
require 'slim'

module MyRails
  class Router
    @@routes = {}
    class << self
      #RSTfull route helpers, simply map path to controllers and actions
      #get "/blogs", "blogs_controller#index"
      #post "/blogs", "blogs_controller#create"
      %i{get post put delete}.each do |m|
        define_method(m) do |path, action| 
          controller, action = action.split('#')
          controller = controller.split('_').collect(&:capitalize).join
          controller = Object.const_get controller

          @@routes[m] = {} if @@routes[m].nil?
          @@routes[m][path] = [controller, action.to_sym]
        end
      end

      def draw_routes(&block)
        self.instance_eval(&block)
      end

      def call_controller_action env
        method, path, params = env['REQUEST_METHOD'].downcase.to_sym, env["REQUEST_PATH"], env["QUERY_STRING"]
        return [404, {"content-type" => "text/html"}, ["404, you got it!!"]] if @@routes[method].nil? || @@routes[method][path].nil?

        controller = @@routes[method][path][0].new(params)
        action = @@routes[method][path][1]
        controller.send action

        [controller.http_status, controller.http_headers, [controller.http_body]]
      rescue
        [500, {"content-type" => "text/html"}, [$!.message]]
      end
    end
  end

  class Controller
    attr_accessor :http_status, :http_headers, :http_body, :params

    class ViewContext 
      def create_accessable_instance_variable key
        singleton_class.class_eval {attr_accessor key}
      end
    end

    def initialize query_string=""
      key_value_strs =  query_string.split('&')
      key_value_strs.each do |str|
        key_value_pair = str.split '='
        @params[key_value_pair[0]] = key_value_pair[1] if 2 <= key_value_pair.length
      end

      @http_status = 200
      @http_headers = {"content-type" => "text/html"}
    end

    def render template
      layout = File.open("./application.html.slim", "rb").read
      content = File.open("./#{template.to_s}.html.slim", "rb").read

      l = Slim::Template.new { layout }

      variables = instance_variables
      env = ViewContext.new
      variables.each do |v|
        env.create_accessable_instance_variable v[1..-1]
        env.send("#{v[1..-1]}=", instance_variable_get(v))
      end

      c = Slim::Template.new { content }.render(env)
      @http_body = l.render { c }
    end
  end

  class Application
    class << self
      def draw_routes &b
        Router.draw_routes &b
      end

      #rack server need this
      def call(env)
        Router.call_controller_action env
      rescue
        $!.backtrace.each {|l| puts l }
      end
    end
  end
end

想起来一年前 @yangchenyun 在上海 Ruby Tuesday 上分享的tiny_rails_mvc 项目,时间过得好快。。

#1 楼 @ericguo 看了他的实现,我感觉我的版本有很多可以改进。不过有几点没搞懂: 1.路由是真么配置的 2.render 的时候 erb 是怎么访问 controller 的 instance 变量的

def render(name = action_name)
    return if @rendered
    template = load_template(name)
    html = template.result(binding)
    response.write html
    @rendered = true
  end

值得学习,很不错

You need to Sign in before reply, if you don't have an account, please Sign up first.