在Rails中动态重载路由是个坏主意吗?
我有一个正在编写的应用程序,允许管理员添加页面,类别等的别名,并且我想根据别名使用其他控制器/操作(不进行重定向,我发现render实际上并没有调用该方法,我只是渲染模板)。我已经尝试了全部捕获,但是对于引起并捕获每次抛出的DoubleRender异常,我并不感到疯狂。
我想出的解决方案是在服务器启动时动态生成路由,并在创建/更新/销毁别名时使用Alias模型的回调来重新加载路由。
这是我的routes.rb中的代码:
Alias.find(:all).each do |alias_to_add| map.connect alias_to_add.name, :controller => alias_to_add.page_type.controller, :action => alias_to_add.page_type.action, :navigation_node_id => alias_to_add.navigation_node.id end
我在Alias模型中使用回调,如下所示:
after_save :rebuild_routes after_destroy :rebuild_routes def rebuild_routes ActionController::Routing::Routes.reload! end
这违反了Rails的最佳做法吗?有更好的解决方案吗?
解决方案
我不确定我是否完全理解这个问题,但是我们可以在控制器中使用method_missing
,然后查找别名,也许像这样:
class MyController def method_missing(sym, *args) aliased = Alias.find_by_action_name(sym) # sanity check here in case no alias self.send( aliased.real_action_name ) # sanity check here in case the real action calls a different render explicitly render :action => aliased.real_action_name end def normal_action @thing = Things.find(params[:id]) end end
如果我们想优化它,可以在" method_missing"中放置一个" define_method",这样它只会在第一次调用时"丢失",并且从那时起将是一种常规方法。
快速解决方案
在routes.rb的底部有一个包罗万象的路由。在将我们路由到的操作中实现所需的任何别名查找逻辑。
在我的实现中,我有一个表,它将定义的URL映射到控制器,操作和参数哈希。我只是将它们从数据库中删除,然后调用适当的操作,然后尝试呈现该操作的默认模板。如果操作已经呈现了某些内容,则将引发DoubleRenderError,我将其捕获并忽略。
我们可以将这种技术扩展到所需的复杂程度,尽管随着它变得越来越复杂,通过调整路由或者Rails的默认路由逻辑来实现它比从本质上重新实现所有路由逻辑要有意义。
如果找不到别名,则可以根据需要抛出404或者500错误。
注意事项:
缓存:不知道URL是先验的,可以使页面缓存成为绝对负担。请记住,它基于提供的URI进行缓存,而不是基于url_for(:action_you_actually_executed)。这意味着如果我们使用别名
/foo_action/bar_method
到
/some-wonderful-alias
我们将在缓存目录中找到some-wonderful-alias.html。而且,当我们尝试清除foo的栏时,除非明确指定文件,否则不会清除该文件。
容错能力:请检查并确保没有人误认为现有路由。我们可以通过将所有别名强制进入一个"目录"来轻松地做到这一点,而该目录在其他情况下是不可路由的(在这种情况下,别名在文本上是唯一的,足以确保它们永不冲突),但这并不是最大的期望我可以想到的一些应用程序的解决方案。
首先,正如其他人所建议的那样,在routes.rb的底部创建一个包罗万象的路由:
map.connect ':name', :controller => 'aliases', :action => 'show'
然后,在AliasesController中,可以使用render_component来呈现别名动作:
class AliasesController < ApplicationController def show if alias = Alias.find_by_name(params[:name]) render_component(:controller => alias.page_type.controller, :action => alias.page_type.action, :navigation_node_id => alias.navigation_node.id) else render :file => "#{RAILS_ROOT}/public/404.html", :status => :not_found end end end
本
我发现我们已经使用的方法是最好的。使用Rails 3,我们必须将代码稍作更改,以:
MyNewApplication::Application.reload_routes!
就这样。