Class Data provides a convenient way to define simple classes for value-alike objects.
The simplest example of usage:
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"
Constructed object also has a reasonable definitions of ==
operator, to_h
hash conversion, and deconstruct
/ deconstruct_keys
to be used in pattern matching.
::define
method accepts an optional block and evaluates it in the context of the newly defined class. That allows to define additional methods:
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
provides no member writers, or enumerators: it is meant to be a storage for immutable atomic values. But note that if some of data members is of a mutable class, Data
does no additional immutability enforcement:
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"]>
See also Struct
, which is a similar concept, but has more container-alike API, allowing to change contents of the object and enumerate it.
Load the given PStore
file. If read_only
is true, the unmarshalled Hash
will be returned. If read_only
is false, a 3-tuple will be returned: the unmarshalled Hash
, a checksum of the data, and the size of the data.
The path to standard location of the user’s data directory.
Defines a new Data class.
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">
Note that member-less Data is acceptable and might be a useful technique for defining several homogenous data classes, like
class HTTPFetcher Response = Data.define(:body) NotFound = Data.define # ... implementation end
Now, different kinds of responses from HTTPFetcher
would have consistent representation:
#<data HTTPFetcher::Response body="<html..."> #<data HTTPFetcher::NotFound>
And are convenient to use in pattern matching:
case fetcher.get(url) in HTTPFetcher::Response(body) # process body variable in HTTPFetcher::NotFound # handle not found case end
Returns an array of member names of the data class:
Measure = Data.define(:amount, :unit) Measure.members # => [:amount, :unit]
Constructors for classes defined with ::define
accept both positional and keyword arguments.
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">
All arguments are mandatory (unlike Struct
), and converted to keyword arguments:
Measure.new(amount: 1) # in `initialize': missing keyword: :unit (ArgumentError) Measure.new(1) # in `initialize': missing keyword: :unit (ArgumentError)
Note that Measure#initialize
always receives keyword arguments, and that mandatory arguments are checked in initialize
, not in new
. This can be important for redefining initialize in order to convert arguments or provide defaults:
Measure = Data.define(:amount, :unit) do 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 NONE>>
Returns true
if other
is the same class as self
, and all members are equal.
Examples:
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
Equality check that is used when two items of data are keys of a Hash
.
The subtle difference with ==
is that members are also compared with their eql?
method, which might be important in some cases:
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
See also Object#eql?
for further explanations of the method usage.
Redefines Object#hash
(used to distinguish objects as Hash
keys) so that data objects of the same class with same content would have the same hash
value, and represented the same Hash
key.
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
Returns a string representation of self
:
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">
Returns the member names from self
as an array:
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.members #=> [:amount, :unit]
Returns the values in self
as an array, to use in pattern matching:
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"
Or, with checking the class, too:
case distance in Measure(n, 'km') puts "It is #{n} kilometers away" # ... end
Returns a shallow copy of self
— the instance variables of self
are copied, but not the objects they reference.
If the method is supplied any keyword arguments, the copy will be created with the respective field values updated to use the supplied keyword argument values. Note that it is an error to supply a keyword that the Data
class does not have as a member.
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
returns the cmsg data as a string.
p Socket::AncillaryData.new(:INET6, :IPV6, :PKTINFO, "").data #=> ""
returns the socket option data as a string.
p Socket::Option.new(:INET6, :IPV6, :RECVPKTINFO, [1].pack("i!")).data #=> "\x01\x00\x00\x00"
Guesses the type of the data which have been inputed into the stream. The returned value is either BINARY
, ASCII
, or UNKNOWN
.
Returns Hash
representation of the data object.
Measure = Data.define(:amount, :unit) distance = Measure[10, 'km'] distance.to_h #=> {:amount=>10, :unit=>"km"}
Like Enumerable#to_h
, if the block is provided, it is expected to produce key-value pairs to construct a hash:
distance.to_h { |name, val| [name.to_s, val.to_s] } #=> {"amount"=>"10", "unit"=>"km"}
Note that there is a useful symmetry between to_h
and initialize:
distance2 = Measure.new(**distance.to_h) #=> #<data Measure amount=10, unit="km"> distance2 == distance #=> true
Returns a hash of the name/value pairs, to use in pattern matching.
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"
Or, with checking the class, too:
case distance in Measure(amount:, unit: 'km') puts "It is #{amount} kilometers away" # ... end
Returns a string representation of self
:
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">
Sets the length of the plaintext / ciphertext message that will be processed in CCM mode. Make sure to call this method after key=
and iv=
have been set, and before auth_data=
.
Only call this method after calling Cipher#encrypt
or Cipher#decrypt
.
Sets the request body to a URL-encoded string derived from argument params
, and sets request header field 'Content-Type'
to 'application/x-www-form-urlencoded'
.
The resulting request is suitable for HTTP
request POST
or PUT
.
Argument params
must be suitable for use as argument enum
to URI.encode_www_form
.
With only argument params
given, sets the body to a URL-encoded string with the default separator '&'
:
req = Net::HTTP::Post.new('example.com') req.set_form_data(q: 'ruby', lang: 'en') req.body # => "q=ruby&lang=en" req['Content-Type'] # => "application/x-www-form-urlencoded" req.set_form_data([['q', 'ruby'], ['lang', 'en']]) req.body # => "q=ruby&lang=en" req.set_form_data(q: ['ruby', 'perl'], lang: 'en') req.body # => "q=ruby&q=perl&lang=en" req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']]) req.body # => "q=ruby&q=perl&lang=en"
With string argument sep
also given, uses that string as the separator:
req.set_form_data({q: 'ruby', lang: 'en'}, '|') req.body # => "q=ruby|lang=en"
Net::HTTPHeader#form_data=
is an alias for Net::HTTPHeader#set_form_data
.