Here is my solution. As a side note, it was probably not the best idea to 
stick to the benchmarking code in the quiz, as it forced not the best design 
decisions (I'd rather not have default constructor for Rope, and for sure - 
no normalizing 'in place'). But - what's done is done.
For slice variants I've decided to have two argument ones in #slice, and one 
arg  - in #[]

Results:
String

Build:   0.188000   0.032000   0.220000 (  0.219000)
 Sort:   1.578000   0.843000   2.421000 (  2.422000)

Rope

Build:   0.031000   0.000000   0.031000 (  0.031000)
 Sort:   0.203000   0.000000   0.203000 (  0.234000)

Code:
-------------------------------------------------------------------------------------------------------------------------
class NilClass
  def length; 0; end
end

class String
  def shift
    return nil if empty?
    res=self[0]
    self[0]=""
    res
  end
end

class Rope
  attr_reader :length, :left, :right

  def initialize(left=nil,right=nil)
    @left=left if left
    @right=right if right
    @length=left.length+right.length
  end

  def append(what)
    len=what.length
    if (len>0)
      @left=self.dup if @right.length>0
      @right=what
      @length+=len
    end
    self
  end

  alias << append

  def prepend(what)
    len=what.length
    if (len>0)
      @right=self.dup if @left.length>0
      @left=what
      @length+=len
    end
    self
  end

  def to_s
    @left.to_s+@right.to_s
  end

  def [](i)
    return i.match(self.to_s)[0] if i.kind_of? Regexp
    if i.kind_of? Range
      pos,last=i.first,i.last
      pos=@length+pos if pos<0
      last=@length+last if last<0
      return nil if pos<0 || last<0
      return slice(pos,last-pos+1)
    end
    i=@length+i if i<0
    return nil if i<0 || i>@length-1
    llen=@left.length
    i<llen ? @left[i] : @right[i-llen]
  end

  def []=(i,val)
    #fixnum only
    i=@length+i if i<0
    ""[i]=0 if i<0 || i>@length-1
    @length+=val.length-1
    llen=@left.length
    i<llen ? @left[i]=val : @right[i-llen]=val
  end

  def slice(pos,len)
    return pos.match(self.to_s)[len] if pos.kind_of? Regexp
    pos=@length+pos if pos<0
    return nil if pos<0 || len<0 || pos>@length-1
    llen=@left.length
    return @left.slice(pos,len) if pos+len<=llen
    return @right.slice(pos-llen, len) if pos>=llen
    Rope.new(@left.slice(pos,len),@right.slice(0,len+pos-llen))
  end

  def shift
    return nil if @length==0
    @length-=1
    res=@left.length>0 ? @left.shift : @right.shift
    @left=nil if @left.length==0
    @right=nil if @right.length==0
    res
  end

  def normalize
    r=Rebalancer.new(@length)
    self.traverse { |str| r.append(str) }
    @left,@right=r.get_ropes
  end

  def traverse(&blck)
    @left.kind_of?(String) ? yield(@left) : @left.traverse(&blck) if @left
    @right.kind_of?(String) ? yield(@right) : @right.traverse(&blck) if 
@right
  end

end

class Rebalancer
  def initialize len
    @limits=[1,2]
    @slots=[]
    n=2
    @limits<<n=@limits[-2]+@limits[-1] while n<len
  end

  def append str
    @slots[0]=@slots[0] ? Rope.new(@slots[0],str) : str
    i=0
    while @slots[i].length>@limits[i]
      @slots[i+1]=@slots[i+1] ? Rope.new(@slots[i+1],@slots[i]) : @slots[i]
      @slots[i]=nil
      i+=1
    end
  end

  def get_ropes
    @slots.compact!
    (@slots.length-1).times { |i|
      @slots[i+1]=@slots[i+1] ? Rope.new(@slots[i+1],@slots[i]) : @slots[i]
      @slots[i]=nil
      i+=1
    }
    [@slots[-1].left,@slots[-1].right]
  end
end