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 }
  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, UnknownEntity, ValidationError

Constant Summary collapse

INVALID_XML_REGEX =

Invalid XML characters

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

Regex to validate and find entity names

"[a-z]*(?:_[a-z]*)*"

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PropertiesDSL

class_props, default_values, entity, missing_props, optional_props, property, resolv_aliases

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:



61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/diaspora_federation/entity.rb', line 61

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

.class_nameString

Returns class name as string.

Returns:

  • (String)

    class name as string



163
164
165
# File 'lib/diaspora_federation/entity.rb', line 163

def self.class_name
  name.rpartition("::").last
end

.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:



151
152
153
154
155
156
157
158
159
160
# File 'lib/diaspora_federation/entity.rb', line 151

def self.entity_class(entity_name)
  raise InvalidEntityName, "'#{entity_name}' is invalid" unless entity_name =~ /\A#{ENTITY_NAME_REGEX}\z/

  class_name = entity_name.sub(/\A[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:



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

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

.from_hash(properties_hash) ⇒ Entity

Creates an instance of self, filling it with data from a provided hash of properties.

The hash format is described as following:
1) Properties of the hash are representation of the entity’s class properties
2) Keys of the hash must be of Symbol type
3) Possible values of the hash properties depend on the types of the entity’s class properties
4) Basic properties, such as booleans, strings, integers and timestamps are represented by values of respective formats
5) Nested hashes and arrays of hashes are allowed to represent nested entities. Nested hashes follow the same format as the parent hash.
6) Besides, the nested entities can be passed in the hash as already instantiated objects of the respective type.

Parameters:

  • properties_hash (Hash)

    A hash of the expected format

Returns:



195
196
197
# File 'lib/diaspora_federation/entity.rb', line 195

def self.from_hash(properties_hash)
  new(properties_hash)
end

.from_json(json_hash) ⇒ Object

Creates an instance of self by parsing a hash in the format of JSON serialized object (which usually means data from a parsed JSON input).



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

def self.from_json(json_hash)
  from_hash(*json_parser_class.new(self).parse(json_hash))
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:



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

def self.from_xml(root_node)
  from_hash(*xml_parser_class.new(self).parse(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).



79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/diaspora_federation/entity.rb', line 79

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

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

#to_json(*_args) ⇒ Hash

Renders entity to a hash representation of the entity JSON format

Returns:

  • (Hash)

    Returns a hash that is equal by structure to the entity in JSON format



174
175
176
177
178
179
# File 'lib/diaspora_federation/entity.rb', line 174

def to_json(*_args)
  {
    entity_type: self.class.entity_name,
    entity_data: json_data
  }
end

#to_sString

Returns string representation of this object.

Returns:

  • (String)

    string representation of this object



168
169
170
# File 'lib/diaspora_federation/entity.rb', line 168

def to_s
  "#{self.class.class_name}#{":#{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


99
100
101
102
103
104
105
106
# File 'lib/diaspora_federation/entity.rb', line 99

def to_xml
  doc = 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