Rails Rails 5.2.3 cattr_accessor default value 的坑

tinyfeng · 2020年03月25日 · 最后由 lolychee 回复于 2020年03月25日 · 2551 次阅读

今天写了这么一段代码,发现只要类定义过的 cattr_accessor 的任意一个发生改变,所有都变了

后面翻了cattr_accessor源码,发现是真的坑,在send("#{sym}=", sym_default_value) 这个地方,并没有对原对象进行拷贝,导致所有的默认值是同一个对象....

<<方法其实是对最初的对象Array.new进行操作....

class module
  def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
    mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, &blk)
    mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor, default: default)
  end
  alias :cattr_accessor :mattr_accessor

  def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil)
    syms.each do |sym|
      raise NameError.new("invalid attribute name: #{sym}") unless /\A[_A-Za-z]\w*\z/.match?(sym)
      class_eval(<<-EOS, __FILE__, __LINE__ + 1)
        @@#{sym} = nil unless defined? @@#{sym}

        def self.#{sym}=(obj)
          @@#{sym} = obj
        end
      EOS

      if instance_writer && instance_accessor
        class_eval(<<-EOS, __FILE__, __LINE__ + 1)
          def #{sym}=(obj)
            @@#{sym} = obj
          end
        EOS
      end

      sym_default_value = (block_given? && default.nil?) ? yield : default
      send("#{sym}=", sym_default_value) unless sym_default_value.nil?
    end
  end
  alias :cattr_writer :mattr_writer
end

可这也怪不到 rails 身上,cattr_accessor支持传 block,可以解决引用同一对象的问题。

class Test
  cattr_accessor(:a, :b) { [] }

  # 但实际上常用的是这样
  cattr_accessor :a, default: []
  cattr_accessor :b, default: []

end

ruby 传参都是引用,这是要时刻注意的。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号