berkes berkes - 3 days ago 6
Ruby Question

Is it possible to render the "source code" for a Ruby class that includes any included models?

Say, I have the following Ruby:

module Nameable
def name
"John"
end
end

class User
include Nameable
def email
"john@example.com"
end
end


Is there a way to display, print or review the "expanded source code" of the entire "User"? I'm not sure what the term would be, but with "expanded source code" I mean the code, in Ruby (so not an AST), that has the included parts included:

class User
def name
"John"
end

def email
"john@example.com"
end
end


Bonuspoints when a solution can also display the code for inherited behaviour.

Answer

You can use method_source gem.

require 'method_source'

def flatten_source_code(klass)
  excluded_parents = [Kernel, Object, BasicObject]
  type = klass.instance_of?(Module) ? "module" : "class"
  puts "#{type} #{klass}"

  (klass.ancestors-excluded_parents).reverse.each do |ancestor|
    ancestor.instance_methods(false).each do |method_name|
      method = ancestor.instance_method(method_name)
      script, line = method.source_location
      if script then
        puts
        puts "  # #{script} (line #{line})"
        puts method.source
      else
        puts
        puts "  #  def #{method_name}"
        puts "  #  end"
      end
    end
  end
  puts "end"
end

module Nameable
  def name
    "John"
  end
end

class User
  include Nameable
  def email
    "john@example.com"
  end
end


flatten_source_code(User)

puts 

flatten_source_code(Nameable)

#=>
# class User
#
#   # flatten_source_code.rb (line 27)
#   def name
#     "John"
#   end
#
#   # flatten_source_code.rb (line 34)
#   def email
#     "john@example.com"
#   end
# end

# module Nameable
#
#   # flatten_source_code.rb (line 27)
#   def name
#     "John"
#   end
# end

It will not work perfectly for more complex cases, but it does the job for your example.

Just for fun, with :

module Nameable
  def name
    "John"
  end

  def email
    "included john@example.com"
  end
end

module AnotherModule
  def email
    "prepended john@example.com"
  end
end

class User
  include Nameable
  prepend AnotherModule
  def email
    "john@example.com"
  end
end

flatten_source_code(User)
puts "u = User.new"
puts "puts u.name"
puts "puts u.email"

Launching ruby flatten_source_code.rb | ruby returns :

John
prepended john@example.com

So the displayed Ruby code is valid and respects inheritance order. It will define the same method multiple times, though.

You could keep an Hash of |method_name,method_source|. If a method overrides an old one and calls super, you could define the old method as as a private old_method_name_from_Module_blabla and replace super accordingly.

Comments