Class: Shep::Entity
- Inherits:
-
Object
- Object
- Shep::Entity
- Defined in:
- lib/shep/entity_base.rb
Overview
Abstract base class for Mastodon objects.
Mastodon provides its content as JSON hashes with documented names and values. Shep takes this one step further and provides a class for each object type. These are similar to Ruby’s ‘Struct` but are also strongly typed.
Typing is primarily useful for converting things that don’t have explicit JSON types (e.g. Time, URI) into Ruby types. However, it will also catch the case where you’re trying to set a field to something with the wrong type.
Supported types are:
-
Number - (Integer but also allows Float)
-
Boolean
-
String
-
URI - (a Ruby URI object)
-
Time - parsed from and converted to ISO8601-format strings
-
Entity - an arbitrary Entity subclass
-
Array - strongly typed array of any of the above types
Fields may also be set to nil, except for ‘Array` which must instead be set to an ampty array.
Entities can be converted to and from Ruby Hashes. For this, we provide two flavours of Hash: the regular Ruby Hash where values are just the Ruby objects and the JSON hash where everything has been converted to the types expected by a Mastodon server.
For JSON hashes, ‘Time` objects become ISO8601-formatted strings, `URI` objects become strings containing the url and `Entity` subobjects become their own JSON hashes. (Note that conversion to JSON hashes isn’t really used outside of some testing and internal stuff so I don’t guarantee that a Mastodon server or client will accept them.)
Normally, we care about initializing Entity objects from the corresponding parsed JSON object and produce Ruby hashes when we need to use a feature ‘Hash` provides.
Subclasses are all defined inside the Entity namespace so that it groups nicely in YARD docs (and because it makes the intent obvious).
Direct Known Subclasses
Entity::CustomEmoji, Account, Context, MediaAttachment, Notification, Status, StatusSource, Status_Application, Status_Mention, Status_Tag
Defined Under Namespace
Classes: Account, Context, CustomEmoji, MediaAttachment, Notification, Status, StatusSource, Status_Application, Status_Mention, Status_Tag
Class Method Summary collapse
-
.fields(*flds) ⇒ Object
private
Cool metaprogramming thing for defining Entity subclasses.
-
.from(json_hash) ⇒ Object
Construct an instance from the (parsed) JSON object returned by Mastodon.
-
.with(**fields) ⇒ Object
Construct an instance initialized with Ruby objects.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Compare for equality.
-
#[](key) ⇒ Object
Retrieve a field value by name.
-
#[]=(key, value) ⇒ Object
Set a field value by name.
-
#initialize ⇒ Entity
constructor
Default constructor; creates an empty instance.
-
#print ⇒ Object
Wrapper around ‘puts to_long_s()`.
-
#set_from_hash!(some_hash, ignore_unknown: false, from_json: false) ⇒ Object
Set all fields from a hash.
-
#to_h(json_compatible = false) ⇒ Hash
Return a hash of the contents mapping field name to value.
-
#to_long_s(indent_level = 0) ⇒ String
Produce a long-form human-friendly description of this Entity.
-
#to_s ⇒ String
(also: #inspect)
Produce a short human-friendly description.
Constructor Details
Class Method Details
.fields(*flds) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Cool metaprogramming thing for defining Shep::Entity subclasses.
A typical Shep::Entity subclass should contain only a call to this method. For example:
class Thingy < Entity
fields(
:id, %i{show}, StringBox,
:timestamp, TimeBox,
:count, %i{show}, NumBox,
:url, URIBox,
)
end
fields takes a variable sequence of arguments that must be grouped as follows:
1. The field name. This **must** be a symbol.
2. An optional Array containing the symbol :show. If given,
this field will be included in the string returned by
`to_s`. (This is actually a mechanism for setting various
properties, but all we need is `:show`, so that's it for
now.)
3. The type specifier. If omitted, defaults to StringBox.
The type specifier must be either:
1. One of the following classes: `TypeBox`, `StringBox`,
`TimeBox`, `URIBox`, or `NumBox`, corresponding to the type
this field will be.
2. A subclass of Entity, indicating that this field holds
another Mastodon object.
3. An Array holding a single element which must be one of the
above classes, indicating that the field holds an array of
items of that type.
281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/shep/entity_base.rb', line 281 def self.fields(*flds) known_props = %i{show}.to_set names_and_types = [] notables = [] until flds.empty? name = flds.shift assert{ name.class == Symbol } properties = Set.new if flds[0].is_a?(Array) && flds[0][0].is_a?(Symbol) properties += flds[0] assert("Unknown properti(es): #{(properties - known_props).to_a}") { (properties - known_props).empty? } flds.shift end notables.push(name) if properties.include? :show if flds[0] && flds[0].class != Symbol typefld = flds.shift # Array means ArrayBox with the element as type if typefld.is_a? Array assert{typefld.size == 1 && typefld[0].is_a?(Class)} atype = typefld[0] # If this is an array of entity boxes, handle that atype = EntityBox.wrapping(atype) if atype < Entity type = ArrayBox.wrapping(atype) elsif typefld.is_a?(Class) && typefld < Entity type = EntityBox.wrapping(typefld) elsif typefld.is_a?(Class) && typefld < TypeBox type = typefld else raise Error::Caller.new("Unknown field type '#{typefld}'") end else type = StringBox end add_fld(name, type) names_and_types.push [name, type] end names_and_types.freeze notables.freeze add_init(names_and_types) make_has_name(names_and_types) make_disp_fields(notables) make_keys(names_and_types) # This gets used to generate documentation so we make it private # and (ab)use Object.send to call it later define_singleton_method(:names_and_types) { names_and_types } singleton_class.send(:private, :names_and_types) end |
.from(json_hash) ⇒ Object
Construct an instance from the (parsed) JSON object returned by Mastodon.
Values must be of the expected types as they appear in the Mastodon object (i.e. the JSON Hash described above). Missing key/value pairs are allowed and treated as nil; unknown keys are ignored.
87 88 |
# File 'lib/shep/entity_base.rb', line 87 def self.from(json_hash) = new.set_from_hash!(json_hash, ignore_unknown: true, from_json: true) |
.with(**fields) ⇒ Object
Construct an instance initialized with Ruby objects.
This intended for creating Shep::Entity subobjects in Ruby code. Keys of fields must correspond to the class’s supported fields and be of the correct type. No fields may be omitted.
75 76 77 78 |
# File 'lib/shep/entity_base.rb', line 75 def self.with(**fields) = new.set_from_hash!(fields, ignore_unknown: false, from_json: false) |
Instance Method Details
#==(other) ⇒ Boolean
Compare for equality.
Two Entity subinstances are identical if they are of the same class and all of their field values are also equal according to ‘:==`
181 182 183 184 185 |
# File 'lib/shep/entity_base.rb', line 181 def ==(other) return false unless self.class == other.class keys.each{|k| return false unless self[k] == other[k] } return true end |
#[](key) ⇒ Object
Retrieve a field value by name
161 |
# File 'lib/shep/entity_base.rb', line 161 def [](key) = getbox(key).get |
#[]=(key, value) ⇒ Object
Set a field value by name
169 170 171 172 |
# File 'lib/shep/entity_base.rb', line 169 def []=(key, value) getbox(key).set(value) return value end |
#print ⇒ Object
Wrapper around ‘puts to_long_s()`
232 |
# File 'lib/shep/entity_base.rb', line 232 def print = puts(to_long_s) |
#set_from_hash!(some_hash, ignore_unknown: false, from_json: false) ⇒ Object
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/shep/entity_base.rb', line 103 def set_from_hash!(some_hash, ignore_unknown: false, from_json: false) some_hash.each do |key, value| key = key.intern unless has_fld?(key) raise Error::Caller.new("Unknown field: '#{key}'!") unless ignore_unknown next end if from_json getbox(key).set_from_json(value) else self.send("#{key}=".intern, value) end end return self end |
#to_h(json_compatible = false) ⇒ Hash
Return a hash of the contents mapping field name to value.
If ‘json_compatible` is true, the resulting hash will be easily convertable to Mastodon-format JSON. See above.
Unset (i.e. nil) values appear as entries with nil values.
197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/shep/entity_base.rb', line 197 def to_h(json_compatible = false) result = {} keys.each{|name| hkey = json_compatible ? name.to_s : name box = getbox(name) val = json_compatible ? box.get_for_json : box.get result[hkey] = val } result end |
#to_long_s(indent_level = 0) ⇒ String
Produce a long-form human-friendly description of this Entity.
This is mostly here for debugging.
217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/shep/entity_base.rb', line 217 def to_long_s(indent_level = 0) name_pad = keys.map(&:size).max + 2 result = keys.map do |key| line = " " * (indent_level * 2) line += sprintf("%-*s", name_pad, "#{key}:") val = self[key] line += val.is_a?(Entity) ? val.to_long_s(indent_level + 1) : val.to_s end return result.join("\n") end |
#to_s ⇒ String Also known as: inspect
Produce a short human-friendly description.
130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
# File 'lib/shep/entity_base.rb', line 130 def to_s maxlen = 20 notable = self.disp_fields() .reject{|fld| getbox(fld).get_for_json.to_s.empty? } .map{|fld| valtxt = getbox(fld).get valtxt = valtxt[0..maxlen] + '...' unless valtxt.size < maxlen+3 "#{fld}=#{valtxt}" } .join(",") notable = "0x#{self.object_id.to_s(16)}" if notable.empty? "#{self.class}<#{notable}>" end |