class Data
Die Klasse Data bietet eine bequeme Möglichkeit, einfache Klassen für wertähnliche Objekte zu definieren.
Das einfachste Anwendungsbeispiel
Measure = Data.define(:amount, :unit) # Positional arguments constructor is provided distance = Measure.new(100, 'km') #=> #<data Measure amount=100, unit="km"> # Keyword arguments constructor is provided weight = Measure.new(amount: 50, unit: 'kg') #=> #<data Measure amount=50, unit="kg"> # Alternative form to construct an object: speed = Measure[10, 'mPh'] #=> #<data Measure amount=10, unit="mPh"> # Works with keyword arguments, too: area = Measure[amount: 1.5, unit: 'm^2'] #=> #<data Measure amount=1.5, unit="m^2"> # Argument accessors are provided: distance.amount #=> 100 distance.unit #=> "km"
Das konstruierte Objekt hat auch vernünftige Definitionen für den == Operator, die to_h Hash-Konvertierung und deconstruct / deconstruct_keys für die Verwendung in der Mustererkennung.
Die Methode ::define akzeptiert einen optionalen Block und wertet ihn im Kontext der neu definierten Klasse aus. Dies ermöglicht die Definition zusätzlicher Methoden.
Measure = Data.define(:amount, :unit) do def <=>(other) return unless other.is_a?(self.class) && other.unit == unit amount <=> other.amount end include Comparable end Measure[3, 'm'] < Measure[5, 'm'] #=> true Measure[3, 'm'] < Measure[5, 'kg'] # comparison of Measure with Measure failed (ArgumentError)
Data bietet keine Member-Writer oder Enumeratoren: Es ist als Speicher für unveränderliche atomare Werte gedacht. Beachten Sie jedoch, dass, wenn einige der Datenmember einer veränderlichen Klasse angehören, Data keine zusätzliche Unveränderlichkeitsprüfung durchführt.
Event = Data.define(:time, :weekdays) event = Event.new('18:00', %w[Tue Wed Fri]) #=> #<data Event time="18:00", weekdays=["Tue", "Wed", "Fri"]> # There is no #time= or #weekdays= accessors, but changes are # still possible: event.weekdays << 'Sat' event #=> #<data Event time="18:00", weekdays=["Tue", "Wed", "Fri", "Sat"]>
Siehe auch Struct, was ein ähnliches Konzept ist, aber eine eher Container-ähnliche API hat, die es ermöglicht, den Inhalt des Objekts zu ändern und es zu durchlaufen.
Öffentliche Klassenmethoden
Source
static VALUE
rb_data_s_def(int argc, VALUE *argv, VALUE klass)
{
VALUE rest;
long i;
VALUE data_class;
rest = rb_ident_hash_new();
RBASIC_CLEAR_CLASS(rest);
for (i=0; i<argc; i++) {
VALUE mem = rb_to_symbol(argv[i]);
if (rb_is_attrset_sym(mem)) {
rb_raise(rb_eArgError, "invalid data member: %"PRIsVALUE, mem);
}
if (RTEST(rb_hash_has_key(rest, mem))) {
rb_raise(rb_eArgError, "duplicate member: %"PRIsVALUE, mem);
}
rb_hash_aset(rest, mem, Qtrue);
}
rest = rb_hash_keys(rest);
RBASIC_CLEAR_CLASS(rest);
OBJ_FREEZE(rest);
data_class = anonymous_struct(klass);
setup_data(data_class, rest);
if (rb_block_given_p()) {
rb_mod_module_eval(0, 0, data_class);
}
return data_class;
}
Definiert eine neue Data-Klasse.
measure = Data.define(:amount, :unit) #=> #<Class:0x00007f70c6868498> measure.new(1, 'km') #=> #<data amount=1, unit="km"> # It you store the new class in the constant, it will # affect #inspect and will be more natural to use: Measure = Data.define(:amount, :unit) #=> Measure Measure.new(1, 'km') #=> #<data Measure amount=1, unit="km">
Beachten Sie, dass Data ohne Member akzeptabel ist und eine nützliche Technik zur Definition mehrerer homogener Datenklassen sein kann, wie z.B.:
class HTTPFetcher Response = Data.define(:body) NotFound = Data.define # ... implementation end
Nun haben verschiedene Arten von Antworten von HTTPFetcher eine konsistente Darstellung.
#<data HTTPFetcher::Response body="<html..."> #<data HTTPFetcher::NotFound>
Und sind bequem in der Mustererkennung zu verwenden.
case fetcher.get(url) in HTTPFetcher::Response(body) # process body variable in HTTPFetcher::NotFound # handle not found case end
Source
#define rb_data_s_members_m rb_struct_s_members_m
Gibt ein Array von Member-Namen der Datenklasse zurück.
Measure = Data.define(:amount, :unit) Measure.members # => [:amount, :unit]
Source
static VALUE
rb_data_initialize_m(int argc, const VALUE *argv, VALUE self)
{
VALUE klass = rb_obj_class(self);
rb_struct_modify(self);
VALUE members = struct_ivar_get(klass, id_members);
size_t num_members = RARRAY_LEN(members);
if (argc == 0) {
if (num_members > 0) {
rb_exc_raise(rb_keyword_error_new("missing", members));
}
return Qnil;
}
if (argc > 1 || !RB_TYPE_P(argv[0], T_HASH)) {
rb_error_arity(argc, 0, 0);
}
if (RHASH_SIZE(argv[0]) < num_members) {
VALUE missing = rb_ary_diff(members, rb_hash_keys(argv[0]));
rb_exc_raise(rb_keyword_error_new("missing", missing));
}
struct struct_hash_set_arg arg;
rb_mem_clear((VALUE *)RSTRUCT_CONST_PTR(self), num_members);
arg.self = self;
arg.unknown_keywords = Qnil;
rb_hash_foreach(argv[0], struct_hash_set_i, (VALUE)&arg);
// Freeze early before potentially raising, so that we don't leave an
// unfrozen copy on the heap, which could get exposed via ObjectSpace.
OBJ_FREEZE(self);
if (arg.unknown_keywords != Qnil) {
rb_exc_raise(rb_keyword_error_new("unknown", arg.unknown_keywords));
}
return Qnil;
}
Konstruktoren für mit ::define definierte Klassen akzeptieren sowohl positionsgebundene als auch Schlüsselwortargumente.
Measure = Data.define(:amount, :unit) Measure.new(1, 'km') #=> #<data Measure amount=1, unit="km"> Measure.new(amount: 1, unit: 'km') #=> #<data Measure amount=1, unit="km"> # Alternative shorter initialization with [] Measure[1, 'km'] #=> #<data Measure amount=1, unit="km"> Measure[amount: 1, unit: 'km'] #=> #<data Measure amount=1, unit="km">
Alle Argumente sind obligatorisch (im Gegensatz zu Struct) und werden in Schlüsselwortargumente umgewandelt.
Measure.new(amount: 1) # in `initialize': missing keyword: :unit (ArgumentError) Measure.new(1) # in `initialize': missing keyword: :unit (ArgumentError)
Beachten Sie, dass Measure#initialize immer Schlüsselwortargumente erhält und dass obligatorische Argumente in initialize und nicht in new überprüft werden. Dies kann wichtig sein, wenn Sie initialize neu definieren, um Argumente zu konvertieren oder Standardwerte bereitzustellen.
Measure = Data.define(:amount, :unit) class Measure NONE = Data.define def initialize(amount:, unit: NONE.new) super(amount: Float(amount), unit:) end end Measure.new('10', 'km') # => #<data Measure amount=10.0, unit="km"> Measure.new(10_000) # => #<data Measure amount=10000.0, unit=#<data Measure::NONE>>
Öffentliche Instanzmethoden
Source
#define rb_data_equal rb_struct_equal
Gibt true zurück, wenn other dieselbe Klasse wie self ist und alle Member gleich sind.
Beispiele
Measure = Data.define(:amount, :unit) Measure[1, 'km'] == Measure[1, 'km'] #=> true Measure[1, 'km'] == Measure[2, 'km'] #=> false Measure[1, 'km'] == Measure[1, 'm'] #=> false Measurement = Data.define(:amount, :unit) # Even though Measurement and Measure have the same "shape" # their instances are never equal Measure[1, 'km'] == Measurement[1, 'km'] #=> false
Source
#define rb_data_deconstruct rb_struct_to_a
Gibt die Werte in self als Array zurück, um sie in der Mustererkennung zu verwenden.
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.deconstruct #=> [10, "km"] # usage case distance in n, 'km' # calls #deconstruct underneath puts "It is #{n} kilometers away" else puts "Don't know how to handle it" end # prints "It is 10 kilometers away"
Oder mit Überprüfung der Klasse, ebenfalls
case distance in Measure(n, 'km') puts "It is #{n} kilometers away" # ... end
Source
#define rb_data_deconstruct_keys rb_struct_deconstruct_keys
Gibt einen Hash der Namens-/Wertpaare zurück, um ihn in der Mustererkennung zu verwenden.
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.deconstruct_keys(nil) #=> {:amount=>10, :unit=>"km"} distance.deconstruct_keys([:amount]) #=> {:amount=>10} # usage case distance in amount:, unit: 'km' # calls #deconstruct_keys underneath puts "It is #{amount} kilometers away" else puts "Don't know how to handle it" end # prints "It is 10 kilometers away"
Oder mit Überprüfung der Klasse, ebenfalls
case distance in Measure(amount:, unit: 'km') puts "It is #{amount} kilometers away" # ... end
Source
#define rb_data_eql rb_struct_eql
Gleichheitsprüfung, die verwendet wird, wenn zwei Datenelemente Schlüssel eines Hash sind.
Der subtile Unterschied zu == besteht darin, dass die Member auch mit ihrer eql? Methode verglichen werden, was in einigen Fällen wichtig sein kann.
Measure = Data.define(:amount, :unit) Measure[1, 'km'] == Measure[1.0, 'km'] #=> true, they are equal as values # ...but... Measure[1, 'km'].eql? Measure[1.0, 'km'] #=> false, they represent different hash keys
Siehe auch Object#eql? für weitere Erklärungen zur Methodennutzung.
Source
#define rb_data_hash rb_struct_hash
Definiert Object#hash (verwendet, um Objekte als Hash-Schlüssel zu unterscheiden) neu, so dass Datenobjekte derselben Klasse mit demselben Inhalt denselben hash-Wert haben und als derselbe Hash-Schlüssel dargestellt werden.
Measure = Data.define(:amount, :unit) Measure[1, 'km'].hash == Measure[1, 'km'].hash #=> true Measure[1, 'km'].hash == Measure[10, 'km'].hash #=> false Measure[1, 'km'].hash == Measure[1, 'm'].hash #=> false Measure[1, 'km'].hash == Measure[1.0, 'km'].hash #=> false # Structurally similar data class, but shouldn't be considered # the same hash key Measurement = Data.define(:amount, :unit) Measure[1, 'km'].hash == Measurement[1, 'km'].hash #=> false
Source
static VALUE
rb_data_inspect(VALUE s)
{
return rb_exec_recursive(inspect_struct, s, rb_str_new2("#<data "));
}
Gibt eine String-Repräsentation von self zurück.
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] p distance # uses #inspect underneath #<data Measure amount=10, unit="km"> puts distance # uses #to_s underneath, same representation #<data Measure amount=10, unit="km">
Source
#define rb_data_members_m rb_struct_members_m
Gibt die Member-Namen von self als Array zurück.
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.members #=> [:amount, :unit]
Source
#define rb_data_to_h rb_struct_to_h
Gibt die Hash-Repräsentation des Datenobjekts zurück.
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.to_h #=> {:amount=>10, :unit=>"km"}
Wie Enumerable#to_h, wenn der Block bereitgestellt wird, wird erwartet, dass er Schlüssel-Wert-Paare zur Konstruktion eines Hashs erzeugt.
distance.to_h { |name, val| [name.to_s, val.to_s] } #=> {"amount"=>"10", "unit"=>"km"}
Beachten Sie, dass es eine nützliche Symmetrie zwischen to_h und initialize gibt.
distance2 = Measure.new(**distance.to_h) #=> #<data Measure amount=10, unit="km"> distance2 == distance #=> true
Gibt eine String-Repräsentation von self zurück.
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] p distance # uses #inspect underneath #<data Measure amount=10, unit="km"> puts distance # uses #to_s underneath, same representation #<data Measure amount=10, unit="km">
Source
static VALUE
rb_data_with(int argc, const VALUE *argv, VALUE self)
{
VALUE kwargs;
rb_scan_args(argc, argv, "0:", &kwargs);
if (NIL_P(kwargs)) {
return self;
}
VALUE h = rb_struct_to_h(self);
rb_hash_update_by(h, kwargs, 0);
return rb_class_new_instance_kw(1, &h, rb_obj_class(self), TRUE);
}
Gibt eine flache Kopie von self zurück — die Instanzvariablen von self werden kopiert, aber nicht die Objekte, auf die sie verweisen.
Wenn der Methode Schlüsselwortargumente übergeben werden, wird die Kopie mit den jeweiligen Feldwerten erstellt, die aktualisiert wurden, um die übergebenen Schlüsselwortargumentwerte zu verwenden. Beachten Sie, dass es ein Fehler ist, ein Schlüsselwort zu übergeben, das die Data-Klasse nicht als Member hat.
Point = Data.define(:x, :y) origin = Point.new(x: 0, y: 0) up = origin.with(x: 1) right = origin.with(y: 1) up_and_right = up.with(y: 1) p origin # #<data Point x=0, y=0> p up # #<data Point x=1, y=0> p right # #<data Point x=0, y=1> p up_and_right # #<data Point x=1, y=1> out = origin.with(z: 1) # ArgumentError: unknown keyword: :z some_point = origin.with(1, 2) # ArgumentError: expected keyword arguments, got positional arguments