Friday, May 6, 2011

Extending core Ruby classes when in Rails

Rails provides a number of extensions to core Ruby classes. One of these is the to_sentence method, added to the Array class, allowing for the following:

['a', 'b', 'c'].to_sentence # gives: "a, b, and c"

I would like to extend this method to allow it to take a block, so that you can do something like the following (where people is an array of Person objects, which have the name attribute):

people.to_sentence { |person| person.name } # give something like: "Bill, John, and Mark"

I don't have a problem with writing the extension method. But I can't work out where to put it.

The Rails core extensions get loaded somewhere down in the depths of ActiveSupport.

My need is for a place where user-defined code is always loaded, and is pre-loaded (before any application code).

From stackoverflow
  • Create config/initializers/super_to_sentence.rb. All files in this directory are loaded after Rails has been loaded, so you'll have a chance to override Rails' definition of Array#to_sentence.

    For code you want to load before Rails gets loaded, add it to config/environment.rb.

    cpjolicoeur : like was mentioned, add all your custom extensions to a config/initializers/*.rb file
    dcw : Thanks. Will check it out.
  • I like to do this:

    # config/initializers/app.rb
    Dir[File.join(Rails.root, "lib", "core_ext", "*.rb")].each {|l| require l }
    
    # lib/core_ext/array.rb
    class Array
      def to_sentence_with_block(*args, &block)
        if block_given?
          # do something...
          # to_sentence_without_block(*args) perhaps?
        else
          to_sentence_without_block(*args)
        end
      end
      alias_method_chain :to_sentence, :block
    end
    
    dcw : Thanks. btw, I think it's good practice to remember the previous defn, i.e. add an "alias old_to_sentence to_sentence" before the defn of the new method and its aliasing.
  • I think this is an ugly idea. Why dont you just write

    people.collect { |person| person.name }.to_sentence
    

    This looks almost the same and will not confuse other people reading your code (like yourself in 2 years)

  • just searching around the web, seems like good practice is to add it in lib/

    so if you wanna extend some ruby class (in my case, DateTime), just put the code in .rb and then in config/environment.rb:

      config.after_initialize do
        require "lib/super_datetime.rb"
      end
    

    my super_datetime.rb looks like this (code from http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/140184):

    class DateTime
      def days_in_month
        self::class.civil(year, month, -1).day
      end
      alias dim days_in_month
      def weekdays
        (1..dim).to_a
      end
    end
    

    restart your server and you'll have access to the new method for all DateTime objects.

0 comments:

Post a Comment