今天在Metal中用到了读取当前登录用户current_user的方法,于是找到了下面这篇文章,虽然是09年的,但是价值依旧在,翻译一下,以备后用
Rails Metal使用指南
Posted by mikong on February 03, 2009
当我在使用Rails Metal写程序的时,才
发现我已经被Rails带来的便利所宠坏了,没有了controller和view
helpers,简直太痛苦了。希望这个指南能给您带来更好的体验。
在这个指南中,我们会写一个Widget Refresher Metal的小程序,假设我们的rails工程中的widget页面访问次数非常多,并且我们想充分发挥使用Metal的优势。那么在我们的工程的 app/metal 目录下,创建 refresher.rb
class Refresher < Rails::Rack::Metal
def self.call(env)
refresher = RefresherApp.new
refresher.call(env)
end
end
class RefresherApp
def call(env)
# refresh widget path: /widgets/:id/refresh
if env["PATH_INFO"] =~ /^\/widgets\/(\d+)\/refresh/
widget_id = $1
prepare(env, widget_id)
refresh
else
[404, { "Content-Type" => "text/html" }, "Not Found"]
end
end
# to setup the environment
def prepare(env, widget_id)
...
end
# the heart of our Metal app
def refresh
...
end
end
相比较把所有的代码都写在Refresher类(继承自Rails::Rack::Metal)里面,我更喜欢创建一个单独的RefresherApp类。当你的Metal程序已经不再是
平凡简单的HelloWorld的时候,需要一大串相互调用的方法, 因为这个call方法在这个Metal程序中是一个类方法,把所有的代码放在一个类里面将意味着所有的这些方法都将是一些类方法。但是我觉得那样太丑陋了。如果你想,尽管把这些代码放在一个类里面。如果真的
打算这么做,你可以设置上下文为self的,这样就没有必要在每一个方法前面加一个“self.”了。
class Refresher < Rails::Rack::Metal
# the methods in here are class methods
class << self
def call(env)
...
end
def method
...
end
end
end
另外,在开发Metal程序的时候需要注意,为确保你的代码变化生效
你需要持续的
重启你的服务。.
Request和Session
你可以通过下面这些代码,访问request以及它里面的参数
request = Rack::Request.new(env)
params = request.params
params['mykey'] # String keys, so not params[:mykey]
正如你所看到的,这些 keys 是字符串实例,而不是Symbol。
对于session则可以这样得到
session = env['rack.session']
你可以吧所有的这些代码一移动到prepare方法中。此外,我们可以设置params[:id](如果你愿意你可以使用Symbol),我们的主refresh方法中,看起来像是一个rails的controller。通过session我们可以得到当前用户。同样我们可以像在rails controller中写方法一样去定义其他方法。正如下面所示:
attr_reader :request, :session, :current_user
def params
@request.params
end
def logged_in?
!!current_user
end
def prepare(env, widget_id)
@request = Rack::Request.new(env)
params[:id] = widget_id
@session = env["rack.session"]
@current_user = session[:user_id] ? User.find(session[:user_id]) : false
end
做了这些准备工作,我们可以开始写这个叫做refresh的主方法了
refresh 和 ActiveRecord
假设,在客户端,我们只需要返回widget 的status属性
def refresh
@widget = Widget.find(params[:id])
return [200, { "Content-Type" => "text/html" }, @widget.status]
end
只要确保格式正确,你同样可以给客户端返回
javascript或者其他类型的内容。另外可以添加简单的检查用户是否登录的校验
def refresh
@widget = Widget.find(params[:id])
if logged_in?
return [200, { "Content-Type" => "text/javascript" }, "Element.update('status', '#{@widget.status}');"]
else
return [200, { "Content-Type" => "text/javascript" }, "Element.update('message', 'Must be logged in for widget status to refresh');"]
end
end
但是在返回一个比较复杂的javascript的时候,最好还是避免回车和引号,否则在浏览器这边我们会
解析错误,rails提供了一个叫做escape_javascript的方法,但是Metal程序中默认情况下是不能访问的。
View Helpers
要想在Metal程序中使用helper,只需要引用(include)你所需要的modules的就可以了
include ActionView::Helpers::JavascriptHelper # so escape_javascript works
include WidgetsHelper # for example
但是我更倾向于避免使用这些帮助方法
Request Forgery Protection
伪请求防护
如果不是get请求的request,你需要辨别authenticity token的真假
可以实现的一种方法:
def refresh
# before everything else
return redirect_to_widgets_response unless verified_request?
# everything else
...
end
def redirect_to_widgets_response
return [302, { "Content-Type" => "text/html", "Location" => "/widgets" },
"<html><body><a href=\"/widgets\">Redirecting...</a></body></html>"]
end
# Based on Rails method of the same name but simplified, i.e. no need to check if:
# - protection is disabled
# - request method is :post
# - format is verifiable
def verified_request?
form_authenticity_token == params['authenticity_token']
end
def form_authenticity_token
session[:_csrf_token] ||= ActiveSupport::SecureRandom.base64(32)
end
更多的挑战
在写自己的Rail Metal程序的时候,你可能会遇到其他的一些挑战,我已经尝试直接使用ERB来渲染一个模板,但感觉还是不要在这里献丑了。但是我已经尽力了。不是所有的业务逻辑可以这么快的专程Metal程序。简单的事情建议这么做,否则就太得不偿失了。不管怎样,希望这篇指南可以帮你解决
一些问题。当然,如果你有什么好的建议可以提。
------------
欢迎指正建议,谢谢
原文地址
http://devblog.michaelgalero.com/2009/02/03/guide-to-rails-metal/