• Ruby 的实例变量的 scope at 2016年06月02日

    比较容易理解一点的方法就是你要知道实例变量是定义在 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 是 #<0x0000000790cd90> 也就是 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

  • #1 楼 @pathbox 项目的历史遗留问题吧,rails 自带的 sqlite3 一直没动,线上使用了 mysql 这样。对于简单的信息比如姓名、电话地址啥的直接都用 text,这么合适吗?

  • #2 楼 @martin91 更好一些的做法可以是放在 lib 下的文件夹中,然后在 config/initilizers 手动引用,这样看起来比较清晰

  • 可以部署成功后,自动访问一把

  • Rails cookie for path at 2015年09月22日

    好吧,可能问的问题太简答了, 我自己来回答吧。顺便说一下对于 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 存储机制

    说完配置,那么 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 安全的分隔开了。