frontman icon indicating copy to clipboard operation
frontman copied to clipboard

Add ability to monkey patch frontman / global_require

Open westonganger opened this issue 5 years ago • 6 comments

Its pretty difficult to monkey patch frontman. Currently I am using the following technique at the top of my config file.

# config.rb

::Frontman::App.class_eval do
  def import_config(path)
    eval File.read(path)
  end
end

It would be nice if this could be extracted to another file somehow. The problem here is that the context is within Frontman::App. Maybe we could have a method like global_require that requires files from the global context.

westonganger avatar Sep 01 '20 01:09 westonganger

Do you have some ideas on what this would look like? Some (code) examples would help in understanding how this would work exactly.

In the Algolia documentation, we currently monkey patch in seperate files when needed, and then require those files in our config.rb:

# config.rb

require './lib/overrides/sitemap'

I only think this wouldn't work for the Frontman::App class, because the config.rb file is executed in the context of this class. However, this means that you can define methods in config.rb that are then attached to Frontman::App. So instead of the code you're including, I believe you can use the following:

def import_config(path)
  eval File.read(path)
end

DevinCodes avatar Sep 02 '20 10:09 DevinCodes

It would appear that any require calls are also executed in the context of Front::App.

https://github.com/algolia/frontman/blob/f73d660e2cacc7b1eada5f4453a0b118b6a945ef/lib/frontman/bootstrapper.rb#L34

This will force us to prefix every single module/class in these files with :: to access the global namespace. This is going to make it difficult to include other code.

westonganger avatar Sep 02 '20 15:09 westonganger

This will force us to prefix every single module/class in these files with :: to access the global namespace. This is going to make it difficult to include other code.

I think this is only the case if there's a collision with class names in the Frontman namespace, but it would be a best practice in that case to always use :: to access the global namespace from your config.rb file to prevent naming collisions from happening.

I don't see a quick way to solve this, so I'll add it to the documentation for now. Feel free to dump ideas for solving this if you have any 🙂

DevinCodes avatar Sep 09 '20 07:09 DevinCodes

It would be cool if maybe there is a way to get the global/Kernel binding and evaluate the code within this binding. I would love to hear if anyone has an idea for this.

westonganger avatar Sep 09 '20 19:09 westonganger

I think we should be able to accomplish the global requires using:

module Frontman
  class App
    def global_scope(&block)
      Kernel.instance_exec(&block)
    end

    def global_require(file)
      Kernel.instance_exec do
        instance_eval(file)
      end
    end
  end
end

Then use like so:

global_scope do
  require 'foo'
end

# OR

global_require('foo')

westonganger avatar Jan 06 '21 16:01 westonganger

If that works indeed, it seems like an elegant solution to me 😄 Feel free to open a PR for this!

DevinCodes avatar Jan 07 '21 10:01 DevinCodes