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.
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:
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.
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)
Related
I've been assigned to perform some maintenance on an existing Ruby gem project. I'm not native to Ruby, but there are files that to me appear to be unnecessary.
Let's say that folder a/b/c is the root of the project, representing module A::B::C. There are also sub modules A::B::C::D1, A::B::C::D2, A::B::C::D3, etc.
The first file that I don't find logical is file modules.rb in folder a/b/c. This contains an empty module declaration for every module in the entire project:
module A::B::C
end
module A::B::C::D1
end
module A::B::C::D2
end
# etc
I tried to find out what this file is for, but I couldn't find any mentions anywhere on Google apart from examples where the modules.rb actually contains all of the code (e.g. https://github.com/shrinidhi99/learn-ruby-for-fun/blob/master/Modules.rb, https://borg.garasilabs.org/andrew/ruby-training/-/blob/master/codes/modules.rb, https://zetcode.com/lang/rubytutorial/oop2/). There's also no reference to it anywhere in the project itself. The only thing it appears to achieve is to provide documentation on rubydoc.info. That can probably be inlined in the separate files.
Besides that, most modules M have a matching file m.rb that sits besides the m folder. For example, file a/b/c.rb, a/b/c/d1.rb, etc. This file contains nothing except require statements of all files in the matching module. That means that file a/b/c.rb also has require statements for a/b/c/d1 etc.
There are two sub modules of A::B::C that are not included in these require files, and these modules have several sub modules of their own. That, combined with the fact that each file should just mention it requirements itself, indicates that these require files are completely unnecessary. The only reason I can think of is mentioned here: https://stackoverflow.com/a/26470550/
Am I wrong in thinking that I can simply remove these files, or are they still necessary? Or is this something that's just "the Ruby way"?
Something like this should work. One file -- one class (or module). If you have namespace and there is general logic in it -- separate file for the namespace
# a.rb
Dir[File.join(__dir__, 'a', '*.rb')].each { |f| require f }
module A
# general A logic here
end
# a/b.rb
Dir[File.join(__dir__, 'b', '*.rb')].each { |f| require f }
module A
module B
# general A::B logic here
end
end
# a/b/c.rb
module A
module B
module C
end
end
end
# a/b/d.rb
module A
module B
module D
end
end
end
# x.rb
require_relative 'a'
class X
include A::B::C
end
In my Rails application I have a file sample_data.rb inside /lib/tasks as well as a bunch of test files inside my /spec directory.
All these files often share common functionality such as:
def random_address
[Faker::Address.street_address, Faker::Address.city].join("\n")
end
Where should I put those helper functions? Is there some sort of convention on this?
Thanks for any help!
You could create a static class, with static functions. That would look something like this:
class HelperFunctions
def self.random_address
[Faker::Address.street_address, Faker::Address.city].join("\n")
end
def self.otherFunction
end
end
Then, all you would need to do is:
include your helper class in the file you want to use
execute it like:
HelperFunctions::random_address(anyParametersYouMightHave)
When doing this, make sure you include any dependencies in your HelperFunctions class.
If you're sure it's rake only specific, you also can add in directly in RAILS_ROOT/Rakefile (that's probably not the case for the example you use).
I use this to simplify rake's invoke syntax :
#!/usr/bin/env rake
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require File.expand_path('../config/application', __FILE__)
def invoke( task_name )
Rake::Task[ task_name ].invoke
end
MyApp::Application.load_tasks
That way, I can use invoke "my_namespace:my_task" in rake tasks instead of Rake::Task[ "my_namespace:my_task" ].invoke.
You share methods in a module, and you place such a module inside the lib folder.
Something like lib/fake_data.rb containing
module FakeData
def random_address
[Faker::Address.street_address, Faker::Address.city].join("\n")
end
module_function
end
and inside your rake task just require the module, and call FakeData.random_address.
But, if it is like a seed you need to do every time you run your tests, you should consider adding this to your general before all.
E.g. my spec_helper looks like this:
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
RSpec.configure do |config|
config.use_transactional_fixtures = true
config.infer_base_class_for_anonymous_controllers = false
config.order = "random"
include SetupSupport
config.before(:all) do
load_db_seed
end
end
and the module SetupSupport is defined in spec/support/setup_support.rb and looks as follows:
module SetupSupport
def load_db_seed
load(File.join(Rails.root, 'db', 'seeds.rb'))
end
end
Not sure if you need to load the seeds, or are already doing this, but this is the ideal spot to also generate needed fake data.
Note that my setup support class is defined in spec/support because the code is only relevant to my specs, I have no rake task also needing the same code.
I have a command line utility written in Ruby using GLI framework. I would like to have configuration for my command line utility in my home directory, using Ruby itself as DSL to handle it (similar to Gemfile or Rakefile).
I have in class ConfigData in folder lib/myapp. The class looks like following way:
class ConfigData
##data = {}
class ConfigItem
def initialize
#data = {}
end
def missing_method(name, *args)
#data[name] = args[0]
end
end
def self.add(section)
item = ConfigItem.new()
yield item
##data[section]=item
end
end
Now, what I would like to have, is the config file, preferrably with name Myappfile, in current working folder, with the following content
add('section1') do |i|
i.param1 'Some data'
i.param2 'More data'
end
When this code was included between class and end of ConfigData, it worked fine. But now I would like to have it placed in the working folder, where I start the application.
I tried require('./Myappfile') between class and end of ConfigData, but it doesn't work for me. I tried to read the source codes of rake, but it is not very much clear to me.
Any hint how this can be solved?
To evaluate code within the context of an instance, which is what you want to do, you need the instance_eval() method. Never, ever, use normal eval. Ever. Anyway, here's how you'd load your fingi file and get the data:
config = ConfigData.new
config.instance_eval(File.read("Myconfig"))
#Access configuration data here from the config object
That simple. After you've loaded the object in that way, you can access values of the object.
WARNING: This is not very secure. This is actually a gaping security hole. Here's the secure version:
f = Fiber.new {str = File.read("Myconfig"); $SAFE = 4; config = ConfigData.new; config.instance_eval(str); Fiber.yield config}
confdata = f.resume
#Access configuration data here from confdata.
This executes the external code in a (sort of) sandbox, so that it can't do anything dastardly.
Also, why don't you just use a YAML config? Unless configuration needs to run code like pwd or access RUBY_VERSION, YAML is much simpler and more secure, in addition to being more failproof.
I've placed a set of .rb files in a directory modules/. Each of these files contains a module. I'm loading all of these files from a separate script with this:
Dir["modules/*.rb"].each {|file| load file }
But then I have to include them, one by one, listing and naming each of them explicitly:
class Foo
include ModuleA
include ModuleB
# ... and so on.
end
I'm wondering if there is some non-explicit one-liner that can accomplish this, a la (pseudocode) ...
class Foo
for each loaded file
include the module(s) within that file
end
# ... other stuff.
end
I've considered actually reading the files' contents, searching for the string "module", and then extracting the module name, and somehow doing the include based on that -- but that seems ridiculous.
Is what I'm trying to do advisable and/or possible?
You can manually define some variable in each of your file and then check for its value while including the file.
For example, module1.rb:
export = ["MyModule"]
module MyModule
end
And the second one, module2.rb:
export = ["AnotherModule", "ThirdModule"]
module AnotherModule
end
module ThirdModule
end
And then just include all of them in your file (just the idea, it may not work correctly):
class Foo
Dir["modules/*.rb"].each do |file|
load file
if export != nil
export.each do { |m| include(Kernel.const_get(m))
end
end
end
I would expect it to be possible since you can place ruby code below class and include is also just a ruby call but you have to think of which module needs to be included where since your code is not the only one that loads and includes modules. And you wil not want to include all modules from the object space in your class.
So i personally would include them by name - you are adding funcionality to a class and by naming each module you document what is being added where.
And if you want this one liner: put a code in each module that adds its name to global list (remembering the disadvantages if using globals at all) and then iterate this list in the class definition to include all of them or part based on a criterium like name or what.
What I want is a way of not having to 'require' the class under test in each spec file.
So hoping there is a means of setting the root of the source code under test and rspec automatically mapping my tests, or any other means of automatically mapping specs to ruby files.
In Rspec for rails this happens magically, but this is not a rails project and I can't find any useful information.
I am assuming you have a lib folder and a spec folder within your project where you have code and specs respectively. Create a spec/spec_helper.rb and add
# project_name/spec/spec_helper.rb
$: << File.join(File.dirname(__FILE__), "/../lib")
require 'spec'
require 'main_file_within_lib_folder_that_requires_other_files'
Now within your individual spec files now you just need to add the following line like rails
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
What you have to do is to redefine Object.const_missing.
Found this basic example, modify it to fit your needs (set the right path, etc.):
def Object.const_missing(name)
#looked_for ||= {}
str_name = name.to_s
raise "Class not found: #{name}" if #looked_for[str_name]
#looked_for[str_name] = 1
file = str_name.downcase
require file
klass = const_get(name)
return klass if klass
raise "Class not found: #{name}"
end