在rails controller中使用DSL,并自动生成路由

ruby

为了实现如下效果,写rails的时候不再重复的写路由,今天决定搞一点事

核心代码 app/controllers/concerns/api_defined.rb:

module ApiDefined
  extend ActiveSupport::Concern
  included do
    mattr_accessor :ns, :request_param, :basepath
    @@ns = name.deconstantize.split("::").map(&:underscore).join('/')
    self.extend ClassMethods
  end

  module ClassMethods
    %w[get post put delete].each do |http_method|
      define_method http_method do |method_name, path: nil, param: nil, on_collection: true, on_member: nil, &blk|
        on_collection = !on_member unless on_member.nil?
        define_method method_name, &blk
        path = path || get_path(http_method, method_name, param, on_collection)
        $routes << {method: 'get', path: path, action: "#{name.deconstantize.split("::").map(&:underscore).join('/')}/#{controller_name}##{method_name}"}
      end
    end

    def get_path http_method, method_name, param, on_collection
      path = basepath || "#{ns}/#{controller_name}"
      on_collection = false if param
      if http_method == 'get'
        if method_name == 'show'
          on_collection, method_name = true, nil
        elsif method_name == 'index'
          method_name = nil
        end
      elsif http_method == 'put' && method_name == 'update'
        on_collection, method_name = true, nil
      elsif http_method == 'delete' && method_name == 'destroy'
        on_collection, method_name = true, nil
      end
      path += "/:#{request_param || param.presence || 'id'}" unless on_collection
      path += "/#{method_name}" if method_name
      path
    end

    def namespace value
      self.ns = value
    end

    def request_mapping value
      self.basepath = value
    end

    def param value
      self.request_param = value
    end
  end

  def valid! param_name, *args, &blk
    param! param_name, *args, &blk
    self.singleton_class.mattr_accessor param_name
    self.send("#{param_name}=", params[param_name])
  end
end

在 ApplicationController 中 include ApiDefined

增加 config/initializers/routes.rb

$routes = []

增加一个module,加载controller所有类(用于在开发环境reload代码的时候,重载路由,防止懒加载的问题)

# app/controllers/init.rb
module Pro::V2::Init end

# 注意这里只会加载app/controllers/目录下的controller
# 如果要加载子目录下的,需要替换为app/controllers/*/*,并且要注意module
Dir["#{Rails.root}/app/controllers/*"].each do |filename|
  klass_name = File.basename(filename, '.rb').classify
  begin
    klass_name.constantize
  rescue LoadError, NameError
    nil
  end
end

在 config/application.rb中增加几行代码

    config.to_prepare do
      $routes = []
      Init
      Rails.application.reload_routes!
    end

发表于 2021.07.01