Rails 4:如何以DRY方式为routes.rb中包含点的字符串参数定义约束?

Rails 4: How to define a constraint for string param containing dots in routes.rb the DRY way?

如何绘制以 IPv4 地址为参数的路由。就像这样:

http://localhost:3000/ip/192.168.2.2

仅当 request.params[:ipaddress] 是 IPv4 地址时才应使用此路由,但我得到的只是:No route matches [GET] "/ip/192.168.2.2"


第一次尝试:constraints: { ipaddress: /^regexp$/ }

.config/routes.rb 中定义内联约束表达式,如下所示:

resources :ipaddresses, path: :ip, param: :ipaddress, constraints: {
  ipaddress: /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ }, only: :show

导致错误,服务器无法启动:

/home/mschmidt/.rvm/gems/ruby-2.2.3/gems/actionpack-4.2.4/lib/action_dispatch/routing/mapper.rb:207:in

`verify_regexp_requirement': Regexp anchor characters are not allowed in routing requirements


第二次尝试:没有锚字符

如果我删除锚字符,服务器确实会启动,但约束没有表达想要的内容。该字符串现在可以包含 一个或多个 个 IPv4 地址。


第三次尝试:将其移至函数

因此,我使用名为 matches? 的方法将表达式放入 class 中。如果参数没有任何点,这就完美了。不幸的是,所有这些 IP 都有一些点,我又回到了起点:

No route matches [GET] "/ip/192.168.2.2"

/config/routes.rb:

Rails.application.routes.draw do
  class IPv4Constraint
    def matches?(request)
      request.params[:ipaddress] =~ /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
    end 
  end 
  
  resources :ipaddresses, path: :ip, param: :ipaddress, constraints: IPv4Constraint.new, only: :show
end

因为这些点,我必须在 routes.rb 中定义一个约束,而且我认为将更多代码放入控制器中不会很枯燥。做这个的最好方式是什么?有没有办法用一个表达式来做到这一点?

我现在要使用 routing-filter

将 gem 添加到 Gemfile 后: gem 'routing-filter'

我添加了一个新过滤器:

# The IPv4Address filter extracts segments matching /ip/:IPv4Address from the
# end of the recognized url and exposes the page parameter as
# params[:ipv4address]. When a url is generated the filter adds the address to
# the url accordingly if the page parameter is passed to the url helper.
#
#   incoming url: /ip/192.168.2.2
#   filtered url: /ip
#   generated url: /ip/192.168.2.2
#
# You can install the filter like this:
#
#   # in config/routes.rb
#   Rails.application.routes.draw do
#     filter :ipv4address
#   end
module RoutingFilter
  class Ipv4address < Filter
    IPV4ADDRESS_PATTERN = %r(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)

    def around_recognize(path, env, &block)
      pattern = pattern_builder(path)
      ipv4address = extract_segment!(pattern, path)
      yield.tap do |params|
        params[:ipv4address] = ipv4address if ipv4address
      end
    end

    def around_generate(params, &block)
      ipv4address = params.delete(:ipv4address)
      yield.tap do |result|
        append_segment!(result, "ip/#{ipv4address}") if append_ipv4address?(ipv4address)
      end
    end

    private

    def pattern_builder(path)
      ipv4address = extract_ipv4address(path)
      build = "/(#{ipv4address.gsub('.','\.')})/?$"
      /#{build}/
    end

    def extract_ipv4address(path)
      path.match(IPV4ADDRESS_PATTERN).to_s
    end

    def append_ipv4address?(ipv4address)
      ipv4address && !ipv4address.blank?
    end
  end
end

在 routes.rb 中调用它并将操作从 show 更改为 index

filter :ipv4address

resources :ipaddresses, path: :ip, only: :index

IP 现在在我的控制器中可用 params[:ipv4address]