refactoring xml generation

also refactoring `xml_name`
This commit is contained in:
Benjamin Neff 2016-02-17 23:25:26 +01:00
parent e4cdb7e7a9
commit 823db3ee18
5 changed files with 52 additions and 82 deletions

View file

@ -75,7 +75,7 @@ module DiasporaFederation
# Returns a Hash representing this Entity (attributes => values) # Returns a Hash representing this Entity (attributes => values)
# @return [Hash] entity data (mostly equal to the hash used for initialization). # @return [Hash] entity data (mostly equal to the hash used for initialization).
def to_h def to_h
self.class.class_prop_names.each_with_object({}) do |prop, hash| self.class.class_props.keys.each_with_object({}) do |prop, hash|
hash[prop] = public_send(prop) hash[prop] = public_send(prop)
end end
end end
@ -103,26 +103,22 @@ module DiasporaFederation
private private
def setable?(name, val) def setable?(name, val)
prop_def = self.class.class_props.find {|p| p[:name] == name } type = self.class.class_props[name]
return false if prop_def.nil? # property undefined return false if type.nil? # property undefined
setable_string?(prop_def, val) || setable_nested?(prop_def, val) || setable_multi?(prop_def, val) setable_string?(type, val) || setable_nested?(type, val) || setable_multi?(type, val)
end end
def setable_string?(definition, val) def setable_string?(type, val)
(definition[:type] == String && val.respond_to?(:to_s)) type == String && val.respond_to?(:to_s)
end end
def setable_nested?(definition, val) def setable_nested?(type, val)
t = definition[:type] type.is_a?(Class) && type.ancestors.include?(Entity) && val.is_a?(Entity)
(t.is_a?(Class) && t.ancestors.include?(Entity) && val.is_a?(Entity))
end end
def setable_multi?(definition, val) def setable_multi?(type, val)
t = definition[:type] type.instance_of?(Array) && val.instance_of?(Array) && val.all? {|v| v.instance_of?(type.first) }
(t.instance_of?(Array) &&
val.instance_of?(Array) &&
val.all? {|v| v.instance_of?(t.first) })
end end
def nilify(value) def nilify(value)
@ -146,35 +142,36 @@ module DiasporaFederation
"Failed validation for properties: #{errors.join(' | ')}" "Failed validation for properties: #{errors.join(' | ')}"
end end
def xml_elements
Hash[to_h.map {|name, value| [name, self.class.class_props[name] == String ? value.to_s : value] }]
end
# Serialize the Entity into XML elements # Serialize the Entity into XML elements
# @return [Nokogiri::XML::Element] root node # @return [Nokogiri::XML::Element] root node
def entity_xml def entity_xml
doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new) doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new)
Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element| Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element|
self.class.class_props.each do |prop_def| xml_elements.each do |name, value|
add_property_to_xml(doc, prop_def, root_element) add_property_to_xml(doc, root_element, name, value)
end end
end end
end end
def add_property_to_xml(doc, prop_def, root_element) def add_property_to_xml(doc, root_element, name, value)
property = prop_def[:name] if value.is_a? String
type = prop_def[:type] root_element << simple_node(doc, name, value)
if type == String
root_element << simple_node(doc, prop_def[:xml_name], property)
else else
# call #to_xml for each item and append to root # call #to_xml for each item and append to root
[*public_send(property)].compact.each do |item| [*value].compact.each do |item|
root_element << item.to_xml root_element << item.to_xml
end end
end end
end end
# create simple node, fill it with text and append to root # create simple node, fill it with text and append to root
def simple_node(doc, name, property) def simple_node(doc, name, value)
Nokogiri::XML::Element.new(name.to_s, doc).tap do |node| Nokogiri::XML::Element.new(self.class.xml_names[name].to_s, doc).tap do |node|
data = public_send(property).to_s node.content = value unless value.empty?
node.content = data unless data.empty?
end end
end end

View file

@ -11,9 +11,9 @@ module DiasporaFederation
# entity :nested, NestedEntity # entity :nested, NestedEntity
# entity :multiple, [OtherEntity] # entity :multiple, [OtherEntity]
module PropertiesDSL module PropertiesDSL
# @return [Array<Hash>] hash of declared entity properties # @return [Hash] hash of declared entity properties
def class_props def class_props
@class_props ||= [] @class_props ||= {}
end end
# Define a generic (string-type) property # Define a generic (string-type) property
@ -43,7 +43,7 @@ module DiasporaFederation
# Return array of missing required property names # Return array of missing required property names
# @return [Array<Symbol>] missing required property names # @return [Array<Symbol>] missing required property names
def missing_props(args) def missing_props(args)
class_prop_names - default_props.keys - args.keys class_props.keys - default_props.keys - args.keys
end end
# Return a new hash of default values, with dynamic values # Return a new hash of default values, with dynamic values
@ -55,18 +55,6 @@ module DiasporaFederation
} }
end end
# Returns all nested Entities
# @return [Array<Hash>] nested properties
def nested_class_props
@nested_class_props ||= class_props.select {|p| p[:type] != String }
end
# Returns all property names
# @return [Array] property names
def class_prop_names
@class_prop_names ||= class_props.map {|p| p[:name] }
end
# @param [Hash] data entity data # @param [Hash] data entity data
# @return [Hash] hash with resolved aliases # @return [Hash] hash with resolved aliases
def resolv_aliases(data) def resolv_aliases(data)
@ -81,15 +69,22 @@ module DiasporaFederation
}] }]
end end
# @return [Symbol] alias for the xml-generation/parsing
# @deprecated
def xml_names
@xml_names ||= {}
end
# finds a property by +xml_name+ or +name+ # finds a property by +xml_name+ or +name+
# @param [String] xml_name name of the property from the received xml # @param [String] xml_name name of the property from the received xml
# @return [Hash] the property data # @return [Hash] the property data
def find_property_for_xml_name(xml_name) def find_property_for_xml_name(xml_name)
class_props.find {|prop| prop[:xml_name].to_s == xml_name || prop[:name].to_s == xml_name } class_props.keys.find {|name| name.to_s == xml_name || xml_names[name].to_s == xml_name }
end end
private private
# @deprecated
def determine_xml_name(name, type, opts={}) def determine_xml_name(name, type, opts={})
raise ArgumentError, "xml_name is not supported for nested entities" if type != String && opts.has_key?(:xml_name) raise ArgumentError, "xml_name is not supported for nested entities" if type != String && opts.has_key?(:xml_name)
@ -110,8 +105,9 @@ module DiasporaFederation
def define_property(name, type, opts={}) def define_property(name, type, opts={})
raise InvalidName unless name_valid?(name) raise InvalidName unless name_valid?(name)
class_props << {name: name, xml_name: determine_xml_name(name, type, opts), type: type} class_props[name] = type
default_props[name] = opts[:default] if opts.has_key? :default default_props[name] = opts[:default] if opts.has_key? :default
xml_names[name] = determine_xml_name(name, type, opts)
instance_eval { attr_reader name } instance_eval { attr_reader name }

View file

@ -95,7 +95,7 @@ module DiasporaFederation
property = klass.find_property_for_xml_name(xml_name) property = klass.find_property_for_xml_name(xml_name)
if property if property
entity_data[property[:name]] = parse_element_from_node(property[:type], xml_name, root_node) entity_data[property] = parse_element_from_node(klass.class_props[property], xml_name, root_node)
else else
additional_xml_elements[xml_name] = child.text additional_xml_elements[xml_name] = child.text
end end

View file

@ -7,9 +7,8 @@ module DiasporaFederation
dsl.property :test dsl.property :test
properties = dsl.class_props properties = dsl.class_props
expect(properties).to have(1).item expect(properties).to have(1).item
expect(properties.first[:name]).to eq(:test) expect(properties[:test]).to eq(String)
expect(properties.first[:xml_name]).to eq(:test) expect(dsl.xml_names[:test]).to eq(:test)
expect(properties.first[:type]).to eq(String)
end end
it "will not accept other types for names" do it "will not accept other types for names" do
@ -26,18 +25,16 @@ module DiasporaFederation
dsl.property :zzzz dsl.property :zzzz
properties = dsl.class_props properties = dsl.class_props
expect(properties).to have(3).items expect(properties).to have(3).items
expect(properties.map {|e| e[:name] }).to include(:test, :asdf, :zzzz) expect(properties.keys).to include(:test, :asdf, :zzzz)
expect(properties.map {|e| e[:xml_name] }).to include(:test, :asdf, :zzzz) properties.values.each {|type| expect(type).to eq(String) }
properties.each {|e| expect(e[:type]).to eq(String) }
end end
it "can add an xml name to simple properties with a symbol" do it "can add an xml name to simple properties with a symbol" do
dsl.property :test, xml_name: :xml_test dsl.property :test, xml_name: :xml_test
properties = dsl.class_props properties = dsl.class_props
expect(properties).to have(1).item expect(properties).to have(1).item
expect(properties.first[:name]).to eq(:test) expect(properties[:test]).to eq(String)
expect(properties.first[:xml_name]).to eq(:xml_test) expect(dsl.xml_names[:test]).to eq(:xml_test)
expect(properties.first[:type]).to eq(String)
end end
it "will not accept other types for xml names" do it "will not accept other types for xml names" do
@ -51,24 +48,22 @@ module DiasporaFederation
context "nested entities" do context "nested entities" do
it "gets included in the properties" do it "gets included in the properties" do
expect(Entities::TestNestedEntity.class_prop_names).to include(:test, :multi) expect(Entities::TestNestedEntity.class_props.keys).to include(:test, :multi)
end end
it "can define nested entities" do it "can define nested entities" do
dsl.entity :other, Entities::TestEntity dsl.entity :other, Entities::TestEntity
properties = dsl.class_props properties = dsl.class_props
expect(properties).to have(1).item expect(properties).to have(1).item
expect(properties.first[:name]).to eq(:other) expect(properties[:other]).to eq(Entities::TestEntity)
expect(properties.first[:type]).to eq(Entities::TestEntity)
end end
it "can define an array of a nested entity" do it "can define an array of a nested entity" do
dsl.entity :other, [Entities::TestEntity] dsl.entity :other, [Entities::TestEntity]
properties = dsl.class_props properties = dsl.class_props
expect(properties).to have(1).item expect(properties).to have(1).item
expect(properties.first[:name]).to eq(:other) expect(properties[:other]).to be_an_instance_of(Array)
expect(properties.first[:type]).to be_an_instance_of(Array) expect(properties[:other].first).to eq(Entities::TestEntity)
expect(properties.first[:type].first).to eq(Entities::TestEntity)
end end
it "must be an entity subclass" do it "must be an entity subclass" do
@ -108,22 +103,6 @@ module DiasporaFederation
end end
end end
describe ".nested_class_props" do
it "returns the definition of nested class properties in an array" do
n_props = Entities::TestNestedEntity.nested_class_props
expect(n_props).to be_an_instance_of(Array)
expect(n_props.map {|p| p[:name] }).to include(:test, :multi)
expect(n_props.map {|p| p[:type] }).to include(Entities::TestEntity, [Entities::OtherEntity])
end
end
describe ".class_prop_names" do
it "returns the names of all class props in an array" do
expect(Entities::TestDefaultEntity.class_prop_names).to be_an_instance_of(Array)
expect(Entities::TestDefaultEntity.class_prop_names).to include(:test1, :test2, :test3, :test4)
end
end
describe ".resolv_aliases" do describe ".resolv_aliases" do
it "resolves the defined alias" do it "resolves the defined alias" do
dsl.property :test, alias: :test_alias dsl.property :test, alias: :test_alias
@ -156,12 +135,12 @@ module DiasporaFederation
describe ".find_property_for_xml_name" do describe ".find_property_for_xml_name" do
it "finds property by xml_name" do it "finds property by xml_name" do
dsl.property :test, xml_name: :xml_test dsl.property :test, xml_name: :xml_test
expect(dsl.find_property_for_xml_name("xml_test")).to eq(dsl.class_props.first) expect(dsl.find_property_for_xml_name("xml_test")).to eq(:test)
end end
it "finds property by name" do it "finds property by name" do
dsl.property :test, xml_name: :xml_test dsl.property :test, xml_name: :xml_test
expect(dsl.find_property_for_xml_name("test")).to eq(dsl.class_props.first) expect(dsl.find_property_for_xml_name("test")).to eq(:test)
end end
it "returns nil if property is not defined" do it "returns nil if property is not defined" do

View file

@ -4,7 +4,7 @@ shared_examples "an Entity subclass" do
end end
it "has its properties set" do it "has its properties set" do
expect(described_class.class_prop_names).to include(*data.keys) expect(described_class.class_props.keys).to include(*data.keys)
end end
context "behaviour" do context "behaviour" do
@ -51,10 +51,8 @@ shared_examples "an XML Entity" do
end end
def check_entity(entity, parsed_entity) def check_entity(entity, parsed_entity)
entity.class.class_props.each do |prop_def| entity.class.class_props.each do |name, type|
name = prop_def[:name] validate_values(entity.send(name), parsed_entity.send(name), type)
validate_values(entity.send(name), parsed_entity.send(name), prop_def[:type])
end end
end end