Class: XRPL::Model
- Inherits:
-
Object
- Object
- XRPL::Model
- Defined in:
- lib/xrpl/model.rb
Overview
Common ground for the objects definitions.json describes field by field: transactions (TRANSACTION_FORMATS) and ledger entries (LEDGER_ENTRY_FORMATS).
A family - XRPL::Transaction, XRPL::LedgerEntry - is a subclass that names the type field ("TransactionType", "LedgerEntryType") and calls .define_types! with the right tables. That creates one class per entry in the table, with an accessor for every field the type accepts and a constant for every flag it defines. None of them is written by hand, so syncing definitions.json updates them all and they cannot drift.
Fields are written in snake_case and stored under the ledger's own PascalCase names, so #to_h hands the binary codec exactly what it expects.
Direct Known Subclasses
Defined Under Namespace
Classes: ValidationError
Constant Summary collapse
- REQUIRED =
rippled's SOEStyle: what the format says about a field.
0- OPTIONAL =
1- DEFAULT =
2- DEFINITIONS =
BinaryCodec::Definitions.instance.raw
- FIELD_TO_ACCESSOR =
DEFINITIONS['FIELDS'].to_h { |name, _| [name, underscore(name)] }.freeze
- ACCESSOR_TO_FIELD =
FIELD_TO_ACCESSOR.invert.freeze
Class Attribute Summary collapse
-
.flags ⇒ Object
readonly
Flag name -> bit, e.g.
-
.format ⇒ Object
readonly
Ledger field name -> optionality, including the common fields.
-
.type_name ⇒ Object
readonly
The ledger's name for this type, e.g.
Class Method Summary collapse
-
.define_types!(formats, flags, flag_aliases = {}) ⇒ Object
Builds one subclass per type in
formats, with an accessor for every field the type accepts and a constant for every flag it defines. -
.flag_bit(flag) ⇒ Object
The bit for a flag given by ledger name, constant name or bit.
-
.for(type) ⇒ Object
The class for a type name, or nil if the ledger has no such type.
-
.from(hash) ⇒ Object
Build the right subclass from a hash, PascalCase or snake.
-
.label ⇒ Object
How the family reads in an error message, e.g.
-
.resolve(name) ⇒ Object
Translates an accessor name to the ledger's field name.
-
.supplied_later ⇒ Object
Required by the format, but supplied by a later step (autofill, signing) rather than by the caller.
-
.type_field ⇒ Object
The field that names the type: "TransactionType" or "LedgerEntryType".
-
.underscore(name) ⇒ Object
Ledger field name -> snake_case accessor, and back.
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
-
#[](name) ⇒ Object
Reads a field by accessor name, symbol or ledger name.
-
#[]=(name, value) ⇒ Object
Writes a field, rejecting anything the type does not define.
-
#flag?(flag) ⇒ Boolean
Whether a flag is set in Flags.
-
#flag_names ⇒ Object
The names of the flags set in Flags, in the ledger's spelling.
- #hash ⇒ Object
-
#initialize(fields = {}) ⇒ Model
constructor
A new instance of Model.
- #inspect ⇒ Object
-
#missing_fields ⇒ Object
Fields the format requires that have not been set, ignoring the ones a later step provides.
-
#to_blob ⇒ Object
The serialised object, as hex.
-
#to_h ⇒ Object
(also: #to_hash)
The object as the binary codec wants it: ledger field names, with the type field filled in.
- #valid? ⇒ Boolean
-
#validate! ⇒ Object
Raises unless every required field is present.
Constructor Details
#initialize(fields = {}) ⇒ Model
Returns a new instance of Model.
140 141 142 143 |
# File 'lib/xrpl/model.rb', line 140 def initialize(fields = {}) @fields = {} fields.each { |name, value| self[name] = value } end |
Class Attribute Details
.flags ⇒ Object (readonly)
Flag name -> bit, e.g. "tfPartialPayment" => 131072.
58 59 60 |
# File 'lib/xrpl/model.rb', line 58 def flags @flags end |
.format ⇒ Object (readonly)
Ledger field name -> optionality, including the common fields.
55 56 57 |
# File 'lib/xrpl/model.rb', line 55 def format @format end |
.type_name ⇒ Object (readonly)
The ledger's name for this type, e.g. "Payment" or "AccountRoot".
52 53 54 |
# File 'lib/xrpl/model.rb', line 52 def type_name @type_name end |
Class Method Details
.define_types!(formats, flags, flag_aliases = {}) ⇒ Object
Builds one subclass per type in formats, with an accessor for every
field the type accepts and a constant for every flag it defines.
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/xrpl/model.rb', line 107 def define_types!(formats, flags, flag_aliases = {}) common = formats.fetch('common') formats.each do |type, own_format| next if type == 'common' format = (common + own_format) .to_h { |field| [field['name'], field['optionality']] } .freeze klass = Class.new(self) klass.instance_variable_set(:@type_name, type) klass.instance_variable_set(:@format, format) klass.instance_variable_set(:@flags, (flags[flag_aliases.fetch(type, type)] || {}).freeze) format.each_key do |field| next if field == type_field accessor = FIELD_TO_ACCESSOR[field] or next klass.define_method(accessor) { self[field] } klass.define_method("#{accessor}=") { |value| self[field] = value } end klass.flags.each do |flag, bit| klass.const_set(underscore(flag).upcase, bit) end const_set(type, klass) end end |
.flag_bit(flag) ⇒ Object
The bit for a flag given by ledger name, constant name or bit.
213 214 215 216 217 218 219 |
# File 'lib/xrpl/model.rb', line 213 def self.flag_bit(flag) return flag if flag.is_a?(Integer) key = flag.to_s _, bit = flags.find { |name, _| name == key || underscore(name) == key.downcase } bit or raise ArgumentError, "#{type_name} has no flag #{flag}" end |
.for(type) ⇒ Object
The class for a type name, or nil if the ledger has no such type.
78 79 80 |
# File 'lib/xrpl/model.rb', line 78 def for(type) const_get(type) if const_defined?(type, false) end |
.from(hash) ⇒ Object
Build the right subclass from a hash, PascalCase or snake.
83 84 85 86 87 88 89 90 91 |
# File 'lib/xrpl/model.rb', line 83 def from(hash) type = hash[type_field] || hash[type_field.to_sym] || hash[underscore(type_field).to_sym] raise ValidationError, "#{label.capitalize} hash has no #{type_field}" unless type klass = self.for(type.to_s) raise ValidationError, "Unknown #{label} type #{type}" unless klass klass.new(hash) end |
.label ⇒ Object
How the family reads in an error message, e.g. "transaction".
67 68 69 |
# File 'lib/xrpl/model.rb', line 67 def label raise NotImplementedError, "#{name}.label" end |
.resolve(name) ⇒ Object
Translates an accessor name to the ledger's field name.
94 95 96 97 98 99 |
# File 'lib/xrpl/model.rb', line 94 def resolve(name) key = name.to_s return key if FIELD_TO_ACCESSOR.key?(key) ACCESSOR_TO_FIELD[key] || key end |
.supplied_later ⇒ Object
Required by the format, but supplied by a later step (autofill, signing) rather than by the caller. Not demanded by #validate!.
73 74 75 |
# File 'lib/xrpl/model.rb', line 73 def supplied_later [] end |
.type_field ⇒ Object
The field that names the type: "TransactionType" or "LedgerEntryType". A family defines it.
62 63 64 |
# File 'lib/xrpl/model.rb', line 62 def type_field raise NotImplementedError, "#{name}.type_field" end |
.underscore(name) ⇒ Object
Ledger field name -> snake_case accessor, and back.
XChain, NFToken and MPToken are brand names rather than acronyms, so they are folded to a single word the way the reference SDKs write them.
32 33 34 35 36 37 38 39 40 41 |
# File 'lib/xrpl/model.rb', line 32 def self.underscore(name) name .sub(/\AXChain/, 'Xchain') .sub(/\ANFToken/, 'Nftoken') .sub(/\AMPToken/, 'Mptoken') .gsub(/([A-Z]{2,})s(?=[A-Z]|\z)/) { "#{Regexp.last_match(1).capitalize}s" } .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2') .downcase end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
226 227 228 |
# File 'lib/xrpl/model.rb', line 226 def ==(other) other.is_a?(Model) && other.to_h == to_h end |
#[](name) ⇒ Object
Reads a field by accessor name, symbol or ledger name.
146 147 148 |
# File 'lib/xrpl/model.rb', line 146 def [](name) @fields[self.class.resolve(name)] end |
#[]=(name, value) ⇒ Object
Writes a field, rejecting anything the type does not define. The type field itself is accepted, and checked, so a hash read off the ledger can be passed in whole.
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/xrpl/model.rb', line 153 def []=(name, value) field = self.class.resolve(name) if field == self.class.type_field return if value.to_s == self.class.type_name raise ValidationError, "#{self.class.type_name} cannot carry #{field} #{value}" end unless self.class.format.key?(field) raise ValidationError, "#{self.class.type_name} has no field #{field}" end value.nil? ? @fields.delete(field) : @fields[field] = value end |
#flag?(flag) ⇒ Boolean
Whether a flag is set in Flags. Takes the ledger's name ("lsfDefaultRipple"), the constant's name (:lsf_default_ripple) or the bit itself.
202 203 204 |
# File 'lib/xrpl/model.rb', line 202 def flag?(flag) (self['Flags'].to_i & self.class.flag_bit(flag)) != 0 end |
#flag_names ⇒ Object
The names of the flags set in Flags, in the ledger's spelling.
207 208 209 210 |
# File 'lib/xrpl/model.rb', line 207 def flag_names value = self['Flags'].to_i self.class.flags.select { |_, bit| (value & bit) != 0 }.keys end |
#hash ⇒ Object
231 232 233 |
# File 'lib/xrpl/model.rb', line 231 def hash to_h.hash end |
#inspect ⇒ Object
235 236 237 |
# File 'lib/xrpl/model.rb', line 235 def inspect "#<#{self.class.name} #{to_h.inspect}>" end |
#missing_fields ⇒ Object
Fields the format requires that have not been set, ignoring the ones a later step provides.
178 179 180 181 182 183 184 |
# File 'lib/xrpl/model.rb', line 178 def missing_fields self.class.format .select { |_, optionality| optionality == REQUIRED } .keys .reject { |field| field == self.class.type_field } .reject { |field| self.class.supplied_later.include?(field) || @fields.key?(field) } end |
#to_blob ⇒ Object
The serialised object, as hex.
222 223 224 |
# File 'lib/xrpl/model.rb', line 222 def to_blob BinaryCodec.json_to_binary(to_h) end |
#to_h ⇒ Object Also known as: to_hash
The object as the binary codec wants it: ledger field names, with the type field filled in.
171 172 173 |
# File 'lib/xrpl/model.rb', line 171 def to_h { self.class.type_field => self.class.type_name }.merge(@fields) end |
#valid? ⇒ Boolean
186 187 188 |
# File 'lib/xrpl/model.rb', line 186 def valid? missing_fields.empty? end |
#validate! ⇒ Object
Raises unless every required field is present.
191 192 193 194 195 196 197 |
# File 'lib/xrpl/model.rb', line 191 def validate! missing = missing_fields return self if missing.empty? raise ValidationError, "#{self.class.type_name} is missing #{missing.join(', ')}" end |