diff --git a/lib/lutaml/model/serialize.rb b/lib/lutaml/model/serialize.rb index f7c97c0..6c1ee3f 100644 --- a/lib/lutaml/model/serialize.rb +++ b/lib/lutaml/model/serialize.rb @@ -85,7 +85,7 @@ def attribute(name, type, options = {}) Lutaml::Model::Config::AVAILABLE_FORMATS.each do |format| define_method(format) do |&block| klass = format == :xml ? XmlMapping : KeyValueMapping - mappings[format] = klass.new + mappings[format] ||= klass.new mappings[format].instance_eval(&block) if format == :xml && !mappings[format].root_element diff --git a/spec/lutaml/model/delegation_spec.rb b/spec/lutaml/model/delegation_spec.rb index 50d56c1..5461484 100644 --- a/spec/lutaml/model/delegation_spec.rb +++ b/spec/lutaml/model/delegation_spec.rb @@ -145,34 +145,41 @@ class Ceramic < Lutaml::Model::Serializable expect(xml_data).to include('') end - it "sets the default namespace of " do + it "sets the namespace of a particular element inside Ceramic" do Delegation::Ceramic.class_eval do xml do root "delegation" - namespace "https://example.com/delegation/1.2" - map_element "type", to: :type + map_element "type", + to: :type, + namespace: "https://example.com/type/1.2", + prefix: "type" map_element "color", to: :color, delegate: :glaze map_element "finish", to: :finish, delegate: :glaze end end delegation_class = Delegation::Ceramic + delegation = delegation_class.from_yaml(yaml_data) xml_data = delegation.to_xml( pretty: true, declaration: true, encoding: "UTF-8", ) - expect(xml_data).to( - include(''), - ) + expect(xml_data).to include('') + expect(xml_data).to include("Vase") end - it "sets the namespace of with a prefix" do + it "sets the namespace of a particular attribute inside " do Delegation::Ceramic.class_eval do + attribute :date, Lutaml::Model::Type::Date + xml do root "delegation" - namespace "https://example.com/delegation/1.2", "del" + map_attribute "date", + to: :date, + namespace: "https://example.com/delegation/1.2", + prefix: "del" map_element "type", to: :type map_element "color", to: :color, delegate: :glaze map_element "finish", to: :finish, delegate: :glaze @@ -180,52 +187,58 @@ class Ceramic < Lutaml::Model::Serializable end delegation_class = Delegation::Ceramic - delegation = delegation_class.from_yaml(yaml_data) + delegation = delegation_class.new( + type: "Vase", + glaze: Delegation::Glaze.new( + color: "Blue", + finish: "Glossy", + ), + date: "2024-06-08", + ) + xml_data = delegation.to_xml( pretty: true, declaration: true, encoding: "UTF-8", ) - expect(xml_data).to( - include( - '', - ), - ) + delegation_attributes = [ + 'xmlns:del="https://example.com/delegation/1.2"', + 'del:date="2024-06-08"', + ] + + expect(xml_data).to include("") end - it "sets the namespace of a particular element inside Ceramic" do + it "sets the default namespace of " do Delegation::Ceramic.class_eval do xml do root "delegation" - map_element "type", - to: :type, - namespace: "https://example.com/type/1.2", - prefix: "type" + namespace "https://example.com/delegation/1.2" + map_element "type", to: :type map_element "color", to: :color, delegate: :glaze map_element "finish", to: :finish, delegate: :glaze end end delegation_class = Delegation::Ceramic - delegation = delegation_class.from_yaml(yaml_data) xml_data = delegation.to_xml( pretty: true, declaration: true, encoding: "UTF-8", ) - expect(xml_data).to include('') - expect(xml_data).to include("Vase") + expect(xml_data).to( + include(''), + ) end - it "sets the namespace of and also" \ - "a particular element inside using :inherit" do + it "sets the namespace of with a prefix" do Delegation::Ceramic.class_eval do xml do root "delegation" namespace "https://example.com/delegation/1.2", "del" - map_element "type", to: :type # , namespace: :inherit + map_element "type", to: :type map_element "color", to: :color, delegate: :glaze map_element "finish", to: :finish, delegate: :glaze end @@ -239,50 +252,37 @@ class Ceramic < Lutaml::Model::Serializable encoding: "UTF-8", ) - delegation_attribute = 'xmlns:del="https://example.com/delegation/1.2">' - - expect(xml_data).to include("Vase") + expect(xml_data).to( + include( + '', + ), + ) end - it "sets the namespace of a particular attribute inside " do + it "sets the namespace of and also" \ + "a particular element inside using :inherit" do Delegation::Ceramic.class_eval do - attribute :date, Lutaml::Model::Type::Date - xml do root "delegation" - map_attribute "date", - to: :date, - namespace: "https://example.com/delegation/1.2", - prefix: "del" - map_element "type", to: :type + namespace "https://example.com/delegation/1.2", "del" + map_element "type", to: :type # , namespace: :inherit map_element "color", to: :color, delegate: :glaze map_element "finish", to: :finish, delegate: :glaze end end delegation_class = Delegation::Ceramic - delegation = delegation_class.new( - type: "Vase", - glaze: Delegation::Glaze.new( - color: "Blue", - finish: "Glossy", - ), - date: "2024-06-08", - ) - + delegation = delegation_class.from_yaml(yaml_data) xml_data = delegation.to_xml( pretty: true, declaration: true, encoding: "UTF-8", ) - delegation_attributes = [ - 'xmlns:del="https://example.com/delegation/1.2"', - 'del:date="2024-06-08"', - ] + delegation_attribute = 'xmlns:del="https://example.com/delegation/1.2">' - expect(xml_data).to include("") + expect(xml_data).to include("Vase") end it "sets the namespace of and also" \ diff --git a/spec/lutaml/model/inheritance_spec.rb b/spec/lutaml/model/inheritance_spec.rb index f7bc85e..be7a950 100644 --- a/spec/lutaml/model/inheritance_spec.rb +++ b/spec/lutaml/model/inheritance_spec.rb @@ -1,30 +1,34 @@ require "spec_helper" require "lutaml/model" -class Parent < Lutaml::Model::Serializable - attribute :text, Lutaml::Model::Type::String - attribute :id, Lutaml::Model::Type::String - attribute :name, Lutaml::Model::Type::String -end - -class Child < Parent - attribute :age, Lutaml::Model::Type::Integer - - xml do - root "child" +module InheritanceSpec + class Parent < Lutaml::Model::Serializable + attribute :text, Lutaml::Model::Type::String + attribute :id, Lutaml::Model::Type::String + attribute :name, Lutaml::Model::Type::String + + xml do + map_content to: :text + + map_attribute "id", to: :id + map_element "name", to: :name + end + end - map_content to: :text + class Child < Parent + attribute :age, Lutaml::Model::Type::Integer - map_attribute "id", to: :id + xml do + root "child" - map_element "age", to: :age - map_element "name", to: :name + map_element "age", to: :age + end end end RSpec.describe "Inheritance" do subject(:child_object) do - Child.new( + InheritanceSpec::Child.new( { text: "Some text", name: "John Doe", @@ -35,7 +39,7 @@ class Child < Parent end let(:expected_xml) do - '30John DoeSome text' + 'John Doe30Some text' end it "uses parent attributes" do diff --git a/spec/lutaml/model/serializable_spec.rb b/spec/lutaml/model/serializable_spec.rb index 132cbe8..9b7c8fc 100644 --- a/spec/lutaml/model/serializable_spec.rb +++ b/spec/lutaml/model/serializable_spec.rb @@ -1,76 +1,78 @@ -class TestModel - attr_accessor :name, :age +module SerializeableSpec + class TestModel + attr_accessor :name, :age - def initialize(name: nil, age: nil) - @name = name - @age = age + def initialize(name: nil, age: nil) + @name = name + @age = age + end end -end -class TestModelMapper < Lutaml::Model::Serializable - model TestModel + class TestModelMapper < Lutaml::Model::Serializable + model TestModel - attribute :name, Lutaml::Model::Type::String - attribute :age, Lutaml::Model::Type::String -end + attribute :name, Lutaml::Model::Type::String + attribute :age, Lutaml::Model::Type::String + end -class TestMapper < Lutaml::Model::Serializable - attribute :name, Lutaml::Model::Type::String - attribute :age, Lutaml::Model::Type::String + class TestMapper < Lutaml::Model::Serializable + attribute :name, Lutaml::Model::Type::String + attribute :age, Lutaml::Model::Type::String - yaml do - map :na, to: :name - map :ag, to: :age + yaml do + map :na, to: :name + map :ag, to: :age + end end -end -### XML root mapping + ### XML root mapping -class RecordDate < Lutaml::Model::Serializable - attribute :content, :string + class RecordDate < Lutaml::Model::Serializable + attribute :content, :string - xml do - root "recordDate" - map_content to: :content + xml do + root "recordDate" + map_content to: :content + end end -end -class OriginInfo < Lutaml::Model::Serializable - attribute :date_issued, RecordDate, collection: true + class OriginInfo < Lutaml::Model::Serializable + attribute :date_issued, RecordDate, collection: true - xml do - root "originInfo" - map_element "dateIssued", to: :date_issued + xml do + root "originInfo" + map_element "dateIssued", to: :date_issued + end end -end -### Enumeration + ### Enumeration -class Ceramic < Lutaml::Model::Serializable - attribute :type, :string - attribute :firing_temperature, :integer -end + class Ceramic < Lutaml::Model::Serializable + attribute :type, :string + attribute :firing_temperature, :integer + end -class CeramicCollection < Lutaml::Model::Serializable - attribute :featured_piece, - Ceramic, - values: [ - Ceramic.new(type: "Porcelain", firing_temperature: 1300), - Ceramic.new(type: "Stoneware", firing_temperature: 1200), - Ceramic.new(type: "Earthenware", firing_temperature: 1000), - ] -end + class CeramicCollection < Lutaml::Model::Serializable + attribute :featured_piece, + Ceramic, + values: [ + Ceramic.new(type: "Porcelain", firing_temperature: 1300), + Ceramic.new(type: "Stoneware", firing_temperature: 1200), + Ceramic.new(type: "Earthenware", firing_temperature: 1000), + ] + end -class GlazeTechnique < Lutaml::Model::Serializable - attribute :name, :string, values: ["Celadon", "Raku", "Majolica"] + class GlazeTechnique < Lutaml::Model::Serializable + attribute :name, :string, values: ["Celadon", "Raku", "Majolica"] + end end RSpec.describe Lutaml::Model::Serializable do describe ".model" do it "sets the model for the class" do - expect { described_class.model(TestModel) }.to change(described_class, :model) + expect { described_class.model(SerializeableSpec::TestModel) }.to change(described_class, :model) .from(nil) - .to(TestModel) + .to(SerializeableSpec::TestModel) end end @@ -78,22 +80,17 @@ class GlazeTechnique < Lutaml::Model::Serializable subject(:mapper) { described_class.new } it "adds the attribute and getter setter for that attribute" do - expect do - described_class.attribute("foo", Lutaml::Model::Type::String) - end.to change { - described_class.attributes.keys - }.from([]).to(["foo"]).and change { - mapper.respond_to?(:foo) - }.from(false).to(true).and change { - mapper.respond_to?(:foo=) - }.from(false).to(true) + expect { described_class.attribute("foo", Lutaml::Model::Type::String) } + .to change { described_class.attributes.keys }.from([]).to(["foo"]) + .and change { mapper.respond_to?(:foo) }.from(false).to(true) + .and change { mapper.respond_to?(:foo=) }.from(false).to(true) end end describe ".hash_representation" do context "when model is separate" do let(:instance) do - TestModel.new(name: "John", age: 18) + SerializeableSpec::TestModel.new(name: "John", age: 18) end let(:expected_hash) do @@ -104,14 +101,14 @@ class GlazeTechnique < Lutaml::Model::Serializable end it "return hash representation" do - generate_hash = TestModelMapper.hash_representation(instance, :yaml) + generate_hash = SerializeableSpec::TestModelMapper.hash_representation(instance, :yaml) expect(generate_hash).to eq(expected_hash) end end context "when model is self" do let(:instance) do - TestMapper.new(name: "John", age: 18) + SerializeableSpec::TestMapper.new(name: "John", age: 18) end let(:expected_hash) do @@ -122,7 +119,7 @@ class GlazeTechnique < Lutaml::Model::Serializable end it "return hash representation" do - generate_hash = TestMapper.hash_representation(instance, :yaml) + generate_hash = SerializeableSpec::TestMapper.hash_representation(instance, :yaml) expect(generate_hash).to eq(expected_hash) end end @@ -131,7 +128,7 @@ class GlazeTechnique < Lutaml::Model::Serializable describe ".mappings_for" do context "when mapping is defined" do it "returns the defined mapping" do - actual_mappings = TestMapper.mappings_for(:yaml).mappings + actual_mappings = SerializeableSpec::TestMapper.mappings_for(:yaml).mappings expect(actual_mappings[0].name).to eq(:na) expect(actual_mappings[0].to).to eq(:name) @@ -143,9 +140,9 @@ class GlazeTechnique < Lutaml::Model::Serializable context "when mapping is not defined" do it "maps attributes to mappings" do - allow(TestMapper.mappings).to receive(:[]).with(:yaml).and_return(nil) + allow(SerializeableSpec::TestMapper.mappings).to receive(:[]).with(:yaml).and_return(nil) - actual_mappings = TestMapper.mappings_for(:yaml).mappings + actual_mappings = SerializeableSpec::TestMapper.mappings_for(:yaml).mappings expect(actual_mappings[0].name).to eq("name") expect(actual_mappings[0].to).to eq(:name) @@ -204,13 +201,13 @@ class GlazeTechnique < Lutaml::Model::Serializable describe "XML root name override" do it "uses root name defined at the component class" do - record_date = RecordDate.new(content: "2021-01-01") + record_date = SerializeableSpec::RecordDate.new(content: "2021-01-01") expected_xml = "2021-01-01" expect(record_date.to_xml).to eq(expected_xml) end it "uses mapped element name at the aggregating class, overriding root name" do - origin_info = OriginInfo.new(date_issued: [RecordDate.new(content: "2021-01-01")]) + origin_info = SerializeableSpec::OriginInfo.new(date_issued: [SerializeableSpec::RecordDate.new(content: "2021-01-01")]) expected_xml = <<~XML 2021-01-01 XML @@ -221,7 +218,7 @@ class GlazeTechnique < Lutaml::Model::Serializable describe "String enumeration" do context "when assigning an invalid value" do it "raises an error after creation after validate" do - glaze = GlazeTechnique.new(name: "Celadon") + glaze = SerializeableSpec::GlazeTechnique.new(name: "Celadon") glaze.name = "Tenmoku" expect do glaze.validate! @@ -234,13 +231,13 @@ class GlazeTechnique < Lutaml::Model::Serializable context "when assigning a valid value" do it "changes the value after creation" do - glaze = GlazeTechnique.new(name: "Celadon") + glaze = SerializeableSpec::GlazeTechnique.new(name: "Celadon") glaze.name = "Raku" expect(glaze.name).to eq("Raku") end it "assigns the value during creation" do - glaze = GlazeTechnique.new(name: "Majolica") + glaze = SerializeableSpec::GlazeTechnique.new(name: "Majolica") expect(glaze.name).to eq("Majolica") end end @@ -249,7 +246,7 @@ class GlazeTechnique < Lutaml::Model::Serializable describe "Serializable object enumeration" do context "when assigning an invalid value" do it "raises ValidationError containing InvalidValueError after creation" do - glaze = GlazeTechnique.new(name: "Celadon") + glaze = SerializeableSpec::GlazeTechnique.new(name: "Celadon") glaze.name = "Tenmoku" expect do glaze.validate! @@ -261,7 +258,7 @@ class GlazeTechnique < Lutaml::Model::Serializable it "raises ValidationError containing InvalidValueError during creation" do expect do - GlazeTechnique.new(name: "Crystalline").validate! + SerializeableSpec::GlazeTechnique.new(name: "Crystalline").validate! end.to raise_error(Lutaml::Model::ValidationError) do |error| expect(error).to include(Lutaml::Model::InvalidValueError) expect(error.error_messages).to include(a_string_matching(/name is `Crystalline`, must be one of the following/)) @@ -271,19 +268,19 @@ class GlazeTechnique < Lutaml::Model::Serializable context "when assigning a valid value" do it "changes the value after creation" do - collection = CeramicCollection.new( - featured_piece: Ceramic.new(type: "Porcelain", - firing_temperature: 1300), + collection = SerializeableSpec::CeramicCollection.new( + featured_piece: SerializeableSpec::Ceramic.new(type: "Porcelain", + firing_temperature: 1300), ) - collection.featured_piece = Ceramic.new(type: "Stoneware", - firing_temperature: 1200) + collection.featured_piece = SerializeableSpec::Ceramic.new(type: "Stoneware", + firing_temperature: 1200) expect(collection.featured_piece.type).to eq("Stoneware") end it "assigns the value during creation" do - collection = CeramicCollection.new( - featured_piece: Ceramic.new(type: "Earthenware", - firing_temperature: 1000), + collection = SerializeableSpec::CeramicCollection.new( + featured_piece: SerializeableSpec::Ceramic.new(type: "Earthenware", + firing_temperature: 1000), ) expect(collection.featured_piece.type).to eq("Earthenware") end