summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorStefan Daschek <[email protected]>2013-07-08 16:36:42 +0200
committerStefan Daschek <[email protected]>2013-07-08 16:36:42 +0200
commite5e79814670569838621e67249927c104006bcca (patch)
tree011363a114926d71aaa15c995c6ea132440c5ba6
parent2a4c39f7ae863ac7e4d14e0110aba11fce4a822f (diff)
downloadcaxlsx-e5e79814670569838621e67249927c104006bcca.tar.gz
caxlsx-e5e79814670569838621e67249927c104006bcca.zip
Implement Relationship.clear_cached_instances, use it before serializing the package.
This is necessary to make serialization idempotent (i.e. make sure that Relationship instances are generated with the same IDs everytime the package is serialized). It also fixes a memory leak if Axlsx is used in a long running server process (eg a Rails app).
-rw-r--r--lib/axlsx/package.rb2
-rw-r--r--lib/axlsx/rels/relationship.rb18
-rw-r--r--test/tc_package.rb11
3 files changed, 30 insertions, 1 deletions
diff --git a/lib/axlsx/package.rb b/lib/axlsx/package.rb
index a78b5f5d..0a1bceaa 100644
--- a/lib/axlsx/package.rb
+++ b/lib/axlsx/package.rb
@@ -100,6 +100,7 @@ module Axlsx
# File.open('example_streamed.xlsx', 'w') { |f| f.write(s.read) }
def serialize(output, confirm_valid=false)
return false unless !confirm_valid || self.validate.empty?
+ Relationship.clear_cached_instances
Zip::ZipOutputStream.open(output) do |zip|
write_parts(zip)
end
@@ -112,6 +113,7 @@ module Axlsx
# @return [StringIO|Boolean] False if confirm_valid and validation errors exist. rewound string IO if not.
def to_stream(confirm_valid=false)
return false unless !confirm_valid || self.validate.empty?
+ Relationship.clear_cached_instances
zip = write_parts(Zip::ZipOutputStream.new("streamed", true))
stream = zip.close_buffer
stream.rewind
diff --git a/lib/axlsx/rels/relationship.rb b/lib/axlsx/rels/relationship.rb
index 5e75a98a..a6b7bdd2 100644
--- a/lib/axlsx/rels/relationship.rb
+++ b/lib/axlsx/rels/relationship.rb
@@ -11,7 +11,23 @@ module Axlsx
@instances ||= []
end
- # Generate and return a unique id. Used for setting {#Id}.
+ # Clear cached instances.
+ #
+ # This should be called before serializing a package (see {Package#serialize} and
+ # {Package#to_stream}) to make sure that serialization is idempotent (i.e.
+ # Relationship instances are generated with the same IDs everytime the package
+ # is serialized).
+ #
+ # Also, calling this avoids memory leaks (cached instances lingering around
+ # forever).
+ def clear_cached_instances
+ @instances = []
+ end
+
+ # Generate and return a unique id (eg. `rId123`) Used for setting {#Id}.
+ #
+ # The generated id depends on the number of cached instances, so using
+ # {clear_cached_instances} will automatically reset the generated ids, too.
# @return [String]
def next_free_id
"rId#{@instances.size + 1}"
diff --git a/test/tc_package.rb b/test/tc_package.rb
index 86f11dd4..d7aa6904 100644
--- a/test/tc_package.rb
+++ b/test/tc_package.rb
@@ -133,6 +133,17 @@ class TestPackage < Test::Unit::TestCase
assert zip_content_then == zip_content_now, "zip files are not identical"
end
end
+
+ def test_serialization_creates_identical_files_for_identical_packages
+ package_1, package_2 = 2.times.map do
+ Axlsx::Package.new(created_at: Time.utc(2013, 1, 1)).tap do |p|
+ p.workbook.add_worksheet(:name => "Basic Worksheet") do |sheet|
+ sheet.add_row [1, 2, 3]
+ end
+ end
+ end
+ assert package_1.to_stream.string == package_2.to_stream.string, "zip files are not identical"
+ end
def test_validation
assert_equal(@package.validate.size, 0, @package.validate)