DEV Community

loading...

How to define the method for the DSL configuration file

Seiei Miyagi
こんにちは
・2 min read

You can define the method for the DSL configuration file1 by Refinements.

For example, the hi method in a following code can use only in this Rakefile, it shouldn't affect any code outside of the Rakefile.

# Rakefile
main = self

using Module.new {
  refine(main.singleton_class) do
    def hi
      puts :hi
    end
  end
}

desc 'hi'
task :hi do
  hi
end
Enter fullscreen mode Exit fullscreen mode
% rake hi
hi
Enter fullscreen mode Exit fullscreen mode

Common mistake 1

The following code defines the method at top level.

# Rakefile
def hi
  puts :hi
end

desc 'hi'
task :hi do
  hi
end
Enter fullscreen mode Exit fullscreen mode

Common mistake 2

The following code defines the method at top level.

# Rakefile
desc 'hi'
task :hi do
  def hi
    puts :hi
  end

  hi
end
Enter fullscreen mode Exit fullscreen mode

Common mistake 3

The following code defines the method at top level.


# Rakefile
m = Module.new do
  def hi
    puts :hi
  end
end

desc 'hi'
task :hi do
  include m
  hi
end
Enter fullscreen mode Exit fullscreen mode

Why should we avoid to define the method at top level?

It possibly breaks other libraries code.

For example:

# duck.rb
class Duck < Struct.new(:name)
  def sound
    puts 'quack'
  end
end

Enter fullscreen mode Exit fullscreen mode
# Rakefile
require_relative 'duck.rb'

def puts(*)
  print 'Hi! '
  super
end

desc 'hi'
task :hi do
  duck = Duck.new('Donald')
  puts duck.name
  duck.sound
end

Enter fullscreen mode Exit fullscreen mode
$ rake hi
Hi! Donald
Hi! quack
Enter fullscreen mode Exit fullscreen mode

The benefit of the Refinements

The ruby's Refinements are lexical in scope2.
Thus the top level method that defined by the Refinements couldn't breaks other libraries code, It's totally fine!

# Rakefile
require_relative 'duck.rb'

main = self

using Module.new {
  refine(main.singleton_class) do
    def puts(*)
      print 'Hi! '
      super
    end
  end
}

desc 'hi'
task :hi do
  duck = Duck.new('Donald')
  puts duck.name
  duck.sound
end

Enter fullscreen mode Exit fullscreen mode
$ rake hi
Hi! Donald
quack
Enter fullscreen mode Exit fullscreen mode

Other approach

Wrap the method and DSLs within a module.

# Rakefile
require_relative 'duck.rb'

Module.new {
  def puts(*)
    print 'Hi!, '
    super
  end
  extend(self)

  extend Rake::DSL

  desc 'hi'
  task :hi do
    duck = Duck.new('Donald')
    puts duck.name
    duck.sound
  end
}
Enter fullscreen mode Exit fullscreen mode
$ rake hi
Hi!, Donald
quack
Enter fullscreen mode Exit fullscreen mode

  1. e.g. Rakefile, config/routes.rb, unicorn.rb, etc... 

  2. https://docs.ruby-lang.org/en/trunk/syntax/refinements_rdoc.html#label-Scope 

Discussion (3)