Class: Object

Inherits:
BasicObject
Defined in:
lib/qualitysmith_extensions/object/mcall.rb,
lib/qualitysmith_extensions/object/default.rb,
lib/qualitysmith_extensions/object/if_else.rb,
lib/qualitysmith_extensions/object/methods.rb,
lib/qualitysmith_extensions/object/send_if.rb,
lib/qualitysmith_extensions/module/class_methods.rb,
lib/qualitysmith_extensions/object/ignore_access.rb,
lib/qualitysmith_extensions/object/singleton_send.rb,
lib/qualitysmith_extensions/object/send_if_not_nil.rb,
lib/qualitysmith_extensions/object/ancestry_of_method.rb

Overview

Author

Tyler Rick

Copyright

Copyright © 2007 QualitySmith, Inc.

License

Ruby License

Submit to Facets?

Maybe.

Developer notes

++

Instance Method Summary collapse

Instance Method Details

#ancestry_of_method(method_name) ⇒ Object

Returns the module/class which defined the given method. If more than one module/class defined the method, returns the closest ancestor to have defined it (would be self if it is defined in self).

This is (as far as I know – patches welcome) the method that would would be called if you actually called the method. So if you override

It does this by first checking searching the methods defined in each ancestor in turn (in the order that self.ancestors returns them) and returning the first module/class that satisfies the search.

This looks at the results of methods, which means that if you call this on a module/class, it will not return any instance methods, only class methods.

class Base
  def self.it; end
end
class SubWithIt < Base
  def self.it; end
end
class SubWithoutIt < Base
end
SubWithIt.ancestry_of_instance_method(:it)    # => SubWithIt  # (Stops with self)
SubWithoutIt.ancestry_of_instance_method(:it) # => Base       # (Goes one step up the ancestry tree)

If you call this on an object that is not a module or a class (in other words, if you call it on an instance of some class), then it will assume you actually want to know about an instance method defined in self.class or one of the ancestors of self.class. (Since non-modules don’t even technically have the concept of ancestors.) Therefore, this:

class Klass
  def it; end
end
o = Klass.new
o.ancestry_of_method(:it)                      # => Klass

is really just a shorthand way of doing:

o.class.ancestry_of_instance_method(:it)       # => Klass

If the method is a singleton method of self, it will return self:

class << (foo = SubWithIt.new)
  def it; end
end
foo.ancestry_of_method(:it) # => #<SubWithIt:0xb7e5614c>

Returns nil if it cannot be found in self or in any ancestor.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/qualitysmith_extensions/object/ancestry_of_method.rb', line 60

def ancestry_of_method(method_name)
  method_name = method_name.to_s
  (self if self.methods(false).include?(method_name)) \
  ||
  if self.is_a?(Module)
    self.ancestors.find do |ancestor|
      ancestor.methods(false).include? method_name
    end or
    # The above search does not take into account *instance* methods provided by Class, Module, or Kernel.
    # Remember that ancestors and instances/class/superclass are different concepts, and that although classes/modules
    # do not have Class or Module as an "ancestor", they are still *instances* of Module or Class (which is a subclass of module).
    # self.ancestors does NOT include Class or Module, and yet we're still able to "inherit" instance methods from Class or Module.
    # So we have to do this extra search in case the method came from one of the instance methods of Class or Module or Kernel
    # (are there any other cases I'm missing?).
    begin
      # self.class.ancestors is usually [Class, Module, Object, PP::ObjectMixin, Kernel]
      self.class.ancestors.find do |ancestor|
        ancestor.instance_methods(false).include? method_name 
        # || ancestor.private_instance_method_defined?( method_name.to_sym )
      end
    end
  else
    self.class.ancestry_of_instance_method(method_name)
  end
end

#class_methods(include_super = true) ⇒ Object Also known as: module_methods



11
12
13
14
15
16
17
# File 'lib/qualitysmith_extensions/module/class_methods.rb', line 11

def class_methods(include_super = true)
  (
      methods(include_super) \
    - instance_methods
  ).
    sort
end

#default!(default_value) ⇒ Object



31
32
33
# File 'lib/qualitysmith_extensions/object/default.rb', line 31

def default!(default_value)
  # This should have no effect on any objects other than instances of NilClass.
end

#if(condition, else_value = nil) ⇒ Object Also known as: if_else

Returns self if condition; otherwise, returns else_value.

Example:

"Average: #{array.average}". if_else array.size >= 3, ''

That is another way to say this:

array.size >= 3 ? "Average: #{array.average}" : '' )

Sometimes you want to do ‘something unless condition’ and you want that whole expression to return ” (or some other value) if the condition is false, rather than nil, which is what it currently returns.

Important: “self” will always be “evaluated”. So if the receiver of this message is some dangerous call (has side effects), then you would be advised to use the normal if/then/else or ?/: constructs instead.

For example, method_with_adverse_side_effects will be called unconditionally in this case (whether or not ready? returns false):

obj.method_with_adverse_side_effects.   if_else ready?, NotReady

But it will not be called in this case if ready? returns false:

ready? ? obj.method_with_adverse_side_effects : NotReady)

“Isn’t this method useless?” … Yes, basically. Its main advantage is that it lets you put the condition after the normal value, which may make it easier to follow the normal execution flow when reading the source.

This is similar to something I saw in another language (Python?) where a similar syntax is built right into the language. Something like this:

normal_value if condition else else_value

I thought that was a neat idea so I implemented it as best I could in Ruby.

This might also be interesting for folks coming from Smalltalk, where if really is just a message that you pass to an object along with a block (?) to be executed if condition is true and a block to be executed if condition is false.



42
43
44
45
46
# File 'lib/qualitysmith_extensions/object/if_else.rb', line 42

def if(condition, else_value = nil)
  condition ?
    self :
    else_value
end

#ignore_accessObject Also known as: access

Sends all messages to receiver, bypassing access restrictions, allowing you to call private methods (like class_variable_get) without having to write ugly send() calls.

o.class.ignore_access.class_variable_set(:@@v, 'new value')

is equivalent to:

o.class.send(:class_variable_set, :@@v, 'new value')

If you tried to just call the method directly, like this:

o.class.class_variable_set(:@@v, 'new value')

you would get a NoMethodError:

NoMethodError: private method `class_variable_set' called for Klass:Class


36
37
38
39
40
# File 'lib/qualitysmith_extensions/object/ignore_access.rb', line 36

def ignore_access
  @_ignore_access_functor ||= Functor.new do |op,*args|
    self.send(op,*args)
  end
end

#mcall(moduule, message, *args, &block) ⇒ Object Also known as: msend

Calls the method implementation from the module of your choice (moduule) on the object of your choice (self).

The only (huge) catch is that self must either be an instance of moduule or have moduule as an ancestor… which severely limits its usefullness. (Compare with singleton_send.)

It is still useful, though, if you want to call some “original” implementation provided by Kernel (or some other base module) and the people that overrode it didn’t play nice and use alias_method_chain.

No matter! If the class of the object you are calling this on has Kernel as an ancestor, then you can call any method from Kernel on this object!

This implementation is gratefully owed to the folks who wrote PP (/usr/lib/ruby/1.8/pp.rb)



19
20
21
# File 'lib/qualitysmith_extensions/object/mcall.rb', line 19

def mcall(moduule, message, *args, &block)
  moduule.instance_method(message).bind(self).call(*args, &block)
end

#methods_with_sorting_and_include_super(include_super = true) ⇒ Object

Ruby’s built-in Object#methods says:

Returns a list of the names of methods publicly accessible in obj. This will include all the methods accessible in obj's ancestors.

But sometimes you don’t want all of obj’s ancestors!

This Object#methods adds the following features to the built-in Object#methods:

  • Provides the same include_super option that Module#instance_methods has (Backwards compatible, because default is true)

  • Returns an array of symbols rather than strings (Not backwards compatible)

  • Sorts the array for you so you don’t have to! (Not backwards compatible)



24
25
26
27
28
29
30
# File 'lib/qualitysmith_extensions/object/methods.rb', line 24

def methods_with_sorting_and_include_super(include_super = true)
  if include_super
    methods_without_sorting_and_include_super
  else
    (methods_without_sorting_and_include_super - Object.methods_without_sorting_and_include_super)
  end.sort.map(&:to_sym)
end

#send_if(condition, message, *args, &block_to_always_execute) ⇒ Object

Sends message to self (including block_to_always_execute if supplied) if condition is met. If condition is not met, block_to_always_execute will still be called (if supplied), but we will not pass the message. (If condition is not met and block_to_always_execute is not supplied, it will simply return self.)

In summary:

  • block: always executed

  • message: only sent if condition

If a block (block_to_always_execute) is supplied, it is passed on to the message if the condition is met; (otherwise it is simply called without sending the message).

This is useful if you want to wrap a block with some method but you only want the method itself to be used some of the time. For example, if it’s for benchmarking, you may only want to enable it during development but disable during production to save on some overhead.

Note: this cannot be used to call methods that expect blocks (Ruby 1.9 maybe?)



37
38
39
40
41
42
43
44
45
46
47
# File 'lib/qualitysmith_extensions/object/send_if.rb', line 37

def send_if(condition, message, *args, &block_to_always_execute)
    if condition
      self.__send__(message, *args, &block_to_always_execute)
    else
      if block_given?
        block_to_always_execute.call
      else
        self
      end
    end
end

#send_if_not_nil(message, *args) ⇒ Object



10
11
12
13
14
15
16
# File 'lib/qualitysmith_extensions/object/send_if_not_nil.rb', line 10

def send_if_not_nil(message, *args)
  if message
    send(message, *args) 
  else
    self
  end
end

#send_if_true(condition, *args, &block) ⇒ Object

Lets you reduce duplication a little bit. Can do this:

@foo.send_if_true(color)

instead of this:

@foo.send_if(color, color)


59
60
61
# File 'lib/qualitysmith_extensions/object/send_if.rb', line 59

def send_if_true(condition, *args, &block)
  self.send_if(condition, condition, *args, &block)
end

#send_unless(condition, *args, &block) ⇒ Object

Opposite of send_if



50
51
52
# File 'lib/qualitysmith_extensions/object/send_if.rb', line 50

def send_unless(condition, *args, &block)
  self.send_if(!condition, *args, &block)
end

#singleton_send(moduule, message, *args, &block) ⇒ Object Also known as: ss

Creates a singleton method and then calls it.

More specificaly, it extends self with the methods from moduule and then sends the supplied message and args (if any).

Examples:

"whatever".ss(MyColorizer, :colorize, :blue)

Advantages:

  • Keeps things object-oriented. Better than having global/class methods.

    • ("whatever".ss(MyColorizer, :colorize).ss(SomeOtherClass, :another_class_method) instead of

      • SomeOtherClass::another_class_method(MyColorizer::colorize("whatever")))

    • Method calls are listed in the order in which they are called.

  • Still lets you keep your methods in a namespace.

  • Doesn’t clutter up the base classes with methods that are only useful within a very small context. The methods are only added to the objects you specify. So you can “use” the base class without cluttering up all instances of the class with your methods.

  • Useful for cases where creating a subclass wouldn’t help because the methods you are calling would still return instances of the base class.

Disadvantages:

  • You have to have/create a module for the functions you want to use.

    • Can’t just call .sigleton_send(self, :some_method) if you want to use some_method that’s defined in self.

    • So what do we call the module containing the “singleton method”? String::MyColorizer? MyColorizer::String? MyStringColorizer?

Adding methods to the base class using Facets’ own namespacing facilities (Module#namespace and Module#include_as) might actually be a more sensible alternative a lot of the time than bothering to create singleton methods for single objects! That would look somethig like:

class String
  namespace :my_colorizer do
    def colorize(...); ...; end
  end
end
"whatever".my_colorizer.colorize(:blue)

or

class String
  include_as :my_colorizer => MyColorizer
end
"whatever".my_colorizer.colorize(:blue)


61
62
63
64
# File 'lib/qualitysmith_extensions/object/singleton_send.rb', line 61

def singleton_send(moduule, message, *args, &block)
  self.extend(moduule)
  self.send(message, *args, &block)
end

#unless(condition, else_value) ⇒ Object Also known as: unless_else



49
50
51
52
53
# File 'lib/qualitysmith_extensions/object/if_else.rb', line 49

def unless(condition, else_value)
  !condition ?
    self :
    else_value
end