Betterobject

This gem installs several class methods to Object which in turn generates both class and instance methods but only when you are ready. In order to prevent name pollution, you have the ability to manage the generators to pick alternate names if you prefer. After scouring the RubyGems site, some of the better Class upgrades are included here as well as some of my own. The gem creates the backbone upon which future upgrades should be forthcoming. Generators are code in waiting. Only when installed do they become methods (either instance or class methods depending on the generator). The generator names are also the method names which can be renamed in the event that you get a name conflict.

Installation

Add this line to your application's Gemfile:

gem 'betterobject'

And then execute:

$ bundle

Or install it yourself as:

$ gem install betterobject

Or as a stand-alone:

$ require "betterobject"   

Usage

The class methods installed on Object must be called before something useful will happen. Somewhere in the installation, you will need to run the generators. If you open irb, you can try this:

require "betterobject" # load gem

Object.better_install_all # install all generators
5.in? 1..9  # true ... 
class Fred
  def a
  end
end  
fred = Fred.new
fred.local_methods     # [:a]
fred.inherited_methods # [bunch of stuff not including :a]
String.derives_from? Comparable  # true
String.derives_from? String      # false
String.comes_from? Comparable    # true
String.comes_from? String        # true

String.define_presence_of :i_am_a_string?  # updates Object and String
"hello".i_am_a_string? # true
1234.i_am_a_string? # false
Arrray.define_presence_of(:i_am_not_an_array?, false)
[1,2,3].i_am_not_an_array?  # false
"".i_am_not_an_array?  # true

You can see the motivation. In the event that one of these generators conflicts with some other method, you can elect to not execute that generator. Or if you really need the functionality, you can rename the generator. First close the irb then reopen it, then try this:

require "betterobject"

Object.better_rename(:in?, :in_the_thing_to_the_right?)
Object.better_rename(:local_methods, :methods_by_me)
Object.better_install [:in_the_thing_to_the_right?, :methods_by_me]  # or better_install_all
5.in? 1..9    # undefined method ...
5.in_the_thing_to_the_right? 1..9  # true
"".local_methods    # undefined method ...
"".methods_by_me    # [...] 

You can control which methods names get generated. Now let's dive into the other class methods of betterobject.

Object class methods created from the betterobject gem

  • Object.better_installed_instance_methods
  • Object.better_installed_class_methods
  • Object.better_installed
  • Object.better_explain
  • Object.better_source_code
  • Object.better_list
  • Object.better_install
  • Object.better_install_as!
  • Object.better_install_all
  • Object.better_uninstall
  • Object.better_uninstall_all
  • Object.better_skip
  • Object.better_unskip
  • Object.better_skipped
  • Object.better_rename
  • Object.include_many?

Note that there are no instance methods created by this gem until you install a generator. Also, each and every method must be called from Object.

Object.better_install_all

This will install every defined generator. In the event that a generator is renamed, the new name will be generated, while the old name will no longer exist. This is similar to renaming a file. Note that the generator essence never goes away, as it will exist under some new name. It is simply tucked away until needed. Also, generators marked as skipped will not be installed.

Object.better_skip(gen or [gen1, gen2, ...] or gen1, gen2, ...)

This is to prevent a generator from installing by adding a skip attribute. Additionally, skipped generators cannot be uninstalled until the skip flag is removed. If a generator is first installed then given this skip attribute, it cannot be uninstalled. This acts as a protection.

Object.better_unskip(gen or [gen1, gen2, ...] or gen1, gen2, ...)

This removes the skip attribute enabling the generator to be installed or uninstalled.

Object.better_skipped

This returns an array of skipped generators.

Object.better_install(gen or [gen1, gen2, ...] or gen1, gen2, ...)

This will install a specific generator. If you rename a generator, the old name no longer exists, so you must use the new name. See below:

Object.better_rename(:in?, :is_in?)
Object.better_install(:in?)          # Error
Object.better_install(:is_in?)       # true
Object.better_install(:is_in?)       # false  ... already installed

str = "The cat in the hat is back"
'cat'.is_in? str  # true

Object.better_uninstall_all

This will remove all the generators from Object. Note that some generators spawn additional generators such as :define_presence_of. If this generator is ever run, Object.better_uninstall_all will not remove these second generation methods. Note that generators marked with the skip flag will not be uninstalled.

Object.better_uninstall(gen or [gen1, gen2, ...] or gen1, gen2, ...)

This will uninstall a specific generator or a set of generators. This method returns false if the generator is skipped, or already uninstalled; otherwise true is returned. If you installed a generator and wish to rename it, you must first uninstall it before you can rename it. See below:

Object.better_install :local_methods
"a string".local_methods  # [list of local methods]
Object.better_rename(:local_methods, :methods_by_me)  # Error ... installed methods cannot be renamed
Object.better_uninstall :local_methods
Object.better_rename(:local_methods, :methods_by_me)
Object.better_install :methods_by_me
"a string".methods_by_me # [list of local methods]

Object.better_install_as!(:generator_library_name, :permanent_clone_name)

This forces permanent installation of a generator to an alternate target name. Note that once done, this alternate generator is permanently installed and cannot be removed. If you are creating a gem that requires betterobject, this type of installation will prevent someone else's code from deleting what you require. Alternately, you can capture the source code, and physically place the code in Object. This call leaves no trace once it is called other than the methods appearing from the generator. If you are using betterobject as part of a library gem project or a component then you should only install with this method and only install the minimum of what you need. A good naming convention could be prefixing the generator name with your gem name. As an example, this was used in the setfu gem as follows:

class Object
  better_install_as!(:push_unique, :setfu_push_unique)
  better_install_as!(:tag, :setfu_tag)
  better_install_as!(:tag, :setfu_count)
end

Object.better_list(include_skip=false)

This returns an array of generators that can be installed. Skipped generators can also be listed if you call this method with the value true.

Object.better_installed

This returns a list of installed generators. This does not list what the generators generate.

Object.better_installed_instance_methods

This gives us a list of generators that created instance methods. This does not list what the generators generate.

Object.better_installed_class_methods

This give us a list of generators that created class methods. This does not list what the generators generate.

Object.better_rename(old_name, new_name)

This allows us to rename a generator which in turn (most likely) renames what gets generated. This is an important feature as placing methods at the Object level has the danger of bumping into potential name conflicts. See the example below:

Object.better_list.each do |old_name|
  new_name = ("bjc_" + old_name.to_s).to_sym  # my initials
  Object.better_rename(old_name, new_name) 
end
Object.better_install_all
sorted_hash = {zoo:"animals", banana:"fruit"}.bjc_sort!  # == {banana:"fruit", zoo:"animals"}
sorted_hash.bjc_tag?  # false
sorted_hash.bjc_tag!
sorted_hash.bjc_tag?  # true
:zoo.bjc_in? hash  # true

Object.better_explain(:generator_name, pts=true)

Calling this method without a generator name gives us a general list of the generators. Further details are revealed when a generator name is provided. The last default parameter puts the string to STOUT when true; otherwise, a string is returned. See example below:

Object.better_explain(:in?)  #  obj.in?(enum) # ex:  3.in? [3,4,5] == true

Object.better_source_code(:generator_name, return_string = false, inner_only = false, type=:code)

This will revel the source code for the generator. You can then copy and paste this in irb and experiment with it. See below:

Object.better_source_code(:in?)

class Object
  def in?(enum)
    enum.include? self
  end    
end

The parameter return_string will return a string instance when set true. The parameter inner_only when set true will remove the class Object and enclosing end statements. The final parameter is set to either :code or :rm depending on if you need the creation code or the deletion code.

Object.better_define(meth_name, code, type, doc="Undocumented", rm_code=nil)

This installs a user-defined generator. As an example we will create a whotheheckami generator as follows.

doc = "same as .class"
type = :both  # declares that :class and :instance will both be present
meth_name = :whotheheckami
code = "
  def self.BO_METH_NAME
    self.class
  end
  def BO_METH_NAME
    self.class
  end
"
Object.better_define(meth_name, code, type, doc)
Object.better_list.include? :whotheheckami  # true
Object.better_install :whotheheckami
"".whotheheckami      # String
String.whotheheckami  # Class

Note that we used the identifier name BO_METH_NAME instead of whotheheckami. This allows our generator to be renamed. Generally speaking, the generator name need not be coupled with the method name, but that is the convention. Also, you can target a foreign object if you wish. The following example demonstrates updating String and also decouples the rename associations.

doc = "some cool string methods"
type = :instance
meth_name = :string_methods
code = "
  String.class_eval do
    def sort
      self.split('').sort.join('')
    end
    def sort!
      replace self.split('').sort.join('')
    end
    def histogram
      hash = {}
      self.split('').sort.each do |ch|
        hash[ch] = hash[ch].nil? ? 1 : 1 + hash[ch]
      end
      return hash
    end
  end  
"
rm_code = "
  String.class_eval do
    undef sort
    undef sort!
    undef histogram 
  end
"
Object.better_define(meth_name, code, type, doc, rm_code)
Object.better_list.include? :string_methods  # true
Object.better_install :string_methods
"".string_methods      # Error
"everybody".sort       # "bdeeorvyy"

In this case if you rename :string_methods you will only rename the generator. This method was used as a tool to create some of the generators. Note that if you decouple the method name from the generator name, or target a foreign object, you must provide code to remove your methods.

Generator list

  • :local_methods
  • :inherited_methods
  • :replaced_methods
  • :derives_from?
  • :comes_from?
  • :define_presence_of
  • :in?
  • :include_many?
  • :include_any?
  • :to_literal
  • :pluralize
  • :parent
  • :find_def
  • :tag
  • :create_tag
  • :functionize
  • :push_unique
  • :sort!
  • :many_at
  • :delete_many_at
  • :tripleize
  • :dup_safe
  • :cvar

Generator :local_methods(include_singleton_methods=true)

This is an instance generator on Object. It is used on all objects that derive from Object (which is pretty much everything). The purpose is to view methods generated by the class of the instance and not the methods inherited by that class. This reduces the clutter considerably while introspecting the methods. See the example below:

Object.better_install :local_methods  # run the generator

class Fred < String
  def a
  end
  def b
  end
end

fred = Fred.new
def fred.c; end
fred.singleton_methods  # [:c]
fred.local_methods  # [:a, :b, :c]
fred.local_methods(false)  #[:a, :b]
fred.methods        # [:a, :b, :dup, :clone, etc ...]  

Generator :inherited_methods

This is an instance generator on Object. It is similar to #methods, except all locally defined methods are excluded. The combination of #local_methods and #inherited_methods is the same as #methods. See below:

Object.better_install [:local_methods, :inherited_methods]

class Fred < String
  def a
  end
  def b
  end
end

fred = Fred.new
fred.methods.sort == [fred.local_methods + fred.inherited_methods].sort  # true

Generator :replaced_methods

This is an instance generator on Object. This lets us discover which methods have been replaced during inheritance, or replaced due to a singleton method using the same name as an instance method. See the example below:

Object.better_install :replaced_methods

class Fred < String
  def length
  end
end

fred = Fred.new
def fred.to_s
end

fred.replaced_methods  # [:length, :to_s]

Generators :derives_from? and :comes_from?

These methods are both class methods on Object. The purpose is to test hierarchy of derived objects. The two class methods once generated are identical except comes_from? is more inclusive. See the example below:

Object.better_install :derives_from?, :comes_from?

class Fred < String
  def a
  end
  def b
  end
end

Fred.derives_from? String # true
Fred.derives_from? Fred   # false
Fred.derives_from? Object # true
Fred.comes_from? Fred     # true
Fred.comes_from? String   # true
Fred.comes_from? Object   # true

Generators :define_presence_of(:target_method_name, target_default_value = true)

This generator generates a generator! When installed, a class method is created on Object called #define_presence_of. This is called from a derived class such as String or Integer. The intent is to create a test for a certain type. This is a short-hand way of using the #kind_of(CLASS_NAME) method. See the example below:


t1 = "fred"
t2 = 123
t1.kind_of? String  # true ... old way of doing things
t2.kind_of? String  # false

Object.better_install :define_presence_of

String.define_presence_of(:string?)
String.define_presence_of(:not_string?, false)
Integer.define_presence_of(:int?, true)
Integer.define_presence_of(:not_int?, false)

t1.string?      # true
t2.not_string?  # true
t1.int?         # false
t2.int?         # true
[4].int?        # false ... it is an array

As we can see from the above code, we now have a shorter way of doing type checking. The generated generator defines an instance method on Object as well as the descendant class that calls it. Each method returns an opposite result. There are several base class gems (not mine) that force a certain naming convention; this one let's you pick the name!

Generators :in?

This generator allows us to swap the order of the standard #include? method with an improved comprehensive ordering. I saw this in another gem called ("object-in" by Tim Rogers), so I give him credit for the idea. It is included here because it is cool. This is probably the best name, but others (which you could rename to) could be: :member_of, :is_in?, :in, or :element_of. See the example below:


[1,2,3].include? 2  # true ... old way of doing things

Object.better_install :in?

2.in? [1,2,3]   # true
'i'.in? "team"  # false

Generators :pluralize(meth_name, alt=nil)

This generator installs a generator that mimics another method using a pluralized name. One of my pet peves is poorly named methods that are grammatically incorrect. From the String class, the method named #start_with should have been named #starts_with. This class method can be called from Object, String, or any class you wish. The pluralization is pretty smart, but if it does not quite work, you can provide the altername name in the second parameter. The new method name is returned. See the example below

Object.better_install :pluralize  # installs the first-level generator

Object.pluralize :include?        # returns :includes?
[1,2,3].includes? 2               # true

String.pluralize :start_with?     # returns :starts_with?
"whatever".starts_with? "wh"      # true

Object.pluralize :respond_to?     # :responds_to?
17.responds_to? :to_s             # true

Generators :parent

This generator finds the first non-module ancestor of a class. The class Object's parent is always Object. This is a class method installed on Object. See the example below:

Object.better_install :parent

6.class.ancestors  # [Fixnum, Integer, Numeric, Comparable, Object, Kernel, BasicObject]
6.class                      # Fixnum
6.class.parent               # Integer
6.class.parent.parent        # Numeric
6.class.parent.parent.parent # Object  ... Note: Comparable is a Module
6.class.parent.parent.parent.parent.parent.parent.parent.parent.parent  # Object

Generators :to_literal

This is an alias for #inspect. In the event inspect fails, #to_s is then called.

Generators :tag

This generator creates a family of instance methods. If unrenamed, you will get: #tag, #tag!, #untag, #tag? and #tag=. If you rename this generator to :fred, you will get these: #fred, #fred!, #unfred, #fred?, #fred=. Note that uninstall will remove all five methods even if renamed. This method attaches a property to an instance of an Object or a derivative object. Note that objects that have no constructors such as Integer, Float, NilClass, TrueClass, FalseClass and Symbol cannot be tagged. Also, frozen object instances cannot be tagged. You will need to place such objects either in a wrapper class or a use a container such as Array. A good container system is the gem primitive_wrapper. See the example below:

 Object.better_install :tag

 #tag        get current tag value
 #tag=(val)  set tag's value to `val`
 #tag!       set tag's value to true ... same as #tag=true
 #tag?       returns true if tag's value is anything but nil, or false
 #untag      set tag's value to false ... same as #tag=false

 var1 = "Something to remember"
 var1.tag?  # false
 var1.tag   # nil
 var1.tag!  # true
 var1.tag   # true
 var1.tag?  # true
 var1.untag # false
 var1.tag?  # false
 var1.tag= "don't forget me!"
 var1.tag?  # true
 var1.tag   # "don't forget me!"
 var1.untag # false
 var1.tag?  # false
 var1.tag   # false

Generators :create_tag

This generator is a class method on Object that installs a named tag generating system. Once a tag is generated, it will work just like tag in the previous section. This allows an Object derived instance to have one or more tags attached. If you only need one tag, then the :tag generator is a better choice as it can be uninstalled. Tags created by #create_tag are 2nd level generators and cannot be uninstalled. These two generators are independent so long as you avoid using :tag as a source name. See the example below:

 Object.better_install :create_tag
 Object.create_tag :tag1
 Object.create_tag :tag2
 Object.create_tag :tag3
 Object.create_tag :flag
 str = "Tag me!"
 str.tag1!
 str.tag2 = "forget me not"
 str.tag3  = 17
 str.tag1?  # true
 str.tag2?  # true
 str.flag?  # false
 str.tag2   # "forget me not"
 str.flag   # nil
 str.unflag
 str.flag   # false
 end

Generators :sort!

This method targets only the Hash class and creates the #sort! method. Hash has a method #sort that returns a sorted array of key value pairs. Obviously, they omitted the self modifying version because Arrays are not Hashes. See the example below:

hash = {:zoo=>"Animals", :fruit=>"Banana", :better=>"Object"}
hash.keys                         # [:zoo, :fruit, :better]
Object.better_install :sort!
Hash.respond_to? :sort!           # true
hash.sort!         
Hash.keys                         # [:better, :fruit, :zoo]

Generators :find_def

This method locates the owner of either an instance method or a class method. The method expects a symbol representation of the method. The value nil is returned in the event that the method is undefined. See the example below:

Object.better_install :find_def
tst = "".find_def :<        # tst = Comparable
tst = 7.find_def  :<        # tst = Fixnum
tst = 7.find_def  :to_int   # tst = Integer
tst = 7.find_def  :to_c     # tst = Numeric
tst = Fixnum.find_def :to_s # tst = Module
tst = Fixnum.find_def :new  # tst = nil
tst = "nope".find_def :foo  # tst = nil

Generators :functionize(meth = :new)

This method makes your class look like a method that you simply call on the class constant. If you examine the Rational class, there is no #new constructor at all. Instead, you call Rational(2,3) which creates the object. On your own class object, the default will call #new adding shorter syntax candy. This allows less typing to create the object. You can also provide any class method you wish, but because you only get one, probably best if it were a constructor. See the example below:

Object.better_install :functionize
class Clown
  def initialize(prm1, prm2)
    @prms = [prm1, prm2]
  end
end

Clown.functionize
obj1 = Clown(:one, :two)         # fast way
obj2 = Clown.new(:three, :four)  # slow way

Generators :push_unique(item)

This method extends Array with a new method #push_unique. This works just like #push, but first checks to see if that item already exists; in that case no #push occurs. See the example below:

Object.better_install :push_unique

ary = []
ary.push "row"
ary.push "row"
ary.push_unique "row"
ary.push_unique "row"
ary.push_unique "row"
ary.push "row"
ary.push_unique "your boat"
ary.join " "  # "row row row your boat" 

Generators :many_at(*list)

This targets the Array class with an improved access method. You can pass an array of items, or a comma delimited list of items; items include integer indexes, ranges, and flat arrays of integers. See the example below:

Object.better_install :many_at

ary = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight"]
stuff = ary.many_at 1, 3..5, 8     # stuff == ["one", "three", "four", "five", "eight"]
stuff = ary.many_at [1, 3..5, 8]   # same as above
stuff = ary.many_at 1, [3,4,5], 8  # same as above

Generators :delete_many_at(*list)

This targets the Array class with an improved #delete_at method. You can pass an array of items, or a comma delimited list of items; items include integer indexes, ranges, and flat arrays of integers. The method returns the deleted items as an array of elements in the reverse order in which they were deleted. Note that deletion indexes are internally sorted in reverse order in order to preserve the index integrity. If the list of deletion indexes is ascending, then we should see the same data as if we called #many_at. See the example below:

Object.better_install :delete_many_at

ary = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight"]
stuff = ary.delete_many_at 1, 3..5, 8     # stuff == ["one", "three", "four", "five", "eight"]
                                          # ary   == ["zero", "two", "six", "seven"]
# random delete order:                                          
ary = ["zero", "one", "two", "three", "four", "five", "six", "seven", "eight"]
stuff = ary.delete_many_at 8, 3..5, 1     # stuff == ["one", "three", "four", "five", "eight"]
                                          # ary   == ["zero", "two", "six", "seven"]

Generators :tripleize

This installs a generator that defines the === operator on a particular existing class such as Array or Hash. Note that the === is used in conjunction with the case-when statement. The installed generator can be called with or without a block. The default behavior is to mimic what a Range does with the === operator. This is best explained by example:

(1..9) === 5               # true  ... Range defines  === as:  self.include? (right_expr)
[1,2,3,4,5,6,7,8,9] === 5  # false ... Kernel defines === as:  self == (right_expr)

Object.better_install :tripleize

Array.tripleize  # install default behavior
[1,2,3,4,5,6,7,8,9] === 5  # true

Array.tripleize { |me , other| me.first == other }
[1,2,3,4,5,6,7,8,9] === 5  # false
[1,2,3,4,5,6,7,8,9] === 1  # true

Generators :include_many?

This is an instance generator on Object that creates a generalized instance method called #include_many? or some other name you think is better. If an Object implements #include, then #include_many? should work fine. Note that the #include method only looks at a single item, where #include_many? requires that all items are included. You can pass a list of items, or pass an Array of items. See the example below:

Object.better_install :include_many?

[1,2,3,4,5,6,7,8].include?      [3,4,7]  # false
[1,2,3,4,5,6,7,8].include_many? [3,4,7]  # true
[1,2,3,4,5,6,7,8].include_many? 3,4,7    # true
[1,2,3,4,5,6,7,8].include_many? 3        # true
[1,2,3,4,5,6,7,8].include_many? 1,9      # false
[1,2,3,[4,5],[6,7],8].include_many? [[4,5],[6,7],1]    # true  ... not:  [4,5],[6,7],1 see note below:
[1,2,3,[4,5],[6,7],8].include_many? [4,5],[6,7],1      # false ... same as:  .include_many? 4,5 ... see note below:
[1,2,3,[4,5],[6,7],8].include_many? 1,[4,5],[6,7]      # true  ... see note below:
"This is a test string".include_many? "test", "string" # true

Note that it is best to encapsulate the list inside an array, particularly if your elements are arrays. If the first item in the list is an array, then its contents will be the only thing tested.

Generators :include_any?

This works just like :include:many? except instead of all items needing to be included, any item included will return true.

Generators :dup_safe

It is interesting to note that you can't duplicate an Integer instance. Calling #dup will raise an exception. In the event that an instance cannot be duplicated, self is instead returned.

Generators :cvar

Class variables (not the @@name ilk) can be tricky to access from an instance. This method creates and accesses class variables on the class or the instance of that class. See the example below:

Object.better_install :cvar

7.cvar!(:fred,"yaba daba doo!")
2.cvar :fred        # returns "yaba daba doo!"
9.class.cvar :fred  # returns "yaba daba doo!"
55.class.cvar!(:fred,"Wilma?")
100.cvar :fred      # returns "Wilma?"

Change Log

Version 1.2.0

  1. Changed :better_install_as! to class method
  2. Added generator :cvar
  3. Added generator :include_any?
  4. Added generator :dup_safe

Version 1.1.0

  1. Update documentation
  2. Added method :better_install_as!
  3. Added generator :delete_many_at
  4. Added generator :many_at
  5. Added generator :tripleize
  6. Added generator :include_many?

Version 1.0.0

  1. Added new generator :push_unique
  2. Added new generator :create_tag

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install.

Contributing

I need to control this for the time being. You you need a generator, shoot me an email and I will consider it on the next release.

License

The gem is available as open source under the terms of the MIT License.