比较容易理解一点的方法就是你要知道实例变量是定义在 self 上的,所以想搞清楚这两个@i有什么区别首先要知道这两个地方的 self 有什么区别。尝试如下:
class A
@i = "hi"
puts "First place: self is #{self}, @i is #{@i}."
# 实例方法
def say
puts "Second place: self is #{self}, @i is #{@i}."
end
# 类方法,类似于java的static方法
def self.say
puts "Third place: self is #{self}, @i is #{@i}."
end
end
a = A.new
a.say
A.say
结果如下:
First place: self is A, @i is hi.
=> :say
[68] pry(main)>
[69] pry(main)> a = A.new
=> #<A:0x0000000790cd90>
[70] pry(main)> a.say
Second place: self is #<A:0x0000000790cd90>, @i is .
=> nil
[71] pry(main)> A.say
Third place: self is A, @i is hi.
=> nil
这里可以很清楚的看到第一个@i定义的时候 self 是 A,所以第一个实例变量 i 是定义在 A 上的;第二个地方 self 是# 也就是 A 的一个匿名类(可以理解为实例 a),这个时候调用定义在该匿名类(可以理解为实例 a)上的实例变量 i,明显未定义过,所以为 nil;第三个地方 self 是 A,调用定义在 A 上的实例变量 i,故而有值。
除了 self 的概念,ruby 中还有当前类的概念,掌握了这两个概念,这些问题都可以迎刃而解。
最后,推荐你看一下 ruby 元编程。
#3 楼 @pathbox 都用了 mysql,但是这个问题依然存在。对于数据库中的类型,比如 String 一类的 varchar 等等,在数据库一侧是有长度限制的,但是 ActiveRecord 并没有验证长度直接 commit 到数据库,然后数据库报错。这种问题目前还没有找到比较好的方法,比较可行的方法就是在 Model 中对于每一个字段进行数据库长度验证,而这个繁琐的工作最好是有一个通用库来统一处理,目前找到了https://github.com/rubiety/validates_lengths_from_database
#4 楼 @tesla_lee 在 model 里面加 validate 是可行的,但是一个比较麻烦的问题是,你几乎要给应用中所有 model 的几乎所有字段加上这个 validation,这个工作量令人恶心。不过刚好看到了一个 gem 自动帮我们做了这件事情,觉得很棒,大家可以看一下http://codecolossus.com/blog/2011/01/01/validates-lengths-from-database.html#.V1ARLlWqpBc
可以部署成功后,自动访问一把
好吧,可能问的问题太简答了,我自己来回答吧。顺便说一下对于 rails 用 cookie 存储 session 的理解,如有不对,请及时纠正。
正如很多人所说,相比其它策略,使用 cookie 来存储 session 是最简单的,只需要配置一个地方,而且很多情况下还是自动配置好的。
在 /config/initializers
中有一个叫 session_store.rb
的文件。这也就是唯一需要配置的地方。
Rails.application.config.session_store :CookieStore, { key: 'my_site', path: '/', domain: nil }, :expire_after => 30.minutes
这里我们配置了使用 cookie 存储 session;cookie 的 key 是 my_site
,路径是/
(也就是对整个网站有效);30 分钟有效期
说完配置,那么 session 究竟是怎样存储被存储到 cookie 中的呢?
我们先看一段代码
class CookieStore < Rack::Session::Abstract::Persisted
...
def set_cookie(request, session_id, cookie)
cookie_jar(request)[@key] = cookie
end
def get_cookie(req)
cookie_jar(req)[@key]
end
...
这是 Rails 代码中 CookieStore
代码的片段。set_cookie
发生在请求快要结束的时候,会将新的 cookie 值写入这次请求当中。@key
这个实例变量里面存储的就是我们之前在配置文件/config/initializers/session_store.rb
中指定的 key。
而get_cookie
发送在请求的早期,当服务器收到请求会从 request 中尝试去除@key
对应的 cookie 值。
那么你一定会好奇了,cookie 值里面存储了什么内容呢?
class CookieStore < Rack::Session::Abstract::Persisted
...
def load_session(req)
stale_session_check! do
data = unpacked_cookie_data(req)
data = persistent_session_id!(data)
[data["session_id"], data]
end
end
private
def unpacked_cookie_data(req)
req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
v = stale_session_check! do
if data = get_cookie(req)
data.stringify_keys!
end
data || {}
end
req.set_header k, v
end
end
def persistent_session_id!(data, sid=nil)
data ||= {}
data["session_id"] ||= sid || generate_sid
data
end
def write_session(req, sid, session_data, options)
session_data["session_id"] = sid
session_data
end
...
这里我们可以看到 CookieStore 将 session_id 写入了我们的 cookie 值当中。每次加载 session 的时候会从 request 附带的 cookie 值中取出 session_id 重建 session。从实际调试上面一段代码得到的结果看,CookieStore 不仅将 session_id 放在了 cookie 值中,一同被放入的还有你在代码里面设置的所有 session 键值对。
这样看起来 CookieStore 的操作方式岂不是很危险?不用担心,CookieStore 虽然将所有 session 内容存储到了 cookie 并放在客户端保管,但是它也对这些内容进行了加密处理,所以安全方面倒还过得去。
好了,聊完这么多,我们还是回归我们的主题吧,如何在一个 Rails 应用中根据其链接的第一级子目录来分隔 session 呢?要分隔 session 也就是对于第一级子目录不同的微站要使用不同的 cookie 存储。而 Rails 多 cookie 存储方面在网上没有找到什么资料。我们可以做如下处理:
首先,我们要基于 CookieStore 定制我们自己的 SampleCookieStore,代码如下:
class ActionDispatch::Session::MyCookieStore < ActionDispatch::Session::CookieStore
private
def set_cookie(request, session_id, cookie)
cookie[:path] = context_path(request)
cookie_jar(request)[context_key(request)] = cookie
end
def get_cookie(req)
cookie_jar(req)[context_key(req)]
end
def context_path(req)
# 从request获取第一级子目录对应的相对路径,主要用作cookie的path
end
def context_key(req)
# 从request获取第一级子目录对应的key,这里主要用来区分不同微站的cookie,防止冲突
end
end
然后再 SessionStore 的配置文件里面指定使用我们自己的 SampleCookieStore 即可
Rails.application.config.session_store :SampleCookieStore, { key: 'my_site', path: '/', domain: nil }, :expire_after => 30.minutes
这样就可以把各个微站的 session 安全的分隔开了。