rake icon indicating copy to clipboard operation
rake copied to clipboard

`namespace` not only for tasks, but also for functions hermetization

Open ciembor opened this issue 6 years ago • 4 comments

It's a known fact, that ruby function defined in a namespace will be defined in a global scope rather than inside of a hermetized context. So ruby function defined in one namespace will be visible in another and it may be unintentionally overridden.

It would be nice if namespace could be a context for ruby functions as well as rake tasks.

ciembor avatar Oct 31 '18 15:10 ciembor

What about using a module to "namespace" your methods? That's the Ruby style to do it…

mblumtritt avatar Nov 04 '18 15:11 mblumtritt

This is how I do it, however having namespace block for rake task is a bit confusing and I think a lot of people do a mistake of defining global functions inside of a namespace block and expecting that they are hermetized. There is also no way to include the module in a task, so even when I define a module, I have to type its name to call its method.

module Hermetized
  module Context
    def self.echo(text)
      puts text
    end
  end
end

namespace :hermetized do
  namespace :context do
    desc 'This task has hermetized function'
    task :print do
      Hermetized::Context::echo('this works, but is it nice?')
    end
  end
end

This is very inconvenient, and I think there should be a way to define functions namespaced in a rake task. It could work in a similar way to rspecs let block.

namespace :hermetized do
  namespace :context do
    let(:echo) do
      -> (text) { puts text }
    end

    desc 'This task has hermetized function'
    task :print do
      echo.call('this could work')
    end
  end
end

Or even with nicer syntax.

namespace :hermetized do
  namespace :context do
    fun(:echo, :text) do
      puts text
    end

    desc 'This task has hermetized function'
    task :print do
      echo('this could work')
    end
  end
end

(@mblumtritt please, take a look)

ciembor avatar Nov 04 '18 16:11 ciembor

I see what you like to have/use. The problem is that you deal with class instances when you deal with namespaces in Rake. But there may be a solution because defining namespace-specific methods is possible:

namespace :calc do |n|
  def n.add(x)
    x + 21
  end

  task :test do |t, a|
    puts n.add(21)
  end
end

It's not very elegant; I would prefer modules like suggested before…

mblumtritt avatar Jan 02 '19 10:01 mblumtritt

Using Rake::DSL and singleton methods is a way to do this. First, add a file with a helper method define_rake_task and require it in Rakefile

def define_rake_task(*args, &task_block)
  Class.new do
    include Rake::DSL

    define_method(:initialize) do
      task(*args) do |*task_args|
        instance_exec(*task_args, &task_block)
      end
    end
  end.new
end

Then replace old task method with your helper method define_rake_task.

namespace :user do
  define_rake_task echo_test1: :environment do
    def echo(x)
      puts x
    end

    echo 'A'
    echo 'B'
  end

  define_rake_task echo_test2: :environment do
    echo 'C'
  end
end

Test it:

$ rake user:echo_test1 user:echo_test2
A
B
rake aborted!
NoMethodError: undefined method `echo' for #<#<Class:0x00007f8f9b4bfbb8>:0x00007f8f9b4bf4d8>

It will be nice if it is supported by this gem in default. Allow we to define methods in any task or namespace by using Ruby's def keyword. Just like how rspec does (See: https://relishapp.com/rspec/rspec-core/v/3-8/docs/helper-methods/arbitrary-helper-methods).

khiav223577 avatar Jul 10 '20 03:07 khiav223577