EDRY, meet CoC

  • Posted By Stuart Halloway on January 29, 2008

Jay Fields has envisioned a beautiful future for software development with his EDRY dialect of Ruby. But what is Enhanced DRY without better CoC (Convention over Configuration)?

I have modified Jay's code to rely more on convention. Why have a distinct vocabulary for fields vs. mixins, when the right thing to do can be inferred from the types involved? The result is some really tight code:

C Enumerable, :first_name, :last_name, :favorite_color do
  d.complete_info? { nd(first_name,last_name) }
  d.white?.red?.blue?.black? { |color| favorite_color.to_s == color.to_s.chop }
end

I am including the full source at the bottom of this entry. Can you make it even DRYer and more convention-driven?

class Object
  def C(*args, &block)
    attrs = args.find_all {|arg| Symbol === arg}
    includes = args.find_all {|inc| inc.instance_of?(Module)}
    name = File.basename(eval("__FILE__", block.binding),".rb")
    klass = Struct.new(name.capitalize, *attrs)
    Kernel.const_set(name.capitalize, klass)
    klass.class_eval(&block)
    klass.send :include, *includes
  end

  def s
    self
  end
end

class Class
  def ctor(&block)
    define_method :initialize, &block
  end

  def i(mod)
    include mod
  end

  def d
    DefineHelper.new(self)
  end

  def a(*args)
    attr_accessor(*args)
  end
end

class DefineHelper
  def initialize(klass)
    @klass = klass 
  end

  def method_stack
    @method_stack ||= []
  end

  def method_missing(sym, *args, &block)
    method_stack << sym
    if block_given?
      method_stack.each do |meth|
        @klass.class_eval do
          define_method meth do
            instance_exec meth, &block
          end
        end
      end
    end
    self
  end
end

# http://eigenclass.org/hiki.rb?instance_exec
module Kernel
  def instance_exec(*args, &block)
    mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
    Object.class_eval{ define_method(mname, &block) }
    begin
      ret = send(mname, *args)
    ensure
      Object.class_eval{ undef_method(mname) } rescue nil
    end
    ret
  end
end

def nd(*args)
  args.each {|x| return false unless x}
  true
end

# convention: symbols are attributes, modules are to be included
C Enumerable, :first_name, :last_name, :favorite_color do
  d.complete_info? { nd(first_name,last_name) }
  d.white?.red?.blue?.black? { |color| favorite_color.to_s == color.to_s.chop }
end
Comments
  1. Anon. Ruby HackerJanuary 29, 2008 @ 03:15 PM

    I can’t say I would recommend getting within 10 feet of that code. First of all, it can be written much more cleanly with a lot of things Ruby already provides. Second of all, it ignores a lot of things Ruby already has.

    1. Be clear what the base class is or at least what you get. Struct.for_file(:a, :b, :c) do include Enumerable # Nothing wrong with include IMO.def complete?
      1. No need for obscure methods. Enumerable#all? ftw end
      1. Define with one good name def color?(hue) c == hue end
      1. The others can now be noted as aliased in real docs %w[white? green? red? blue? black?].each do |color| alias_method color, :color? end

    end

    Twice as readable and no repetition. If you want to abstract some more then there are clean things to do. For example the alias part could become:

    alt_names_for :color?, %w[white? red? green? blue? black?]

    Finally, I’d say you are golfing and not solving dry with the above code. It looks like shit. Please stop writing such bad code in Ruby so I don’t need to go save yet another client/victim in my consulting practice. I still wonder why someone would want to avoid writing the file name down as a constant on your own. This sort of DRY defeats the original purpose… good luck on your recovery.

  2. ConfabulistJanuary 29, 2008 @ 08:02 PM

    Wow, there are some humor-challenged people in the world. And so many of them are named Anonymous!