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

Constant Summary collapse

INVALID_XML_REGEX =

Invalid XML characters

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

Regex to validate and find entity names

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

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PropertiesDSL

class_props, default_values, entity, find_property_for_xml_name, missing_props, optional_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.

Raises:

  • (ArgumentError)

See Also:



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

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



161
162
163
# File 'lib/diaspora_federation/entity.rb', line 161

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.

Raises:

See Also:



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

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

See Also:



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

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.



193
194
195
# File 'lib/diaspora_federation/entity.rb', line 193

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



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

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.



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

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.



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

def to_h
  enriched_properties.map {|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
  }.to_h
end

#to_jsonHash

Renders entity to a hash representation of the entity JSON format



172
173
174
175
176
177
# File 'lib/diaspora_federation/entity.rb', line 172

def to_json
  {
    entity_type: self.class.entity_name,
    entity_data: json_data
  }
end

#to_sString



166
167
168
# File 'lib/diaspora_federation/entity.rb', line 166

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

See Also:

  • Nokogiri::XML::Node.to_xml


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

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