weshatheleopard / rubyXL

Ruby lib for reading/writing/modifying .xlsx and .xlsm files
MIT License
1.27k stars 253 forks source link

`insert_column` with existing styled cell doesn't set cell `@worksheet`, causing failure in `LegacyCell#validate_worksheet` #441

Open dmolesUC opened 1 year ago

dmolesUC commented 1 year ago

From the RubyXL::WorksheetConvenienceMethods#insert_column source code, it looks like it should:

c = RubyXL::Cell.new(:style_index => old_cell.style_index, :worksheet => self,
                     :row => row_index, :column => column_index,
                     :datatype => RubyXL::DataType::SHARED_STRING)

but AFAICT that worksheet param never gets used.

Steps to reproduce

  1. Create a new worksheet
  2. Add a cell
  3. Style the cell
  4. Insert a column at the index of the cell
  5. Get the first cell from the inserted column
  6. Call change_contents on the cell

Expected

Actual

Minimal example

#!/usr/bin/env ruby

require 'rubyXL'
require 'rubyXL/convenience_methods'

worksheet = RubyXL::Workbook.new.worksheets[0]

cell = worksheet.add_cell(0, 0, 'test 1')
cell.change_font_bold(true)
added_cell = worksheet[0][0]
added_cell_ws = added_cell.instance_variable_get(:@worksheet)

worksheet.insert_column(0)
inserted_cell = worksheet[0][0]
inserted_cell_ws = inserted_cell.instance_variable_get(:@worksheet)

# Uncomment to see the underlying problem
# puts "original worksheet:       #{worksheet || '<nil>'}"
# puts "added cell @worksheet:    #{added_cell_ws || '<nil>'}"
# puts "inserted cell @worksheet: #{inserted_cell_ws || '<nil>'}"

added_cell.change_contents('test 2')
inserted_cell.change_contents('test 3')

Raises:

/Users/david/.rvm/gems/ruby-3.1.2/gems/rubyXL-3.4.25/lib/rubyXL/cell.rb:24:in `validate_worksheet': Cell #<RubyXL::Cell:0x0000000106c01c30> is not in worksheet  (RuntimeError)
    from /Users/david/.rvm/gems/ruby-3.1.2/gems/rubyXL-3.4.25/lib/rubyXL/convenience_methods/cell.rb:4:in `change_contents'
    from /tmp/rubyxl.rb:23:in `<main>'
dmolesUC commented 1 year ago

Workaround:

module RubyXL
  class Cell
    def initialize(params = nil)
      super
      @worksheet ||= params[:worksheet] if params.respond_to?(:[])
    end
  end
end

Updated to add:

If you have multiple rows, LegacyCell#validate_worksheet will still fail even with the above, due to row not getting set, either. Updated workaround:

module RubyXL
  class Cell
    def initialize(params = nil)
      super

      if params.respond_to?(:[])
        @worksheet ||= params[:worksheet] 
        self.row ||= params[:row] # note: not an instance variable
      end
    end
  end
end