Rails 通过 path 实现 subdomain —— default_url_options 的妙用

最近在开发微信开放平台,需要通过 url 来区别不同的第三方。最简单直接的方法,就是通过 subdomain 来实现。
比如有两个第三方 A、B,那么 A 的域名为 A.example.com,B 的域名为 B.example.com。但这次由于 https 证书的问题只能通过 path 来实现,也就是 A 的域名为 www.example.com/A,B 的域名为 www.example.com/B。

但通过 path 实现会有一个严重的问题,就是如何保证生成的 url 带有第三方的信息。

1
2
3
scope path: '/:third_party' do
resources :orders
end

上面的配置使用 orders_path 时会报错 missing required keys: [:third_path]
最笨的办法莫过于给所有的 url 手动加上,但这方法随着项目增大会变得不可行。

我们希望 url 的生成可以更加智能,当访问 url 为 www.example.com/A 时,使用 orders_path 可以自动生成 /A/orders。从逻辑上,这是行得通的。

首先想到 route 可以添加 default 配置,于是做了以下尝试,可惜报错了。在 route 时只能配置静态的默认值。

1
2
3
4
# undefined local variable or method `params'
scope path: '/:third_party', default: {third_party: params[:third_party]} do
resources :orders
end

那么,有没有类似的方法,提供 url default 配置的功能呢?这时就轮到 default_url_options 出场了。
default_url_options 的用处不仅仅是在 config 时设置默认的 host,每个 Controller 都有该方法,调用 url_helper 时会结合该方法生成最终的 url。

在对应的 Controller 里添加以下代码,这时调用 orders_path 就会根据域名中的 third_parth 生成对应的域名了。

1
2
3
def default_url_options
{third_party: params[:third_party]}
end

解决了生成 url 的问题,我们还需要解决测试时 url 的问题。这里我对 ActionController::TestCase 进行了 monkey patch,供大家参考。

1
2
3
4
5
6
7
8
9
10
11
class ActionController::TestCase

alias_method :orig_process, :process

def process(action, method: "GET", params: {}, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil)
if @controller.is_a? WechatBaseController
params.merge!({app_alias: 'test'})
end
orig_process(action, method: method, params: params, session: session, body: body, flash: flash, format: format, xhr: xhr, as: as)
end
end