Class Struct provides a convenient way to create a simple class that can store and fetch values.
This example creates a subclass of Struct
, Struct::Customer
; the first argument, a string, is the name of the subclass; the other arguments, symbols, determine the members of the new subclass.
Customer = Struct.new('Customer', :name, :address, :zip) Customer.name # => "Struct::Customer" Customer.class # => Class Customer.superclass # => Struct
Corresponding to each member are two methods, a writer and a reader, that store and fetch values:
methods = Customer.instance_methods false methods # => [:zip, :address=, :zip=, :address, :name, :name=]
An instance of the subclass may be created, and its members assigned values, via method ::new
:
joe = Customer.new("Joe Smith", "123 Maple, Anytown NC", 12345) joe # => #<struct Struct::Customer name="Joe Smith", address="123 Maple, Anytown NC", zip=12345>
The member values may be managed thus:
joe.name # => "Joe Smith" joe.name = 'Joseph Smith' joe.name # => "Joseph Smith"
And thus; note that member name may be expressed as either a string or a symbol:
joe[:name] # => "Joseph Smith" joe[:name] = 'Joseph Smith, Jr.' joe['name'] # => "Joseph Smith, Jr."
See Struct::new
.
First, what’s elsewhere. Class Struct:
Inherits from class Object.
Includes module Enumerable, which provides dozens of additional methods.
See also Data
, which is a somewhat similar, but stricter concept for defining immutable value objects.
Here, class Struct provides methods that are useful for:
Struct
Subclass ::new
: Returns a new subclass of Struct.
==
: Returns whether a given object is equal to self
, using ==
to compare member values.
eql?
: Returns whether a given object is equal to self
, using eql?
to compare member values.
[]
: Returns the value associated with a given member name.
to_a
(aliased as values
, deconstruct
): Returns the member values in self
as an array.
deconstruct_keys
: Returns a hash of the name/value pairs for given member names.
dig
: Returns the object in nested objects that is specified by a given member name and additional arguments.
members
: Returns an array of the member names.
select
(aliased as filter
): Returns an array of member values from self
, as selected by the given block.
values_at
: Returns an array containing values for given member names.
[]=
: Assigns a given value to a given member name.
each
: Calls a given block with each member name.
each_pair
: Calls a given block with each member name/value pair.
IO streams for strings, with access similar to IO
; see IO
.
Examples on this page assume that StringIO has been required:
require 'stringio'
BasicObject
is the parent class of all classes in Ruby. In particular, BasicObject
is the parent class of class Object
, which is itself the default parent class of every Ruby class:
class Foo; end Foo.superclass # => Object Object.superclass # => BasicObject
BasicObject
is the only class that has no parent:
BasicObject.superclass # => nil
Class BasicObject
can be used to create an object hierarchy (e.g., class Delegator
) that is independent of Ruby’s object hierarchy. Such objects:
Do not have namespace “pollution” from the many methods provided in class Object
and its included module Kernel
.
Do not have definitions of common classes, and so references to such common classes must be fully qualified (::String
, not String
).
A variety of strategies can be used to provide useful portions of the Standard Library in subclasses of BasicObject
:
The immediate subclass could include Kernel
, which would define methods such as puts
, exit
, etc.
A custom Kernel-like module could be created and included.
Delegation can be used via method_missing
:
class MyObjectSystem < BasicObject DELEGATE = [:puts, :p] def method_missing(name, *args, &block) return super unless DELEGATE.include? name ::Kernel.send(name, *args, &block) end def respond_to_missing?(name, include_private = false) DELEGATE.include?(name) end end
These are the methods defined for BasicObject:
::new
: Returns a new BasicObject instance.
!
: Returns the boolean negation of self
: true
or false
.
!=
: Returns whether self
and the given object are not equal.
==
: Returns whether self
and the given object are equivalent.
__id__
: Returns the integer object identifier for self
.
__send__
: Calls the method identified by the given symbol.
equal?
: Returns whether self
and the given object are the same object.
instance_eval
: Evaluates the given string or block in the context of self
.
instance_exec
: Executes the given block in the context of self
, passing the given arguments.
method_missing
: Called when self
is called with a method it does not define.
singleton_method_added
: Called when a singleton method is added to self
.
singleton_method_removed
: Called when a singleton method is removed from self
.
singleton_method_undefined
: Called when a singleton method is undefined in self
.
OptionParser
OptionParser
? See the Tutorial.
OptionParser
is a class for command-line option analysis. It is much more advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented solution.
The argument specification and the code to handle it are written in the same place.
It can output an option summary; you don’t need to maintain this string separately.
Optional and mandatory arguments are specified very gracefully.
Arguments can be automatically converted to a specified class.
Arguments can be restricted to a certain set.
All of these features are demonstrated in the examples below. See make_switch
for full documentation.
require 'optparse' options = {} OptionParser.new do |parser| parser.banner = "Usage: example.rb [options]" parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| options[:verbose] = v end end.parse! p options p ARGV
OptionParser
can be used to automatically generate help for the commands you write:
require 'optparse' Options = Struct.new(:name) class Parser def self.parse(options) args = Options.new("world") opt_parser = OptionParser.new do |parser| parser.banner = "Usage: example.rb [options]" parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n| args.name = n end parser.on("-h", "--help", "Prints this help") do puts parser exit end end opt_parser.parse!(options) return args end end options = Parser.parse %w[--help] #=> # Usage: example.rb [options] # -n, --name=NAME Name to say hello to # -h, --help Prints this help
For options that require an argument, option specification strings may include an option name in all caps. If an option is used without the required argument, an exception will be raised.
require 'optparse' options = {} OptionParser.new do |parser| parser.on("-r", "--require LIBRARY", "Require the LIBRARY before executing your script") do |lib| puts "You required #{lib}!" end end.parse!
Used:
$ ruby optparse-test.rb -r optparse-test.rb:9:in `<main>': missing argument: -r (OptionParser::MissingArgument) $ ruby optparse-test.rb -r my-library You required my-library!
OptionParser
supports the ability to coerce command line arguments into objects for us.
OptionParser
comes with a few ready-to-use kinds of type coercion. They are:
Date
– Anything accepted by Date.parse
(need to require optparse/date
)
DateTime
– Anything accepted by DateTime.parse
(need to require optparse/date
)
Time
– Anything accepted by Time.httpdate
or Time.parse
(need to require optparse/time
)
URI
– Anything accepted by URI.parse
(need to require optparse/uri
)
Shellwords
– Anything accepted by Shellwords.shellwords
(need to require optparse/shellwords
)
String
– Any non-empty string
Integer
– Any integer. Will convert octal. (e.g. 124, -3, 040)
Float
– Any float. (e.g. 10, 3.14, -100E+13)
Numeric
– Any integer, float, or rational (1, 3.4, 1/3)
DecimalInteger
– Like Integer
, but no octal format.
OctalInteger
– Like Integer
, but no decimal format.
DecimalNumeric
– Decimal integer or float.
TrueClass
– Accepts ‘+, yes, true, -, no, false’ and defaults as true
FalseClass
– Same as TrueClass
, but defaults to false
Array
– Strings separated by ‘,’ (e.g. 1,2,3)
Regexp
– Regular expressions. Also includes options.
We can also add our own coercions, which we will cover below.
As an example, the built-in Time
conversion is used. The other built-in conversions behave in the same way. OptionParser
will attempt to parse the argument as a Time
. If it succeeds, that time will be passed to the handler block. Otherwise, an exception will be raised.
require 'optparse' require 'optparse/time' OptionParser.new do |parser| parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| p time end end.parse!
Used:
$ ruby optparse-test.rb -t nonsense ... invalid argument: -t nonsense (OptionParser::InvalidArgument) $ ruby optparse-test.rb -t 10-11-12 2010-11-12 00:00:00 -0500 $ ruby optparse-test.rb -t 9:30 2014-08-13 09:30:00 -0400
The accept
method on OptionParser
may be used to create converters. It specifies which conversion block to call whenever a class is specified. The example below uses it to fetch a User
object before the on
handler receives it.
require 'optparse' User = Struct.new(:id, :name) def find_user id not_found = ->{ raise "No User Found for id #{id}" } [ User.new(1, "Sam"), User.new(2, "Gandalf") ].find(not_found) do |u| u.id == id end end op = OptionParser.new op.accept(User) do |user_id| find_user user_id.to_i end op.on("--user ID", User) do |user| puts user end op.parse!
Used:
$ ruby optparse-test.rb --user 1 #<struct User id=1, name="Sam"> $ ruby optparse-test.rb --user 2 #<struct User id=2, name="Gandalf"> $ ruby optparse-test.rb --user 3 optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError)
Hash
The into
option of order
, parse
and so on methods stores command line options into a Hash
.
require 'optparse' options = {} OptionParser.new do |parser| parser.on('-a') parser.on('-b NUM', Integer) parser.on('-v', '--verbose') end.parse!(into: options) p options
Used:
$ ruby optparse-test.rb -a {:a=>true} $ ruby optparse-test.rb -a -v {:a=>true, :verbose=>true} $ ruby optparse-test.rb -a -b 100 {:a=>true, :b=>100}
The following example is a complete Ruby program. You can run it and see the effect of specifying various options. This is probably the best way to learn the features of optparse
.
require 'optparse' require 'optparse/time' require 'ostruct' require 'pp' class OptparseExample Version = '1.0.0' CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary] CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" } class ScriptOptions attr_accessor :library, :inplace, :encoding, :transfer_type, :verbose, :extension, :delay, :time, :record_separator, :list def initialize self.library = [] self.inplace = false self.encoding = "utf8" self.transfer_type = :auto self.verbose = false end def define_options(parser) parser.banner = "Usage: example.rb [options]" parser.separator "" parser.separator "Specific options:" # add additional options perform_inplace_option(parser) delay_execution_option(parser) execute_at_time_option(parser) specify_record_separator_option(parser) list_example_option(parser) specify_encoding_option(parser) optional_option_argument_with_keyword_completion_option(parser) boolean_verbose_option(parser) parser.separator "" parser.separator "Common options:" # No argument, shows at tail. This will print an options summary. # Try it and see! parser.on_tail("-h", "--help", "Show this message") do puts parser exit end # Another typical switch to print the version. parser.on_tail("--version", "Show version") do puts Version exit end end def perform_inplace_option(parser) # Specifies an optional option argument parser.on("-i", "--inplace [EXTENSION]", "Edit ARGV files in place", "(make backup if EXTENSION supplied)") do |ext| self.inplace = true self.extension = ext || '' self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot. end end def delay_execution_option(parser) # Cast 'delay' argument to a Float. parser.on("--delay N", Float, "Delay N seconds before executing") do |n| self.delay = n end end def execute_at_time_option(parser) # Cast 'time' argument to a Time object. parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time| self.time = time end end def specify_record_separator_option(parser) # Cast to octal integer. parser.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger, "Specify record separator (default \\0)") do |rs| self.record_separator = rs end end def list_example_option(parser) # List of arguments. parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list| self.list = list end end def specify_encoding_option(parser) # Keyword completion. We are specifying a specific set of arguments (CODES # and CODE_ALIASES - notice the latter is a Hash), and the user may provide # the shortest unambiguous text. code_list = (CODE_ALIASES.keys + CODES).join(', ') parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding", "(#{code_list})") do |encoding| self.encoding = encoding end end def optional_option_argument_with_keyword_completion_option(parser) # Optional '--type' option argument with keyword completion. parser.on("--type [TYPE]", [:text, :binary, :auto], "Select transfer type (text, binary, auto)") do |t| self.transfer_type = t end end def boolean_verbose_option(parser) # Boolean switch. parser.on("-v", "--[no-]verbose", "Run verbosely") do |v| self.verbose = v end end end # # Return a structure describing the options. # def parse(args) # The options specified on the command line will be collected in # *options*. @options = ScriptOptions.new @args = OptionParser.new do |parser| @options.define_options(parser) parser.parse!(args) end @options end attr_reader :parser, :options end # class OptparseExample example = OptparseExample.new options = example.parse(ARGV) pp options # example.options pp ARGV
Completion
For modern shells (e.g. bash, zsh, etc.), you can use shell completion for command line options.
The above examples, along with the accompanying Tutorial, should be enough to learn how to use this class. If you have any questions, file a ticket at bugs.ruby-lang.org.
Raised when attempting to divide an integer by 0.
42 / 0 #=> ZeroDivisionError: divided by 0
Note that only division by an exact 0 will raise the exception:
42 / 0.0 #=> Float::INFINITY 42 / -0.0 #=> -Float::INFINITY 0 / 0.0 #=> NaN
The Comparable
mixin is used by classes whose objects may be ordered. The class must define the <=>
operator, which compares the receiver against another object, returning a value less than 0, returning 0, or returning a value greater than 0, depending on whether the receiver is less than, equal to, or greater than the other object. If the other object is not comparable then the <=>
operator should return nil
. Comparable
uses <=>
to implement the conventional comparison operators (<
, <=
, ==
, >=
, and >
) and the method between?
.
class StringSorter include Comparable attr :str def <=>(other) str.size <=> other.str.size end def initialize(str) @str = str end def inspect @str end end s1 = StringSorter.new("Z") s2 = StringSorter.new("YY") s3 = StringSorter.new("XXX") s4 = StringSorter.new("WWWW") s5 = StringSorter.new("VVVVV") s1 < s2 #=> true s4.between?(s1, s3) #=> false s4.between?(s3, s5) #=> true [ s3, s2, s5, s4, s1 ].sort #=> [Z, YY, XXX, WWWW, VVVVV]
Module Comparable provides these methods, all of which use method <=>:
<
: Returns whether self
is less than the given object.
<=
: Returns whether self
is less than or equal to the given object.
==
: Returns whether self
is equal to the given object.
>
: Returns whether self
is greater than the given object.
>=
: Returns whether self
is greater than or equal to the given object.
between?
: Returns true
if self
is between two given objects.
clamp
: For given objects min
and max
, or range (min..max)
, returns:
min
if (self <=> min) < 0
.
max
if (self <=> max) > 0
.
self
otherwise.
This module provides a framework for message digest libraries.
You may want to look at OpenSSL::Digest
as it supports more algorithms.
A cryptographic hash function is a procedure that takes data and returns a fixed bit string: the hash value, also known as digest. Hash
functions are also called one-way functions, it is easy to compute a digest from a message, but it is infeasible to generate a message from a digest.
require 'digest' # Compute a complete digest Digest::SHA256.digest 'message' #=> "\xABS\n\x13\xE4Y..." sha256 = Digest::SHA256.new sha256.digest 'message' #=> "\xABS\n\x13\xE4Y..." # Other encoding formats Digest::SHA256.hexdigest 'message' #=> "ab530a13e459..." Digest::SHA256.base64digest 'message' #=> "q1MKE+RZFJgr..." # Compute digest by chunks md5 = Digest::MD5.new md5.update 'message1' md5 << 'message2' # << is an alias for update md5.hexdigest #=> "94af09c09bb9..." # Compute digest for a file sha256 = Digest::SHA256.file 'testfile' sha256.hexdigest
Additionally digests can be encoded in “bubble babble” format as a sequence of consonants and vowels which is more recognizable and comparable than a hexadecimal digest.
require 'digest/bubblebabble' Digest::SHA256.bubblebabble 'message' #=> "xopoh-fedac-fenyh-..."
See the bubble babble specification at web.mit.edu/kenta/www/one/bubblebabble/spec/jrtrjwzi/draft-huima-01.txt.
Digest
algorithms Different digest algorithms (or hash functions) are available:
MD5
See RFC 1321 The MD5
Message-Digest Algorithm
As Digest::RMD160
. See homes.esat.kuleuven.be/~bosselae/ripemd160.html.
SHA1
See FIPS 180 Secure Hash
Standard.
SHA2
familySee FIPS 180 Secure Hash
Standard which defines the following algorithms:
The latest versions of the FIPS publications can be found here: csrc.nist.gov/publications/PubsFIPS.html.
JSON is a lightweight data-interchange format.
A JSON value is one of the following:
Double-quoted text: "foo"
.
Number: 1
, 1.0
, 2.0e2
.
Boolean: true
, false
.
Null: null
.
Array: an ordered list of values, enclosed by square brackets:
["foo", 1, 1.0, 2.0e2, true, false, null]
Object: a collection of name/value pairs, enclosed by curly braces; each name is double-quoted text; the values may be any JSON values:
{"a": "foo", "b": 1, "c": 1.0, "d": 2.0e2, "e": true, "f": false, "g": null}
A JSON array or object may contain nested arrays, objects, and scalars to any depth:
{"foo": {"bar": 1, "baz": 2}, "bat": [0, 1, 2]} [{"foo": 0, "bar": 1}, ["baz", 2]]
To make module JSON available in your code, begin with:
require 'json'
All examples here assume that this has been done.
You can parse a String containing JSON data using either of two methods:
where
source
is a Ruby object.
opts
is a Hash object containing options that control both input allowed and output formatting.
The difference between the two methods is that JSON.parse!
omits some checks and may not be safe for some source
data; use it only for data from trusted sources. Use the safer method JSON.parse
for less trusted sources.
When source
is a JSON array, JSON.parse
by default returns a Ruby Array:
json = '["foo", 1, 1.0, 2.0e2, true, false, null]' ruby = JSON.parse(json) ruby # => ["foo", 1, 1.0, 200.0, true, false, nil] ruby.class # => Array
The JSON array may contain nested arrays, objects, and scalars to any depth:
json = '[{"foo": 0, "bar": 1}, ["baz", 2]]' JSON.parse(json) # => [{"foo"=>0, "bar"=>1}, ["baz", 2]]
When the source is a JSON object, JSON.parse
by default returns a Ruby Hash:
json = '{"a": "foo", "b": 1, "c": 1.0, "d": 2.0e2, "e": true, "f": false, "g": null}' ruby = JSON.parse(json) ruby # => {"a"=>"foo", "b"=>1, "c"=>1.0, "d"=>200.0, "e"=>true, "f"=>false, "g"=>nil} ruby.class # => Hash
The JSON object may contain nested arrays, objects, and scalars to any depth:
json = '{"foo": {"bar": 1, "baz": 2}, "bat": [0, 1, 2]}' JSON.parse(json) # => {"foo"=>{"bar"=>1, "baz"=>2}, "bat"=>[0, 1, 2]}
When the source is a JSON scalar (not an array or object), JSON.parse
returns a Ruby scalar.
String:
ruby = JSON.parse('"foo"') ruby # => 'foo' ruby.class # => String
Integer:
ruby = JSON.parse('1') ruby # => 1 ruby.class # => Integer
Float:
ruby = JSON.parse('1.0') ruby # => 1.0 ruby.class # => Float ruby = JSON.parse('2.0e2') ruby # => 200 ruby.class # => Float
Boolean:
ruby = JSON.parse('true') ruby # => true ruby.class # => TrueClass ruby = JSON.parse('false') ruby # => false ruby.class # => FalseClass
Null:
ruby = JSON.parse('null') ruby # => nil ruby.class # => NilClass
Option max_nesting
(Integer) specifies the maximum nesting depth allowed; defaults to 100
; specify false
to disable depth checking.
With the default, false
:
source = '[0, [1, [2, [3]]]]' ruby = JSON.parse(source) ruby # => [0, [1, [2, [3]]]]
Too deep:
# Raises JSON::NestingError (nesting of 2 is too deep): JSON.parse(source, {max_nesting: 1})
Bad value:
# Raises TypeError (wrong argument type Symbol (expected Fixnum)): JSON.parse(source, {max_nesting: :foo})
Option allow_nan
(boolean) specifies whether to allow NaN
, Infinity
, and MinusInfinity
in source
; defaults to false
.
With the default, false
:
# Raises JSON::ParserError (225: unexpected token at '[NaN]'): JSON.parse('[NaN]') # Raises JSON::ParserError (232: unexpected token at '[Infinity]'): JSON.parse('[Infinity]') # Raises JSON::ParserError (248: unexpected token at '[-Infinity]'): JSON.parse('[-Infinity]')
Allow:
source = '[NaN, Infinity, -Infinity]' ruby = JSON.parse(source, {allow_nan: true}) ruby # => [NaN, Infinity, -Infinity]
Option symbolize_names
(boolean) specifies whether returned Hash keys should be Symbols; defaults to false
(use Strings).
With the default, false
:
source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}' ruby = JSON.parse(source) ruby # => {"a"=>"foo", "b"=>1.0, "c"=>true, "d"=>false, "e"=>nil}
Use Symbols:
ruby = JSON.parse(source, {symbolize_names: true}) ruby # => {:a=>"foo", :b=>1.0, :c=>true, :d=>false, :e=>nil}
Option object_class
(Class) specifies the Ruby class to be used for each JSON object; defaults to Hash.
With the default, Hash:
source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}' ruby = JSON.parse(source) ruby.class # => Hash
Use class OpenStruct:
ruby = JSON.parse(source, {object_class: OpenStruct}) ruby # => #<OpenStruct a="foo", b=1.0, c=true, d=false, e=nil>
Option array_class
(Class) specifies the Ruby class to be used for each JSON array; defaults to Array.
With the default, Array:
source = '["foo", 1.0, true, false, null]' ruby = JSON.parse(source) ruby.class # => Array
Use class Set:
ruby = JSON.parse(source, {array_class: Set}) ruby # => #<Set: {"foo", 1.0, true, false, nil}>
Option create_additions
(boolean) specifies whether to use JSON additions in parsing. See JSON Additions.
To generate a Ruby String containing JSON data, use method JSON.generate(source, opts)
, where
source
is a Ruby object.
opts
is a Hash object containing options that control both input allowed and output formatting.
When the source is a Ruby Array, JSON.generate
returns a String containing a JSON array:
ruby = [0, 's', :foo] json = JSON.generate(ruby) json # => '[0,"s","foo"]'
The Ruby Array array may contain nested arrays, hashes, and scalars to any depth:
ruby = [0, [1, 2], {foo: 3, bar: 4}] json = JSON.generate(ruby) json # => '[0,[1,2],{"foo":3,"bar":4}]'
When the source is a Ruby Hash, JSON.generate
returns a String containing a JSON object:
ruby = {foo: 0, bar: 's', baz: :bat} json = JSON.generate(ruby) json # => '{"foo":0,"bar":"s","baz":"bat"}'
The Ruby Hash array may contain nested arrays, hashes, and scalars to any depth:
ruby = {foo: [0, 1], bar: {baz: 2, bat: 3}, bam: :bad} json = JSON.generate(ruby) json # => '{"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}'
When the source is neither an Array nor a Hash, the generated JSON data depends on the class of the source.
When the source is a Ruby Integer or Float, JSON.generate
returns a String containing a JSON number:
JSON.generate(42) # => '42' JSON.generate(0.42) # => '0.42'
When the source is a Ruby String, JSON.generate
returns a String containing a JSON string (with double-quotes):
JSON.generate('A string') # => '"A string"'
When the source is true
, false
or nil
, JSON.generate
returns a String containing the corresponding JSON token:
JSON.generate(true) # => 'true' JSON.generate(false) # => 'false' JSON.generate(nil) # => 'null'
When the source is none of the above, JSON.generate
returns a String containing a JSON string representation of the source:
JSON.generate(:foo) # => '"foo"' JSON.generate(Complex(0, 0)) # => '"0+0i"' JSON.generate(Dir.new('.')) # => '"#<Dir>"'
Option allow_nan
(boolean) specifies whether NaN
, Infinity
, and -Infinity
may be generated; defaults to false
.
With the default, false
:
# Raises JSON::GeneratorError (920: NaN not allowed in JSON): JSON.generate(JSON::NaN) # Raises JSON::GeneratorError (917: Infinity not allowed in JSON): JSON.generate(JSON::Infinity) # Raises JSON::GeneratorError (917: -Infinity not allowed in JSON): JSON.generate(JSON::MinusInfinity)
Allow:
ruby = [Float::NaN, Float::Infinity, Float::MinusInfinity] JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,-Infinity]'
Option max_nesting
(Integer) specifies the maximum nesting depth in obj
; defaults to 100
.
With the default, 100
:
obj = [[[[[[0]]]]]] JSON.generate(obj) # => '[[[[[[0]]]]]]'
Too deep:
# Raises JSON::NestingError (nesting of 2 is too deep): JSON.generate(obj, max_nesting: 2)
Options script_safe
(boolean) specifies wether '\u2028'
, '\u2029'
and '/'
should be escaped as to make the JSON
object safe to interpolate in script tags.
Options ascii_only
(boolean) specifies wether all characters outside the ASCII range should be escaped.
The default formatting options generate the most compact JSON data, all on one line and with no whitespace.
You can use these formatting options to generate JSON data in a more open format, using whitespace. See also JSON.pretty_generate
.
Option array_nl
(String) specifies a string (usually a newline) to be inserted after each JSON array; defaults to the empty String, ''
.
Option object_nl
(String) specifies a string (usually a newline) to be inserted after each JSON object; defaults to the empty String, ''
.
Option indent
(String) specifies the string (usually spaces) to be used for indentation; defaults to the empty String, ''
; defaults to the empty String, ''
; has no effect unless options array_nl
or object_nl
specify newlines.
Option space
(String) specifies a string (usually a space) to be inserted after the colon in each JSON object’s pair; defaults to the empty String, ''
.
Option space_before
(String) specifies a string (usually a space) to be inserted before the colon in each JSON object’s pair; defaults to the empty String, ''
.
In this example, obj
is used first to generate the shortest JSON data (no whitespace), then again with all formatting options specified:
obj = {foo: [:bar, :baz], bat: {bam: 0, bad: 1}} json = JSON.generate(obj) puts 'Compact:', json opts = { array_nl: "\n", object_nl: "\n", indent: ' ', space_before: ' ', space: ' ' } puts 'Open:', JSON.generate(obj, opts)
Output:
Compact: {"foo":["bar","baz"],"bat":{"bam":0,"bad":1}} Open: { "foo" : [ "bar", "baz" ], "bat" : { "bam" : 0, "bad" : 1 } }
When you “round trip” a non-String object from Ruby to JSON and back, you have a new String, instead of the object you began with:
ruby0 = Range.new(0, 2) json = JSON.generate(ruby0) json # => '0..2"' ruby1 = JSON.parse(json) ruby1 # => '0..2' ruby1.class # => String
You can use JSON additions to preserve the original object. The addition is an extension of a ruby class, so that:
JSON.generate stores more information in the JSON string.
JSON.parse, called with option create_additions
, uses that information to create a proper Ruby object.
This example shows a Range being generated into JSON and parsed back into Ruby, both without and with the addition for Range:
ruby = Range.new(0, 2) # This passage does not use the addition for Range. json0 = JSON.generate(ruby) ruby0 = JSON.parse(json0) # This passage uses the addition for Range. require 'json/add/range' json1 = JSON.generate(ruby) ruby1 = JSON.parse(json1, create_additions: true) # Make a nice display. display = <<~EOT Generated JSON: Without addition: #{json0} (#{json0.class}) With addition: #{json1} (#{json1.class}) Parsed JSON: Without addition: #{ruby0.inspect} (#{ruby0.class}) With addition: #{ruby1.inspect} (#{ruby1.class}) EOT puts display
This output shows the different results:
Generated JSON: Without addition: "0..2" (String) With addition: {"json_class":"Range","a":[0,2,false]} (String) Parsed JSON: Without addition: "0..2" (String) With addition: 0..2 (Range)
The JSON module includes additions for certain classes. You can also craft custom additions. See Custom JSON Additions.
The JSON module includes additions for certain classes. To use an addition, require
its source:
BigDecimal: require 'json/add/bigdecimal'
Complex: require 'json/add/complex'
Date: require 'json/add/date'
DateTime: require 'json/add/date_time'
Exception: require 'json/add/exception'
OpenStruct: require 'json/add/ostruct'
Range: require 'json/add/range'
Rational: require 'json/add/rational'
Regexp: require 'json/add/regexp'
Set: require 'json/add/set'
Struct: require 'json/add/struct'
Symbol: require 'json/add/symbol'
Time: require 'json/add/time'
To reduce punctuation clutter, the examples below show the generated JSON via puts
, rather than the usual inspect
,
BigDecimal:
require 'json/add/bigdecimal' ruby0 = BigDecimal(0) # 0.0 json = JSON.generate(ruby0) # {"json_class":"BigDecimal","b":"27:0.0"} ruby1 = JSON.parse(json, create_additions: true) # 0.0 ruby1.class # => BigDecimal
Complex:
require 'json/add/complex' ruby0 = Complex(1+0i) # 1+0i json = JSON.generate(ruby0) # {"json_class":"Complex","r":1,"i":0} ruby1 = JSON.parse(json, create_additions: true) # 1+0i ruby1.class # Complex
Date:
require 'json/add/date' ruby0 = Date.today # 2020-05-02 json = JSON.generate(ruby0) # {"json_class":"Date","y":2020,"m":5,"d":2,"sg":2299161.0} ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 ruby1.class # Date
DateTime:
require 'json/add/date_time' ruby0 = DateTime.now # 2020-05-02T10:38:13-05:00 json = JSON.generate(ruby0) # {"json_class":"DateTime","y":2020,"m":5,"d":2,"H":10,"M":38,"S":13,"of":"-5/24","sg":2299161.0} ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02T10:38:13-05:00 ruby1.class # DateTime
Exception (and its subclasses including RuntimeError):
require 'json/add/exception' ruby0 = Exception.new('A message') # A message json = JSON.generate(ruby0) # {"json_class":"Exception","m":"A message","b":null} ruby1 = JSON.parse(json, create_additions: true) # A message ruby1.class # Exception ruby0 = RuntimeError.new('Another message') # Another message json = JSON.generate(ruby0) # {"json_class":"RuntimeError","m":"Another message","b":null} ruby1 = JSON.parse(json, create_additions: true) # Another message ruby1.class # RuntimeError
OpenStruct:
require 'json/add/ostruct' ruby0 = OpenStruct.new(name: 'Matz', language: 'Ruby') # #<OpenStruct name="Matz", language="Ruby"> json = JSON.generate(ruby0) # {"json_class":"OpenStruct","t":{"name":"Matz","language":"Ruby"}} ruby1 = JSON.parse(json, create_additions: true) # #<OpenStruct name="Matz", language="Ruby"> ruby1.class # OpenStruct
Range:
require 'json/add/range' ruby0 = Range.new(0, 2) # 0..2 json = JSON.generate(ruby0) # {"json_class":"Range","a":[0,2,false]} ruby1 = JSON.parse(json, create_additions: true) # 0..2 ruby1.class # Range
Rational:
require 'json/add/rational' ruby0 = Rational(1, 3) # 1/3 json = JSON.generate(ruby0) # {"json_class":"Rational","n":1,"d":3} ruby1 = JSON.parse(json, create_additions: true) # 1/3 ruby1.class # Rational
Regexp:
require 'json/add/regexp' ruby0 = Regexp.new('foo') # (?-mix:foo) json = JSON.generate(ruby0) # {"json_class":"Regexp","o":0,"s":"foo"} ruby1 = JSON.parse(json, create_additions: true) # (?-mix:foo) ruby1.class # Regexp
Set:
require 'json/add/set' ruby0 = Set.new([0, 1, 2]) # #<Set: {0, 1, 2}> json = JSON.generate(ruby0) # {"json_class":"Set","a":[0,1,2]} ruby1 = JSON.parse(json, create_additions: true) # #<Set: {0, 1, 2}> ruby1.class # Set
Struct:
require 'json/add/struct' Customer = Struct.new(:name, :address) # Customer ruby0 = Customer.new("Dave", "123 Main") # #<struct Customer name="Dave", address="123 Main"> json = JSON.generate(ruby0) # {"json_class":"Customer","v":["Dave","123 Main"]} ruby1 = JSON.parse(json, create_additions: true) # #<struct Customer name="Dave", address="123 Main"> ruby1.class # Customer
Symbol:
require 'json/add/symbol' ruby0 = :foo # foo json = JSON.generate(ruby0) # {"json_class":"Symbol","s":"foo"} ruby1 = JSON.parse(json, create_additions: true) # foo ruby1.class # Symbol
Time:
require 'json/add/time' ruby0 = Time.now # 2020-05-02 11:28:26 -0500 json = JSON.generate(ruby0) # {"json_class":"Time","s":1588436906,"n":840560000} ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 11:28:26 -0500 ruby1.class # Time
In addition to the JSON additions provided, you can craft JSON additions of your own, either for Ruby built-in classes or for user-defined classes.
Here’s a user-defined class Foo
:
class Foo attr_accessor :bar, :baz def initialize(bar, baz) self.bar = bar self.baz = baz end end
Here’s the JSON addition for it:
# Extend class Foo with JSON addition. class Foo # Serialize Foo object with its class name and arguments def to_json(*args) { JSON.create_id => self.class.name, 'a' => [ bar, baz ] }.to_json(*args) end # Deserialize JSON string by constructing new Foo object with arguments. def self.json_create(object) new(*object['a']) end end
Demonstration:
require 'json' # This Foo object has no custom addition. foo0 = Foo.new(0, 1) json0 = JSON.generate(foo0) obj0 = JSON.parse(json0) # Lood the custom addition. require_relative 'foo_addition' # This foo has the custom addition. foo1 = Foo.new(0, 1) json1 = JSON.generate(foo1) obj1 = JSON.parse(json1, create_additions: true) # Make a nice display. display = <<~EOT Generated JSON: Without custom addition: #{json0} (#{json0.class}) With custom addition: #{json1} (#{json1.class}) Parsed JSON: Without custom addition: #{obj0.inspect} (#{obj0.class}) With custom addition: #{obj1.inspect} (#{obj1.class}) EOT puts display
Output:
Generated JSON: Without custom addition: "#<Foo:0x0000000006534e80>" (String) With custom addition: {"json_class":"Foo","a":[0,1]} (String) Parsed JSON: Without custom addition: "#<Foo:0x0000000006534e80>" (String) With custom addition: #<Foo:0x0000000006473bb8 @bar=0, @baz=1> (Foo)
OpenSSL
provides SSL
, TLS and general purpose cryptography. It wraps the OpenSSL library.
All examples assume you have loaded OpenSSL
with:
require 'openssl'
These examples build atop each other. For example the key created in the next is used in throughout these examples.
This example creates a 2048 bit RSA keypair and writes it to the current directory.
key = OpenSSL::PKey::RSA.new 2048 File.write 'private_key.pem', key.private_to_pem File.write 'public_key.pem', key.public_to_pem
Keys saved to disk without encryption are not secure as anyone who gets ahold of the key may use it unless it is encrypted. In order to securely export a key you may export it with a password.
cipher = OpenSSL::Cipher.new 'aes-256-cbc' password = 'my secure password goes here' key_secure = key.private_to_pem cipher, password File.write 'private.secure.pem', key_secure
OpenSSL::Cipher.ciphers
returns a list of available ciphers.
A key can also be loaded from a file.
key2 = OpenSSL::PKey.read File.read 'private_key.pem' key2.public? # => true key2.private? # => true
or
key3 = OpenSSL::PKey.read File.read 'public_key.pem' key3.public? # => true key3.private? # => false
OpenSSL
will prompt you for your password when loading an encrypted key. If you will not be able to type in the password you may provide it when loading the key:
key4_pem = File.read 'private.secure.pem' password = 'my secure password goes here' key4 = OpenSSL::PKey.read key4_pem, password
RSA provides encryption and decryption using the public and private keys. You can use a variety of padding methods depending upon the intended use of encrypted data.
Asymmetric public/private key encryption is slow and victim to attack in cases where it is used without padding or directly to encrypt larger chunks of data. Typical use cases for RSA encryption involve “wrapping” a symmetric key with the public key of the recipient who would “unwrap” that symmetric key again using their private key. The following illustrates a simplified example of such a key transport scheme. It shouldn’t be used in practice, though, standardized protocols should always be preferred.
wrapped_key = key.public_encrypt key
A symmetric key encrypted with the public key can only be decrypted with the corresponding private key of the recipient.
original_key = key.private_decrypt wrapped_key
By default PKCS#1 padding will be used, but it is also possible to use other forms of padding, see PKey::RSA
for further details.
Using “private_encrypt” to encrypt some data with the private key is equivalent to applying a digital signature to the data. A verifying party may validate the signature by comparing the result of decrypting the signature with “public_decrypt” to the original data. However, OpenSSL::PKey
already has methods “sign” and “verify” that handle digital signatures in a standardized way - “private_encrypt” and “public_decrypt” shouldn’t be used in practice.
To sign a document, a cryptographically secure hash of the document is computed first, which is then signed using the private key.
signature = key.sign 'SHA256', document
To validate the signature, again a hash of the document is computed and the signature is decrypted using the public key. The result is then compared to the hash just computed, if they are equal the signature was valid.
if key.verify 'SHA256', signature, document puts 'Valid' else puts 'Invalid' end
If supported by the underlying OpenSSL
version used, Password-based Encryption should use the features of PKCS5
. If not supported or if required by legacy applications, the older, less secure methods specified in RFC 2898 are also supported (see below).
PKCS5
supports PBKDF2 as it was specified in PKCS#5 v2.0. It still uses a password, a salt, and additionally a number of iterations that will slow the key derivation process down. The slower this is, the more work it requires being able to brute-force the resulting key.
The strategy is to first instantiate a Cipher
for encryption, and then to generate a random IV plus a key derived from the password using PBKDF2. PKCS #5 v2.0 recommends at least 8 bytes for the salt, the number of iterations largely depends on the hardware being used.
cipher = OpenSSL::Cipher.new 'aes-256-cbc' cipher.encrypt iv = cipher.random_iv pwd = 'some hopefully not to easily guessable password' salt = OpenSSL::Random.random_bytes 16 iter = 20000 key_len = cipher.key_len digest = OpenSSL::Digest.new('SHA256') key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) cipher.key = key Now encrypt the data: encrypted = cipher.update document encrypted << cipher.final
Use the same steps as before to derive the symmetric AES key, this time setting the Cipher
up for decryption.
cipher = OpenSSL::Cipher.new 'aes-256-cbc' cipher.decrypt cipher.iv = iv # the one generated with #random_iv pwd = 'some hopefully not to easily guessable password' salt = ... # the one generated above iter = 20000 key_len = cipher.key_len digest = OpenSSL::Digest.new('SHA256') key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest) cipher.key = key Now decrypt the data: decrypted = cipher.update encrypted decrypted << cipher.final
X509
Certificates This example creates a self-signed certificate using an RSA key and a SHA1 signature.
key = OpenSSL::PKey::RSA.new 2048 name = OpenSSL::X509::Name.parse '/CN=nobody/DC=example' cert = OpenSSL::X509::Certificate.new cert.version = 2 cert.serial = 0 cert.not_before = Time.now cert.not_after = Time.now + 3600 cert.public_key = key.public_key cert.subject = name
You can add extensions to the certificate with OpenSSL::SSL::ExtensionFactory to indicate the purpose of the certificate.
extension_factory = OpenSSL::X509::ExtensionFactory.new nil, cert cert.add_extension \ extension_factory.create_extension('basicConstraints', 'CA:FALSE', true) cert.add_extension \ extension_factory.create_extension( 'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature') cert.add_extension \ extension_factory.create_extension('subjectKeyIdentifier', 'hash')
The list of supported extensions (and in some cases their possible values) can be derived from the “objects.h” file in the OpenSSL
source code.
To sign a certificate set the issuer and use OpenSSL::X509::Certificate#sign
with a digest algorithm. This creates a self-signed cert because we’re using the same name and key to sign the certificate as was used to create the certificate.
cert.issuer = name cert.sign key, OpenSSL::Digest.new('SHA1') open 'certificate.pem', 'w' do |io| io.write cert.to_pem end
Like a key, a cert can also be loaded from a file.
cert2 = OpenSSL::X509::Certificate.new File.read 'certificate.pem'
Certificate#verify will return true when a certificate was signed with the given public key.
raise 'certificate can not be verified' unless cert2.verify key
A certificate authority (CA) is a trusted third party that allows you to verify the ownership of unknown certificates. The CA issues key signatures that indicate it trusts the user of that key. A user encountering the key can verify the signature by using the CA’s public key.
CA keys are valuable, so we encrypt and save it to disk and make sure it is not readable by other users.
ca_key = OpenSSL::PKey::RSA.new 2048 password = 'my secure password goes here' cipher = 'aes-256-cbc' open 'ca_key.pem', 'w', 0400 do |io| io.write ca_key.private_to_pem(cipher, password) end
A CA certificate is created the same way we created a certificate above, but with different extensions.
ca_name = OpenSSL::X509::Name.parse '/CN=ca/DC=example' ca_cert = OpenSSL::X509::Certificate.new ca_cert.serial = 0 ca_cert.version = 2 ca_cert.not_before = Time.now ca_cert.not_after = Time.now + 86400 ca_cert.public_key = ca_key.public_key ca_cert.subject = ca_name ca_cert.issuer = ca_name extension_factory = OpenSSL::X509::ExtensionFactory.new extension_factory.subject_certificate = ca_cert extension_factory.issuer_certificate = ca_cert ca_cert.add_extension \ extension_factory.create_extension('subjectKeyIdentifier', 'hash')
This extension indicates the CA’s key may be used as a CA.
ca_cert.add_extension \ extension_factory.create_extension('basicConstraints', 'CA:TRUE', true)
This extension indicates the CA’s key may be used to verify signatures on both certificates and certificate revocations.
ca_cert.add_extension \ extension_factory.create_extension( 'keyUsage', 'cRLSign,keyCertSign', true)
Root CA certificates are self-signed.
ca_cert.sign ca_key, OpenSSL::Digest.new('SHA1')
The CA certificate is saved to disk so it may be distributed to all the users of the keys this CA will sign.
open 'ca_cert.pem', 'w' do |io| io.write ca_cert.to_pem end
The CA signs keys through a Certificate Signing Request (CSR). The CSR contains the information necessary to identify the key.
csr = OpenSSL::X509::Request.new csr.version = 0 csr.subject = name csr.public_key = key.public_key csr.sign key, OpenSSL::Digest.new('SHA1')
A CSR is saved to disk and sent to the CA for signing.
open 'csr.pem', 'w' do |io| io.write csr.to_pem end
Upon receiving a CSR the CA will verify it before signing it. A minimal verification would be to check the CSR’s signature.
csr = OpenSSL::X509::Request.new File.read 'csr.pem' raise 'CSR can not be verified' unless csr.verify csr.public_key
After verification a certificate is created, marked for various usages, signed with the CA key and returned to the requester.
csr_cert = OpenSSL::X509::Certificate.new csr_cert.serial = 0 csr_cert.version = 2 csr_cert.not_before = Time.now csr_cert.not_after = Time.now + 600 csr_cert.subject = csr.subject csr_cert.public_key = csr.public_key csr_cert.issuer = ca_cert.subject extension_factory = OpenSSL::X509::ExtensionFactory.new extension_factory.subject_certificate = csr_cert extension_factory.issuer_certificate = ca_cert csr_cert.add_extension \ extension_factory.create_extension('basicConstraints', 'CA:FALSE') csr_cert.add_extension \ extension_factory.create_extension( 'keyUsage', 'keyEncipherment,dataEncipherment,digitalSignature') csr_cert.add_extension \ extension_factory.create_extension('subjectKeyIdentifier', 'hash') csr_cert.sign ca_key, OpenSSL::Digest.new('SHA1') open 'csr_cert.pem', 'w' do |io| io.write csr_cert.to_pem end
SSL
and TLS Connections Using our created key and certificate we can create an SSL
or TLS connection. An SSLContext is used to set up an SSL
session.
context = OpenSSL::SSL::SSLContext.new
SSL
Server An SSL
server requires the certificate and private key to communicate securely with its clients:
context.cert = cert context.key = key
Then create an SSLServer with a TCP server socket and the context. Use the SSLServer like an ordinary TCP server.
require 'socket' tcp_server = TCPServer.new 5000 ssl_server = OpenSSL::SSL::SSLServer.new tcp_server, context loop do ssl_connection = ssl_server.accept data = ssl_connection.gets response = "I got #{data.dump}" puts response ssl_connection.puts "I got #{data.dump}" ssl_connection.close end
SSL
client An SSL
client is created with a TCP socket and the context. SSLSocket#connect must be called to initiate the SSL
handshake and start encryption. A key and certificate are not required for the client socket.
Note that SSLSocket#close doesn’t close the underlying socket by default. Set
SSLSocket#sync_close to true if you want.
require 'socket' tcp_socket = TCPSocket.new 'localhost', 5000 ssl_client = OpenSSL::SSL::SSLSocket.new tcp_socket, context ssl_client.sync_close = true ssl_client.connect ssl_client.puts "hello server!" puts ssl_client.gets ssl_client.close # shutdown the TLS connection and close tcp_socket
An unverified SSL
connection does not provide much security. For enhanced security the client or server can verify the certificate of its peer.
The client can be modified to verify the server’s certificate against the certificate authority’s certificate:
context.ca_file = 'ca_cert.pem' context.verify_mode = OpenSSL::SSL::VERIFY_PEER require 'socket' tcp_socket = TCPSocket.new 'localhost', 5000 ssl_client = OpenSSL::SSL::SSLSocket.new tcp_socket, context ssl_client.connect ssl_client.puts "hello server!" puts ssl_client.gets
If the server certificate is invalid or context.ca_file
is not set when verifying peers an OpenSSL::SSL::SSLError
will be raised.
FileTest
implements file test operations similar to those used in File::Stat
. It exists as a standalone module, and its methods are also insinuated into the File
class. (Note that this is not done by inclusion: the interpreter cheats).
The Singleton
module implements the Singleton
pattern.
To use Singleton
, include the module in your class.
class Klass include Singleton # ... end
This ensures that only one instance of Klass can be created.
a,b = Klass.instance, Klass.instance a == b # => true Klass.new # => NoMethodError - new is private ...
The instance is created at upon the first call of Klass.instance().
class OtherKlass include Singleton # ... end ObjectSpace.each_object(OtherKlass){} # => 0 OtherKlass.instance ObjectSpace.each_object(OtherKlass){} # => 1
This behavior is preserved under inheritance and cloning.
This above is achieved by:
Making Klass.new and Klass.allocate private.
Overriding Klass.inherited(sub_klass) and Klass.clone() to ensure that the Singleton
properties are kept when inherited and cloned.
Providing the Klass.instance() method that returns the same object each time it is called.
Overriding Klass._load(str) to call Klass.instance().
Overriding Klass#clone and Klass#dup to raise TypeErrors to prevent cloning or duping.
Singleton
and Marshal
By default Singleton’s _dump(depth)
returns the empty string. Marshalling by default will strip state information, e.g. instance variables from the instance. Classes using Singleton
can provide custom _load(str) and _dump(depth) methods to retain some of the previous state of the instance.
require 'singleton' class Example include Singleton attr_accessor :keep, :strip def _dump(depth) # this strips the @strip information from the instance Marshal.dump(@keep, depth) end def self._load(str) instance.keep = Marshal.load(str) instance end end a = Example.instance a.keep = "keep this" a.strip = "get rid of this" stored_state = Marshal.dump(a) a.keep = nil a.strip = nil b = Marshal.load(stored_state) p a == b # => true p a.keep # => "keep this" p a.strip # => nil
define UnicodeNormalize module here so that we don’t have to look it up
Configuration for the openssl library.
Many system’s installation of openssl library will depend on your system configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE
for the location of the file for your host.
General error for openssl library configuration files. Including formatting, parsing errors, etc.
Response class for Continue
responses (status code 100).
A Continue
response indicates that the server has received the request headers.
References:
Response class for No Content
responses (status code 204).
The No Content
response indicates that the server successfully processed the request, and is not returning any content.
References:
Response class for Partial Content
responses (status code 206).
The Partial Content
response indicates that the server is delivering only part of the resource (byte serving) due to a Range
header in the request.
References:
Response class for Conflict
responses (status code 409).
The request could not be processed because of conflict in the current state of the resource.
References:
Response class for Insufficient Storage (WebDAV)
responses (status code 507).
The server is unable to store the representation needed to complete the request.
References:
Represents the use of the ‘&&=` operator for assignment to an instance variable.
@target &&= value ^^^^^^^^^^^^^^^^^
Represents the use of the ‘||=` operator for assignment to an instance variable.
@target ||= value ^^^^^^^^^^^^^^^^^