为什么比较冻结的宝石版本失败?

时间:2019-07-03 15:30:03

标签: ruby rubygems

我正在用Ruby写一些需要比较版本以便确定是否需要更新的东西。

但是当我运行current_version <=> desired_version并冻结了至少一个版本时,我得到:

    4: from .../ruby/2.6.0/rubygems/version.rb:344:in `<=>'
    3: from .../ruby/2.6.0/rubygems/version.rb:371:in `canonical_segments'
    2: from .../ruby/2.6.0/rubygems/version.rb:393:in `_split_segments'
    1: from .../ruby/2.6.0/rubygems/version.rb:387:in `_segments'
FrozenError (can't modify frozen Gem::Version)

根据the docs,源代码是这样的:

def <=>(other)
  return unless Gem::Version === other
  return 0 if @version == other._version || canonical_segments == other.canonical_segments

  lhsegments = _segments
  rhsegments = other._segments

  lhsize = lhsegments.size
  rhsize = rhsegments.size
  limit  = (lhsize > rhsize ? lhsize : rhsize) - 1

  i = 0

  while i <= limit
    lhs, rhs = lhsegments[i] || 0, rhsegments[i] || 0
    i += 1

    next      if lhs == rhs
    return -1 if String  === lhs && Numeric === rhs
    return  1 if Numeric === lhs && String  === rhs

    return lhs <=> rhs
  end

  return 0
end

我不明白为什么这段代码会改变宝石的状态。有什么我想念的吗?

1 个答案:

答案 0 :(得分:1)

该错误告诉您在哪里:<=>方法调用canonical_segments,后者调用_split_segments,后者调用_segments。因此,那是必须发生突变的地方;不直接使用您在帖子中复制的方法。

更具体地说,here's the offending source code

  def canonical_segments
    @canonical_segments ||=
      _split_segments.map! do |segments|
        segments.reverse_each.drop_while {|s| s == 0 }.reverse
      end.reduce(&:concat)
  end

  protected

  def _version
    @version
  end

  def _segments
    # segments is lazy so it can pick up version values that come from
    # old marshaled versions, which don't go through marshal_load.
    # since this version object is cached in @@all, its @segments should be frozen

    @segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
      /^\d+$/ =~ s ? s.to_i : s
    end.freeze
  end

  def _split_segments
    string_start = _segments.index {|s| s.is_a?(String) }
    string_segments  = segments
    numeric_segments = string_segments.slice!(0, string_start || string_segments.size)
    return numeric_segments, string_segments
  end