Class: DiasporaFederation::Entity Abstract

Inherits:
Object
  • Object
show all
Extended by:
PropertiesDSL
Includes:
Logging
Defined in:
lib/diaspora_federation/entity.rb

Overview

This class is abstract.

Subclass and specify properties to implement various entities.

Note:

Entity properties can only be set during initialization, after that the entity instance becomes frozen and must not be modified anymore. Instances are intended to be immutable data containers, only.

Entity is the base class for all other objects used to encapsulate data for federation messages in the diaspora* network. Entity fields are specified using a simple DSL as part of the class definition.

Any entity also provides the means to serialize itself and all nested entities to XML (for deserialization from XML to Entity instances, see Salmon::XmlPayload).

Examples:

Entity subclass definition

class MyEntity < Entity
  property :prop
  property :optional, default: false
  property :dynamic_default, default: -> { Time.now }
  property :another_prop, xml_name: :another_name
  entity :nested, NestedEntity
  entity :multiple, [OtherEntity]
end

Entity instantiation

nentity = NestedEntity.new
oe1 = OtherEntity.new
oe2 = OtherEntity.new

entity = MyEntity.new(prop: 'some property',
                      nested: nentity,
                      multiple: [oe1, oe2])

Defined Under Namespace

Classes: InvalidEntityName, InvalidRootNode, UnknownEntity, ValidationError

Constant Summary collapse

INVALID_XML_REGEX =

Invalid XML characters

/[^\x09\x0A\x0D\x20-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PropertiesDSL

class_props, default_values, entity, find_property_for_xml_name, missing_props, property, resolv_aliases, xml_names

Methods included from Logging

included

Constructor Details

#initialize(data) ⇒ Entity

Note:

Attributes not defined as part of the class definition (PropertiesDSL#property, PropertiesDSL#entity) get discarded silently.

Initializes the Entity with the given attribute hash and freezes the created instance it returns.

After creation, the entity is validated against a Validator, if one is defined. The Validator needs to be in the Validators namespace and named like “<EntityName>Validator”. Only valid entities can be created.

Parameters:

  • data (Hash)

    entity data

Raises:

  • (ArgumentError)

See Also:



57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/diaspora_federation/entity.rb', line 57

def initialize(data)
  logger.debug "create entity #{self.class} with data: #{data}"
  raise ArgumentError, "expected a Hash" unless data.is_a?(Hash)

  entity_data = self.class.resolv_aliases(data)
  validate_missing_props(entity_data)

  self.class.default_values.merge(entity_data).each do |name, value|
    instance_variable_set("@#{name}", instantiate_nested(name, nilify(value))) if setable?(name, value)
  end

  freeze
  validate
end

Class Method Details

.entity_class(entity_name) ⇒ Class

Transform the given String from the lowercase underscored version to a camelized variant and returns the Class constant.

Parameters:

  • entity_name (String)

    “snake_case” class name

Returns:

  • (Class)

    entity class

Raises:

See Also:



137
138
139
140
141
142
143
144
145
# File 'lib/diaspora_federation/entity.rb', line 137

def self.entity_class(entity_name)
  raise InvalidEntityName, "'#{entity_name}' is invalid" unless entity_name =~ /^[a-z]*(_[a-z]*)*$/
  class_name = entity_name.sub(/^[a-z]/, &:upcase)
  class_name.gsub!(/_([a-z])/) { Regexp.last_match[1].upcase }

  raise UnknownEntity, "'#{class_name}' not found" unless Entities.const_defined?(class_name)

  Entities.const_get(class_name)
end

.entity_nameString

Makes an underscored, lowercase form of the class name

Returns:

  • (String)

    entity name

See Also:



123
124
125
126
127
128
# File 'lib/diaspora_federation/entity.rb', line 123

def self.entity_name
  name.rpartition("::").last.tap do |word|
    word.gsub!(/(.)([A-Z])/, '\1_\2')
    word.downcase!
  end
end

.from_xml(root_node) ⇒ Entity

Construct a new instance of the given Entity and populate the properties with the attributes found in the XML. Works recursively on nested Entities and Arrays thereof.

Parameters:

  • root_node (Nokogiri::XML::Element)

    xml nodes

Returns:

Raises:

  • (ArgumentError)


111
112
113
114
115
116
# File 'lib/diaspora_federation/entity.rb', line 111

def self.from_xml(root_node)
  raise ArgumentError, "only Nokogiri::XML::Element allowed" unless root_node.instance_of?(Nokogiri::XML::Element)
  raise InvalidRootNode, "'#{root_node.name}' can't be parsed by #{name}" unless root_node.name == entity_name

  populate_entity(root_node)
end

Instance Method Details

#to_hHash

Returns a Hash representing this Entity (attributes => values). Nested entities are also converted to a Hash.

Returns:

  • (Hash)

    entity data (mostly equal to the hash used for initialization).



75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/diaspora_federation/entity.rb', line 75

def to_h
  properties.map {|key, value|
    type = self.class.class_props[key]

    if type == String || value.nil?
      [key, value]
    elsif type.instance_of?(Class)
      [key, value.to_h]
    elsif type.instance_of?(Array)
      [key, value.map(&:to_h)]
    end
  }.to_h
end

#to_sString

Returns string representation of this object.

Returns:

  • (String)

    string representation of this object



148
149
150
# File 'lib/diaspora_federation/entity.rb', line 148

def to_s
  "#{self.class.name.rpartition('::').last}#{":#{guid}" if respond_to?(:guid)}"
end

#to_xmlNokogiri::XML::Element

Returns the XML representation for this entity constructed out of Nokogiri::XML::Elements

Returns:

  • (Nokogiri::XML::Element)

    root element containing properties as child elements

See Also:

  • Nokogiri::XML::Node.to_xml
  • XmlPayload#pack


96
97
98
99
100
101
102
103
# File 'lib/diaspora_federation/entity.rb', line 96

def to_xml
  doc = Nokogiri::XML::DocumentFragment.new(Nokogiri::XML::Document.new)
  Nokogiri::XML::Element.new(self.class.entity_name, doc).tap do |root_element|
    xml_elements.each do |name, value|
      add_property_to_xml(doc, root_element, name, value)
    end
  end
end