Monday, October 5, 2015

DRY up your class definitions

Have you ever felt vaguely annoyed at typing the class name in the file name and then typing it again inside the file as well? Have you ever changed a class name and forgotten to change the file name?

Well I have good news for you!

# app/models/user.rb
Dryer.def_class(__FILE__) do
def hi
"hello world"
end
end
User.new.hi
=> "hello world"
# app/models/instruments/horn.rb
Dryer.def_class(__FILE__) do
def play
"toot!"
end
end
Instruments::Horn.new.play
=> "toot!"
# app/models/address.rb
Dryer.def_class(__FILE__, ActiveRecord::Base) do
end
Address.new.respond_to? :save
=> true

Here is the code! This class allows you to condsolidate the declaration of class names into the file name. It also handles namespacing based on the file path if you are using Rails autoloading.

This works with the single exception of constant lookup via nesting, so you will need to replace constants with variables and methods.

class Dryer = Struct.new(:file_path, :super_class, :blk) do
def self.def_class(file_path, super_class=Object, &blk)
new(file_path, super_class, blk).def_class
end
def def_class
constant_names.each.with_index.inject(Object) do |const, (next_const, index)|
if constant_names.length == index + 1
# this is needed to set the class name so that activerecord
# works properly.
klass = const.const_set(next_const, Class.new(super_class))
# here we eval the class block to define methods and everything
klass.class_eval &blk
else
const.const_get(next_const)
end
end
end
def constant_names
get_namespaces.split('/').map(&:camelcase)
end
def get_namespaces
# this assumes you are following ruby convention of paths corresponding to
# namespaces. If you do not, this can't work.
file_path.gsub(most_likely_relative_path, '').gsub(".rb", '')
end
def most_likely_relative_path
# sort by length selects the path closest to the original file path.
possible_relative_paths.sort_by(&:length).last + '/'
end
def possible_relative_paths
$LOAD_PATH.select{|p| file_path[/^#{p}/]}
end
end
view raw dryer_code.rb hosted with ❤ by GitHub

No comments:

Post a Comment