无论如何要在REPL中重新加载修改后的gem文件

时间:2014-03-29 14:20:37

标签: ruby gem bundler read-eval-print-loop

在尝试构建Ruby gem(使用Bundler)时,我倾向于使用Bundler提供的REPL来测试代码 - 可以通过bundle console访问。

有没有办法重新加载整个项目?我最终再次加载单个(已更改)文件以测试新的更改。

1 个答案:

答案 0 :(得分:1)

以下hack适用于我的一个相对简单的gem和Ruby 2.2.2。我很想知道它是否适合你。它做出以下假设:

  1. 您拥有传统的文件夹结构:名为lib/my_gem_name.rb的文件和文件夹lib/my_gem_name/,下面有任何文件/文件夹结构。
  2. 您要重新加载的所有类都嵌套在顶层模块MyGemName中。
  3. 如果在lib/my_gem_name/下的某个文件中,您在MyGemName名称空间之外的猴子补丁类,它可能无效。

    如果您对上述假设很满意,请将以下代码放在lib/my_gem_name.rb中的模块定义中,并尝试一下:

    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
    

    如果您不希望此功能在生产中可用,请考虑在bin/console脚本中对此进行修补,但请确保将行$LOADED_FEATURES.select{|x| x.starts_with?(__FILE__.chomp('.rb'))}更改为将返回列表的内容给定代码的新位置的相关加载文件。

    如果你有一个标准的宝石结构,这应该有效: $LOADED_FEATURES.select{|x| x.starts_with?(File.expand_path('../../lib/my_gem_name'))}(确保将您的猴子修补代码放在IRB.startPry.start之前)