Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Anyway to reload the modified gem files in the REPL

While trying to build a Ruby gem (using Bundler), I tend to test the code using the REPL provided by Bundler - accessible via bundle console.

Is there any way to reload the entire project in it? I end up loading individual (changed) files again to test the new change.

like image 792
Jikku Jose Avatar asked Mar 29 '14 14:03

Jikku Jose


People also ask

How do I reinstall Ruby bundles?

Install BundlerSelect Tools | Bundler | Install Bundler from the main menu. Press Ctrl twice and execute the gem install bundler command in the invoked popup. Open the RubyMine terminal emulator and execute the gem install bundler command.

What is GEM pristine?

Meanwhile, gem pristine --all cleans all installed gems for that Ruby version. If a developer forgets which gems in their project they might have been debugging, the Rubygems gem pristine [GEMNAME] command may be inconvenient. One can avoid waiting for gem pristine --all , and instead run bundle pristine .

What is the GEM file?

A Gemfile describes the gem dependencies required to execute associated Ruby code. Place the Gemfile in the root of the directory containing the associated code. For instance, in a Rails application, place the Gemfile in the same directory as the Rakefile .


1 Answers

The following hack works for a relatively simple gem of mine and Ruby 2.2.2. I'll be curious to see if it works for you. It makes the following assumptions:

  1. You have the conventional folder structure: a file called lib/my_gem_name.rb and a folder lib/my_gem_name/ with any files / folder structure underneath.
  2. All the classes you want to reload are nested within your top module MyGemName.

It will probably not work if in one of the files under lib/my_gem_name/ you monkey-patch classes outside of your MyGemName namespace.

If you're good with the assumptions above, put the following code inside the module definition in lib/my_gem_name.rb and give it a try:

module MyGemName

  def self.reload!
    Reloader.new(self).reload
  end

  class Reloader
    def initialize(top)
      @top = top
    end

    def reload
      cleanup
      load_all
    end

    private

    # @return [Array<String>] array of all files that were loaded to memory
    # under the lib/my_gem_name folder. 
    # This code makes assumption #1 above.  
    def loaded_files
      $LOADED_FEATURES.select{|x| x.starts_with?(__FILE__.chomp('.rb'))}
    end

    # @return [Array<Module>] Recursively find all modules and classes 
    # under the MyGemName namespace.
    # This code makes assumption number #2 above.
    def all_project_objects(current = @top)
      return [] unless current.is_a?(Module) and current.to_s.split('::').first == @top.to_s
      [current] + current.constants.flat_map{|x| all_project_objects(current.const_get(x))}
    end

    # @return [Hash] of the format {Module => true} containing all modules 
    #   and classes under the MyGemName namespace
    def all_project_objects_lookup
      @_all_project_objects_lookup ||= Hash[all_project_objects.map{|x| [x, true]}]
    end

    # Recursively removes all constant entries of modules and classes under
    # the MyGemName namespace
    def cleanup(parent = Object, current = @top)
      return unless all_project_objects_lookup[current]
      current.constants.each {|const| cleanup current, current.const_get(const)}
      parent.send(:remove_const, current.to_s.split('::').last.to_sym)
    end

    # Re-load all files that were previously reloaded
    def load_all
      loaded_files.each{|x| load x}
      true
    end
  end
end

If you don't want this functionality to be available in production, consider monkey-patching this in the bin/console script, but make sure to change the line $LOADED_FEATURES.select{|x| x.starts_with?(__FILE__.chomp('.rb'))} to something that will return a list of relevant loaded files given the new location of the code.

If you have a standard gem structure, this should work: $LOADED_FEATURES.select{|x| x.starts_with?(File.expand_path('../../lib/my_gem_name'))} (make sure to put your monkey patching code before the IRB.start or Pry.start)

like image 183
AmitA Avatar answered Nov 10 '22 11:11

AmitA