From 5513ceba79de947d6c6efb6f381193e1110432d2 Mon Sep 17 00:00:00 2001 From: Julian Kornberger Date: Mon, 18 Mar 2024 17:17:00 +0100 Subject: [PATCH] Fix thread safety issues closes #3 --- lib/xrechnung.rb | 8 ++--- lib/xrechnung/invoice_line.rb | 2 +- lib/xrechnung/legal_monetary_total.rb | 6 ++-- lib/xrechnung/member_container.rb | 45 +++++++++++---------------- spec/xrechnung_spec.rb | 9 ++++++ 5 files changed, 35 insertions(+), 35 deletions(-) diff --git a/lib/xrechnung.rb b/lib/xrechnung.rb index c848f30..ef9070d 100644 --- a/lib/xrechnung.rb +++ b/lib/xrechnung.rb @@ -270,18 +270,18 @@ def to_xml(indent: 2, target: "") xml.cbc :TaxCurrencyCode, tax_currency_code xml.cbc :BuyerReference, buyer_reference - unless members[:invoice_period][:optional] && invoice_period.nil? + unless self.class.members[:invoice_period].optional && invoice_period.nil? invoice_period&.to_xml(xml) end xml.cac :OrderReference do xml.cbc :ID, purchase_order_reference - unless members[:sales_order_reference][:optional] && sales_order_reference.nil? + unless self.class.members[:sales_order_reference].optional && sales_order_reference.nil? xml.cbc :SalesOrderID, sales_order_reference end end - unless members[:billing_reference][:optional] && billing_reference.nil? + unless self.class.members[:billing_reference].optional && billing_reference.nil? xml.cac :BillingReference do billing_reference&.to_xml(xml) end @@ -303,7 +303,7 @@ def to_xml(indent: 2, target: "") accounting_customer_party&.to_xml(xml) end - unless members[:tax_representative_party][:optional] && tax_representative_party.nil? + unless self.class.members[:tax_representative_party].optional && tax_representative_party.nil? xml.cac :TaxRepresentativeParty do tax_representative_party&.to_xml(xml) end diff --git a/lib/xrechnung/invoice_line.rb b/lib/xrechnung/invoice_line.rb index 10cb172..74cc6a4 100644 --- a/lib/xrechnung/invoice_line.rb +++ b/lib/xrechnung/invoice_line.rb @@ -40,7 +40,7 @@ def to_xml(xml) xml.cbc :InvoicedQuantity, invoiced_quantity.amount_to_s, unitCode: invoiced_quantity.unit_code xml.cbc :LineExtensionAmount, *line_extension_amount.xml_args - unless members[:invoice_period][:optional] && invoice_period.nil? + unless self.class.members[:invoice_period].optional && invoice_period.nil? invoice_period&.to_xml(xml) end item&.to_xml(xml) diff --git a/lib/xrechnung/legal_monetary_total.rb b/lib/xrechnung/legal_monetary_total.rb index 3523bc6..f6b4038 100644 --- a/lib/xrechnung/legal_monetary_total.rb +++ b/lib/xrechnung/legal_monetary_total.rb @@ -40,10 +40,10 @@ class LegalMonetaryTotal # noinspection RubyResolve def to_xml(xml) - members.each do |member, _options| - next if self[member].nil? + self.class.members.each_key do |name| + next if self[name].nil? - xml.cbc :"#{member.to_s.split("_").map(&:capitalize).join}", *self[member].xml_args + xml.cbc :"#{name.to_s.split("_").map(&:capitalize).join}", *self[name].xml_args end xml.target! end diff --git a/lib/xrechnung/member_container.rb b/lib/xrechnung/member_container.rb index 579d645..312a7e0 100644 --- a/lib/xrechnung/member_container.rb +++ b/lib/xrechnung/member_container.rb @@ -6,8 +6,9 @@ def self.included(base) end def initialize(**kwargs) - self.class.after_initialize.each do |block| - instance_eval(&block) + # initialize default values + self.class.members.each do |name, member| + self[name] = member.default.dup unless member.default.nil? end kwargs.each do |k, v| @@ -15,54 +16,44 @@ def initialize(**kwargs) end end - def members - self.class.instance_variable_get :@members - end - def [](key) send(key) end def []=(key, value) - send(members[key].fetch(:setter_name), value) + send("#{key}=", value) end + Member = Struct.new(:type, :default, :optional, :transform_value, keyword_init: true) + module ClassMethods + def members + @members + end + # @param [String] member_name # @param [Array, Class] type # @param [Object] default # @param [TrueClass, FalseClass] optional When true, omits tag rather than rendering an empty tag on nil # @param [Proc] transform_value A Proc which is called with the input value to perform type conversion. - def member(member_name, type: nil, default: nil, optional: false, transform_value: nil) + def member(member_name, **kwargs) attr_reader member_name - setter_name = :"#{member_name}=" - @members[member_name] = { optional: optional, setter_name: setter_name } + kwargs[:default].freeze - if default - after_initialize do - send(setter_name, default) - end - end + setter_name = :"#{member_name}=" + member = Member.new(**kwargs) + @members[member_name] = member define_method setter_name do |in_value| - in_value = transform_value.call(in_value) if transform_value + in_value = member.transform_value.call(in_value) if member.transform_value - if type && !in_value.nil? && Array(type).none? { |t| in_value.is_a?(t) } - raise ArgumentError, "expected #{type} for :#{member_name}, got: #{in_value.class}" + if member.type && !in_value.nil? && Array(member.type).none? { |t| in_value.is_a?(t) } + raise ArgumentError, "expected #{member.type} for :#{member_name}, got: #{in_value.class}" end instance_variable_set :"@#{member_name}", in_value end end - - def after_initialize(&block) - @after_initialize_blocks ||= [] - if block - @after_initialize_blocks << block - else - @after_initialize_blocks - end - end end end end diff --git a/spec/xrechnung_spec.rb b/spec/xrechnung_spec.rb index 8d11f6d..e6f7fcd 100644 --- a/spec/xrechnung_spec.rb +++ b/spec/xrechnung_spec.rb @@ -136,4 +136,13 @@ it "sets defaults" do expect(doc.to_xml).to include "380" end + + it "thread safe initializer" do + doc1 = ::Xrechnung::Document.new + doc2 = ::Xrechnung::Document.new + + doc1.invoice_lines << Xrechnung::InvoiceLine.new + + expect(doc2.invoice_lines).to be_empty # Fails - doc1 and doc2 have the same invoice lines + end end