rails params[:id] 实现原理

Posted by wxianfeng Thu, 10 Feb 2011 16:36:00 GMT

环境: ruby1.9.2 + rails 3.0.3

我们知道 params 返回的是一个 hash , 例如 {"id"=>1} ,那为什么 params[:id] = 1 ,而不是 nil 呢 ?
irb下测试一下:

ruby-1.9.2-p0 > h={"id"=>1}
 => {"id"=>1} 
ruby-1.9.2-p0 > h[:id]
 => nil 

带着这个疑问,设置断点 ,debug 进rails 源码 , 发现了原因,

1,跟到了 params 方法源码:

def params
@_params ||= request.parameters
end

给 @_params 设置Watch , 发现如下 :

发现 @_params 的class 是 ActiveSupport::HashWithIndifferentAccess
也就是说

params.class # => ActiveSupport::HashWithIndifferentAccess

2,继续 F7 跟进去

跟到了这个文件 的 default方法

require 'active_support/core_ext/hash/keys'

# This class has dubious semantics and we only have it so that
# people can write params[:key] instead of params['key']
# and they get the same value for both keys.

module ActiveSupport
  class HashWithIndifferentAccess < Hash
    def extractable_options?
      true
    end

    def initialize(constructor = {})
      if constructor.is_a?(Hash)
        super()
        update(constructor)
      else
        super(constructor)
      end
    end

    def default(key = nil)
      if key.is_a?(Symbol) && include?(key = key.to_s)
        self[key]
      else
        super
      end
    end

    def self.new_from_hash_copying_default(hash)
      ActiveSupport::HashWithIndifferentAccess.new(hash).tap do |new_hash|
        new_hash.default = hash.default
      end
    end

    alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
    alias_method :regular_update, :update unless method_defined?(:regular_update)

    # Assigns a new value to the hash:
    #
    #   hash = HashWithIndifferentAccess.new
    #   hash[:key] = "value"
    #
    def []=(key, value)
      regular_writer(convert_key(key), convert_value(value))
    end

    alias_method :store, :[]=

    # Updates the instantized hash with values from the second:
    #
    #   hash_1 = HashWithIndifferentAccess.new
    #   hash_1[:key] = "value"
    #
    #   hash_2 = HashWithIndifferentAccess.new
    #   hash_2[:key] = "New Value!"
    #
    #   hash_1.update(hash_2) # => {"key"=>"New Value!"}
    #
    def update(other_hash)
      other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
      self
    end

    alias_method :merge!, :update

    # Checks the hash for a key matching the argument passed in:
    #
    #   hash = HashWithIndifferentAccess.new
    #   hash["key"] = "value"
    #   hash.key? :key  # => true
    #   hash.key? "key" # => true
    #
    def key?(key)
      super(convert_key(key))
    end

    alias_method :include?, :key?
    alias_method :has_key?, :key?
    alias_method :member?, :key?

    # Fetches the value for the specified key, same as doing hash[key]
    def fetch(key, *extras)
      super(convert_key(key), *extras)
    end

    # Returns an array of the values at the specified indices:
    #
    #   hash = HashWithIndifferentAccess.new
    #   hash[:a] = "x"
    #   hash[:b] = "y"
    #   hash.values_at("a", "b") # => ["x", "y"]
    #
    def values_at(*indices)
      indices.collect {|key| self[convert_key(key)]}
    end

    # Returns an exact copy of the hash.
    def dup
      HashWithIndifferentAccess.new(self)
    end

    # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash
    # Does not overwrite the existing hash.
    def merge(hash)
      self.dup.update(hash)
    end

    # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
    # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a HashWithDifferentAccess.
    def reverse_merge(other_hash)
      super self.class.new_from_hash_copying_default(other_hash)
    end

    def reverse_merge!(other_hash)
      replace(reverse_merge( other_hash ))
    end

    # Removes a specified key from the hash.
    def delete(key)
      super(convert_key(key))
    end

    def stringify_keys!; self end
    def stringify_keys; dup end
    undef :symbolize_keys!
    def symbolize_keys; to_hash.symbolize_keys end
    def to_options!; self end

    # Convert to a Hash with String keys.
    def to_hash
      Hash.new(default).merge!(self)
    end

    protected
      def convert_key(key)
        key.kind_of?(Symbol) ? key.to_s : key
      end

      def convert_value(value)
        case value
        when Hash
          self.class.new_from_hash_copying_default(value)
        when Array
          value.collect { |e| e.is_a?(Hash) ? self.class.new_from_hash_copying_default(e) : e }
        else
          value
        end
      end
  end
end

HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess

好了 所有 实现的原理都在 这个文件里了,HashWithIndifferentAccess是 Hash的子类,其中覆盖了default 方法,Hash当找不到 hash 的 key 时 会寻找default值,即执行 default 方法 , so ….

Hash#default 用法demo:

ruby-1.9.2-p0 > h={}
 => {} 
ruby-1.9.2-p0 > h.default=1
 => 1 
ruby-1.9.2-p0 > h[:a]
 => 1 

核心代码:

def default(key = nil)
      if key.is_a?(Symbol) && include?(key = key.to_s)
        self[key]
      else
        super
      end
end

当是symbol 时 转化为 string , 然后 self[string_key]

举一反三:

DEMO:

class MyHash < Hash

  def initialize(constructor = {})
    if constructor.is_a?(Hash)
      super()
      update(constructor) # Hash#update == Hash#merge!
    else
      super(constructor)
    end
  end

  def default(key = nil)
    p key #=> :id
    if key.is_a?(Symbol) && include?(key = key.to_s) # Hash#include? = Hash#has_key? = Hash#member? = Hash#key?
      p key #=> "id"
      self[key]
    else
      super
    end
  end
end

h = MyHash.new({"id"=>1})
p h #=> {"id"=>1}
p h.class #=> MyHash
p h[:id] #=> 1
p h["id"] #=> 1

SEE:

http://lukaszwrobel.pl/blog/ruby-hash-default-value

This entry was posted on Thu, 10 Feb 2011 16:36:00 GMT and Posted in . You can follow any any response to this entry through the Atom feed. You can leave a comment or a trackback from your own site.

Tags


Trackbacks

Use the following link to trackback from your own site:
http://wxianfeng.com/trackbacks?article_id=178

Comments

  1. Avatar
    charles almost {{count}} years ago:

    看你调试rails了,用的什么调试器?

  2. Avatar
    wxianfeng almost {{count}} years ago:

    @charles
    netbeans, 老早以前用的了, 现在不用了.

  3. Avatar
    Bari - Patras almost {{count}} years ago:

    It’s awesome in favor of me to have a web page, which iss useful in favor
    of myy experience. thanks admin

  4. Avatar
    Bari - Patras almost {{count}} years ago:

    It’s awesome in favor of me to have a web page, which is
    useful in favoir of my experience. thanks admin

  5. Avatar
    Italy - Greece almost {{count}} years ago:

    Genuinely when someone doesn’t understand hen its up to other people thwt they will
    help, so here itt occurs.

  6. Avatar
    Italy - Greece almost {{count}} years ago:

    Genuinely when someohe doesn’t understand then its up to other
    people that they will help, so here it occurs.

  7. Avatar
    Passenger ships Bari Patras almost {{count}} years ago:

    I’ve been browsing online more thann 3 hours
    today, yet I never found any interesting aeticle like yours.
    It is pretty worth enough ffor me. Personally, iff alll webmasters and bloggers made good content as you did, the web will be
    much moee useful than ever before.

  8. Avatar
    Patrice almost {{count}} years ago:

    I feel that is among the so much vital information for
    me. And i’m glad studying your article. But want to rematk on some general issues, The
    web site taste is ideal, the articles is in reality nice :
    D. Just right process, cheers

  9. Avatar
    Patrice almost {{count}} years ago:

    I feel that is among the so much vitsl inforkation for me.
    Annd i’m glad studying your article. But want to remark on some
    general issues, The web site taste is ideal, the articles is in realit nice : D.
    Just right process, cheers

  10. Avatar
    νυφικα φορεματα almost {{count}} years ago:

    I jst could not leave your website before suggesting
    that I actually loved the standard information a person provide ffor your
    visitors? Is going to be back continuously in order to
    check out new posts

  11. Avatar
    Bari - Patras over {{count}} years ago:

    It’s going to be endimg of mine day, however before ending I am reading thi enormous paragraph to improve my know-how.

Leave a comment