Class: SleepingKingStudios::Tools::ObjectTools

Inherits:
Base
  • Object
show all
Defined in:
lib/sleeping_king_studios/tools/object_tools.rb

Overview

Low-level tools for working with objects.

Instance Method Summary collapse

Methods inherited from Base

#initialize, instance, #toolbelt

Constructor Details

This class inherits a constructor from SleepingKingStudios::Tools::Base

Instance Method Details

#apply(receiver, proc, *args, **kwargs, &block) ⇒ Object

Calls a Proc or lambda on the given receiver with the given parameters.

Unlike calling #instance_exec with the block, ObjectTools#apply allows you to specify a block parameter.

Examples:

my_object = double('object', :to_s => 'A mock object')
my_proc   = ->() { puts %{#{self.to_s} says "Greetings, programs!"} }

ObjectTools.apply my_object, my_proc
#=> Writes 'A mock object says "Greetings, programs!"' to STDOUT.

Parameters:

  • receiver (Object)

    The receiver. the proc will be called in the context of this object.

  • proc (Proc)

    the proc or lambda to call.

  • args (Array)

    optional. Additional arguments to pass in to the proc or lambda.

  • kwargs (Hash)

    optional. Additional keywords to pass in to the proc or lambda.

  • block (Proc)

    optional. If present, will be passed in to proc or lambda.

Returns:

  • (Object)

    the result of calling the proc or lambda with the given receiver and any additional arguments or block.



54
55
56
57
58
59
60
61
62
63
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 54

def apply(receiver, proc, *args, **kwargs, &block)
  return receiver.instance_exec(*args, **kwargs, &proc) unless block_given?

  method_name =
    Kernel.format(TEMPORARY_METHOD_NAME, Thread.current.object_id)

  with_temporary_method(receiver, method_name, proc) do
    receiver.send(method_name, *args, **kwargs, &block)
  end
end

#deep_dup(obj) ⇒ Object

Creates a deep copy of the object.

If the object is an Array, returns a new Array with deep copies of each array item. If the object is a Hash, returns a new Hash with deep copies of each hash key and value. Otherwise, returns Object#dup.

Examples:

data = {
  :songs => [
    {
      :name   => 'Welcome to the Jungle',
      :artist => "Guns N' Roses",
      :album  => 'Appetite for Destruction'
    },
    {
      :name   => 'Hells Bells',
      :artist => 'AC/DC',
      :album  => 'Back in Black'
    },
    {
      :name   => "Knockin' on Heaven's Door",
      :artist => 'Bob Dylan',
      :album  => 'Pat Garrett & Billy The Kid'
    }
  ]
}

copy = ObjectTools.deep_dup data

copy[:songs] << { :name => 'Sympathy for the Devil', :artist => 'The Rolling Stones', :album => 'Beggars Banquet' }
data[:songs].count
#=> 3

copy[:songs][1][:name] = 'Shoot to Thrill'
data[:songs][1]
#=> { :name => 'Hells Bells', :artist => 'AC/DC', :album => 'Back in Black' }

Parameters:

  • obj (Object)

    the object to copy.

Returns:

  • the copy of the object.

See Also:

  • ArrayTools#deep_copy
  • HashTools#deep_copy


109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 109

def deep_dup(obj) # rubocop:disable Metrics/AbcSize
  case obj
  when FalseClass, Integer, Float, NilClass, Symbol, TrueClass
    obj
  when ->(_) { toolbelt.array_tools.array?(obj) }
    toolbelt.array_tools.deep_dup obj
  when ->(_) { toolbelt.hash_tools.hash?(obj) }
    toolbelt.hash_tools.deep_dup obj
  else
    obj.respond_to?(:deep_dup) ? obj.deep_dup : obj.dup
  end
end

#deep_freeze(obj) ⇒ Object

Performs a deep freeze of the object.

If the object is an Array, freezes the array and performs a deep freeze on each array item. If the object is a hash, freezes the hash and performs a deep freeze on each hash key and value. Otherwise, calls Object#freeze.

Examples:

data = {
  :songs => [
    {
      :name   => 'Welcome to the Jungle',
      :artist => "Guns N' Roses",
      :album  => 'Appetite for Destruction'
    },
    {
      :name   => 'Hells Bells',
      :artist => 'AC/DC',
      :album  => 'Back in Black'
    },
    {
      :name   => "Knockin' on Heaven's Door",
      :artist => 'Bob Dylan',
      :album  => 'Pat Garrett & Billy The Kid'
    }
  ]
}
ObjectTools.deep_freeze(data)

data.frozen?
#=> true
data[:songs].frozen?
#=> true
data[:songs][0].frozen?
#=> true
data[:songs][0].name.frozen?
#=> true

Parameters:

  • obj (Object)

    The object to freeze.

Returns:

  • (Object)

    the frozen object.



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 162

def deep_freeze(obj) # rubocop:disable Metrics/AbcSize
  case obj
  when FalseClass, Integer, Float, NilClass, Symbol, TrueClass
    # Object is inherently immutable; do nothing here.
  when ->(_) { toolbelt.array_tools.array?(obj) }
    toolbelt.array_tools.deep_freeze(obj)
  when ->(_) { toolbelt.hash_tools.hash?(obj) }
    toolbelt.hash_tools.deep_freeze obj
  else
    obj.respond_to?(:deep_freeze) ? obj.deep_freeze : obj.freeze
  end
end

#dig(obj, *method_names, indifferent_keys: false) ⇒ Object?

Accesses deeply nested properties on an object.

This method finds the first named property on the given object, and then each subsequent property on the result of the previous call.

  • If the object responds to the property name as a public method, it calls the method directly.

  • If the object does not respond to the property name, or if the property name is not itself a valid method name, checks if the object responds to #[]; if so, calls #[] with the property name.

  • Otherwise, immediately returns nil.

Using #[] access allows digging through Array (using integer indices) and Hash data structures as well as object methods.

Examples:

ObjectTools.dig(my_object, :first_method, :second_method, :third_method)
#=> my_object.first_method.second_method.third_method

Parameters:

  • obj (Object)

    the object to dig.

  • method_names (Array)

    the names of the methods to call.

  • indifferent_keys (true, false) (defaults to: false)

    if true, matches both String and Symbol keys when accessing a Hash value using #[]. Defaults to false.

Returns:

  • (Object, nil)

    the result of the last method call, or nil if the last object does not respond to the last method.



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 201

def dig(obj, *method_names, indifferent_keys: false)
  method_names.reduce(obj) do |memo, method_name|
    if object_responds_to_method?(memo, method_name)
      next memo.public_send(method_name)
    end

    next nil unless memo.respond_to?(:[])

    next memo[method_name] unless indifferent_keys

    indifferent_get(memo, method_name)
  end
rescue NameError
  nil
end

#eigenclass(obj) ⇒ Class Also known as: metaclass

Deprecated.

v1.3.0 Use Object#singleton_class instead.

Returns the object’s eigenclass.

Parameters:

  • obj (Object)

    the object for which an eigenclass is required.

Returns:

  • (Class)

    the object’s singleton class.



224
225
226
227
228
229
230
231
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 224

def eigenclass(obj)
  toolbelt.core_tools.deprecate(
    "#{self.class.name}#eigenclass",
    message: 'Use Object#singleton_class instead.'
  )

  obj.singleton_class
end

#fetch(obj, key, default = nil, indifferent_key: false) ⇒ Object #fetch(obj, key, indifferent_key: false) {|key| ... } ⇒ Object

Overloads:

  • #fetch(obj, key, default = nil, indifferent_key: false) ⇒ Object

    Retrieves the value at the specified method, key, or index.

    If the value does not exist, returns the default value, or raises am exception if there is no default value. If the object defines a native #fetch method, delegates to the native implementation.

    Parameters:

    • obj (Object)

      the baseobject.

    • key (Object)

      the key to retrieve.

    • indifferent_key (true, false) (defaults to: false)

      if true and the key is a String or a Symbol, tries to match both the String and Symbol equivalent. Defaults to false.

    • default (Object) (defaults to: nil)

      the default value.

    Returns:

    • (Object)

      the value at the specified key or index.

    Raises:

    • (IndexError, KeyError, NameError)

      if the object does not have a value for the requested key or index and there is no default value.

  • #fetch(obj, key, indifferent_key: false) {|key| ... } ⇒ Object

    Retrieves the value at the specified method, key, or index.

    If the value does not exist, returns the default value, or raises am exception if there is no default value. If the object defines a native #fetch method, delegates to the native implementation.

    Parameters:

    • obj (Object)

      the baseobject.

    • key (Object)

      the key to retrieve.

    • indifferent_key (true, false) (defaults to: false)

      if true and the key is a String or a Symbol, tries to match both the String and Symbol equivalent. Defaults to false.

    Yields:

    • generates the default value if there is no value at the key.

    Yield Parameters:

    • key (Object)

      the requested key.

    Yield Returns:

    • (Object)

      the default value.

    Returns:

    • (Object)

      the value at the specified key or index.

    Raises:

    • (IndexError, KeyError, NameError)

      if the object does not have a value for the requested key or index and there is no default value.



276
277
278
279
280
281
282
283
284
285
286
287
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 276

def fetch(obj, key_or_index, default = UNDEFINED, indifferent_key: false, &)
  case obj
  when ->(_) { object_responds_to_method?(obj, key_or_index) }
    obj.public_send(key_or_index)
  when ->(_) { toolbelt.array_tools.array?(obj) }
    fetch_array(obj, key_or_index, default, &)
  when ->(_) { toolbelt.hash_tools.hash?(obj) }
    fetch_hash(obj, key_or_index, default, indifferent_key:, &)
  else
    handle_invalid_fetch(obj, key_or_index, default, &)
  end
end

#format_inspect(obj, address: true, properties: {}) ⇒ String

Creates a string representation of the object and specified properties.

The string representation resembles the output of Object#inspect, but with support for BasicObjects and greater customization of the displayed properties.

Parameters:

  • obj (BasicObject)

    the object to inspect.

  • address (true, false) (defaults to: true)

    if true, includes the formatted memory address from Object#inspect. Defaults to true.

  • properties (Array, Hash) (defaults to: {})

    the properties to display. If the given properties are an Array, maps the values to instance variables or public methods based on the property name.

Returns:

  • (String)

    the string representation of the object.



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 303

def format_inspect(obj, address: true, properties: {}) # rubocop:disable Metrics/MethodLength
  str = "#<#{class_name(obj)}"

  if address
    native  = Object.instance_method(:inspect).bind(obj).call
    address = MEMORY_ADDRESS_PATTERN.match(native)&.[](:address)

    str << ':' << address
  end

  if properties.is_a?(Hash)
    format_properties_hash(str, **properties)
  elsif properties.is_a?(Enumerable)
    format_properties_array(str, obj, *properties)
  end

  str << '>'
end

#immutable?(obj) ⇒ Boolean

Checks if the object is immutable.

  • nil, false, and true are always immutable, as are instances of Numeric and Symbol.

  • Strings are immutable if frozen, such as strings defined in a file with a frozen_string_literal pragma.

  • Arrays are immutable if the array is frozen and each array item is immutable.

  • Hashes are immutable if the hash is frozen and each hash key and hash value are immutable.

  • Otherwise, objects are immutable if they are frozen.

Examples:

ObjectTools.immutable?(nil)
#=> true

ObjectTools.immutable?(false)
#=> true

ObjectTools.immutable?(0)
#=> true

ObjectTools.immutable?(:hello)
#=> true

ObjectTools.immutable?('Greetings, programs!')
#=> true

ObjectTools.immutable?(+'Greetings, programs!')
#=> false

ObjectTools.immutable?([1, 2, 3])
#=> false

ObjectTools.immutable?([1, 2, 3].freeze)
#=> false

Parameters:

  • obj (Object)

    the object to test.

Returns:

  • (Boolean)

    true if the object is immutable, otherwise false.

See Also:



368
369
370
371
372
373
374
375
376
377
378
379
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 368

def immutable?(obj)
  case obj
  when NilClass, FalseClass, TrueClass, Numeric, Symbol
    true
  when ->(_) { toolbelt.array_tools.array?(obj) }
    toolbelt.array_tools.immutable?(obj)
  when ->(_) { toolbelt.hash_tools.hash?(obj) }
    toolbelt.hash_tools.immutable?(obj)
  else
    obj.frozen?
  end
end

#mutable?(obj) ⇒ Boolean

Checks if the object is mutable.

Parameters:

  • obj (Object)

    the object to test.

Returns:

  • (Boolean)

    true if the object is mutable, otherwise false.

See Also:



388
389
390
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 388

def mutable?(obj)
  !immutable?(obj)
end

#object?(obj) ⇒ Boolean

Returns true if the object is an Object.

This should return false only for objects that have an alternate inheritance chain from BasicObject, such as a Proxy.

Examples:

ObjectTools.object?(nil)
#=> true

ObjectTools.object?([])
#=> true

ObjectTools.object?({})
#=> true

ObjectTools.object?(1)
#=> true

ObjectTools.object?(BasicObject.new)
#=> false

Parameters:

  • obj (Object)

    the object to test.

Returns:

  • (Boolean)

    true if the object is an Object, otherwise false.



416
417
418
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 416

def object?(obj)
  Object.instance_method(:is_a?).bind(obj).call(Object)
end

#try(obj, method_name, *args) ⇒ Object?

Deprecated.

v1.3.0 Use the safe access operator &. instead.

As #send, but returns nil if the object does not respond to the method.

This method relies on #respond_to?, so methods defined with method_missing will not be called.

Examples:

ObjectTools.try(%w(ichi ni san), :count)
#=> 3

ObjectTools.try(nil, :count)
#=> nil

Parameters:

  • obj (Object)

    the receiver of the message.

  • method_name (String, Symbol)

    the name of the method to call.

  • args (Array)

    the arguments to the message.

Returns:

  • (Object, nil)

    the return value of the called method, or nil if the object does not respond to the method.



441
442
443
444
445
446
447
448
449
450
451
452
# File 'lib/sleeping_king_studios/tools/object_tools.rb', line 441

def try(obj, method_name, *)
  toolbelt.core_tools.deprecate(
    "#{self.class.name}#try",
    message: 'Use the safe access operator &. instead.'
  )

  return obj.try(method_name, *) if obj.respond_to?(:try)

  return nil unless obj.respond_to?(method_name)

  obj.send(method_name, *)
end