Make config available through an entire ruby gem - ruby

I am creating a gem where I'd like to define some constants to be available.
In my actual gemspec, I added the following:
config = File.expand_path('../config', __FILE__)
$LOAD_PATH.unshift(config) unless $LOAD_PATH.include?(config)
require 'constants.rb'
In constants.rb I have a simple var defined: $FOO = "Hello, World!"
Then in my lib/gem_name/core.rb, I'm attempting to puts $FOO but it doesn't seem to be available. No error, just comes up blank. Am I not understanding how gem dependencies and the require tree works here?
** UPDATE **
I have also tried just adding a config directory directly underneath lib which is in the LOAD_PATH already. then in my library, I am attempting to require config/constants, but that's saying it cannot load such a file.
I have also tried just moving constants.rb to the lib directory directly and requiring that, and it's warning me that it cannot load such file. Something is terribly wonky.

According to Katz,
When your gem is built, Rubygems will run that code and create a static representation. This means it’s fine to pull your gem’s version or other shared details out of your library itself. Do not, however, use other libraries or dependencies.
That means that require 'constants.rb' and $LOAD_PATH.unshift(...) etc are run when you build the gem. At runtime, it doesn't change the $LOAD_PATH or cause a global require. Use gem.require_paths instead to modify $LOAD_PATH at runtime.
For example, in your gemspec, use
gem.files += Dir['config/**/*']
gem.require_paths = %w[lib config]
Then in places where $FOO is required, use
require 'constants'
Side Notes
You don't need to include the .rb extension when using require.
Make sure all the files you need are in gem.files.

Related

Ruby: require works in gem, fails when running from source

Try creating a gem based on bundler's official guide on developing a Ruby gem.
Running bundle gem foodie will create a structure and generate files in the lib directory:
foodie
version.rb
foodie.rb
foodie.rb reads
require "foodie/version"
module Foodie
# Your code goes here...
end
Running ruby lib/foodie.rb (or also from different directories) will result in
C:/Ruby23-x64/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- foodie/versio
n (LoadError)
from C:/Ruby23-x64/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from foodie.rb:1:in `<main>'
On the other hand installing the gem via rake install and then requiring the gem works just fine.
It works from source if require "foodie/version" is changed to require_relative "foodie/version" in foodie.rb. As I understand
require works based on modules
require_relative works based on directory structure
To me the latter looks like a hack. It'd no longer make sense to structure your code via modules as it wouldn't be enforced (maybe it'd still make sense but you could make mistakes and never notice).
My questions are:
Is it possible to test a gem from source without installing it while following the bundler convention (using require instead of require_relative)?
Why does the gem work after installed?
Is there any best practice for the usage of require, require_relative, modules, files and general structure?
Thank you.
You need to add your lib dir to Ruby’s load path. The load path is a list of directories that Ruby searches for files in when you call require. Rubygems also manages the load path when you are using gems, which is why your code works when installed as a gem.
You say “as I understand ... require works based on modules”, this is not correct. require works with files, it’s just convention that a class or module is defined in a file with a matching name, e.g. MyModule might be in my_module.rb.
There are a few ways to add a dir to the load path. From the command line you can use the -I option:
$ ruby -I lib lib/foodie.rb
If you wanted to avoid typing -I lib you could use the RUBYLIB environment variable. Ruby adds the contents of this to the load path:
$ export RUBYLIB=lib
$ ruby lib/foodie.rb
(On Windows I think you will need to use set rather than export.)
You can also manipulate the load path from withing the program itself. It is stored in the global variable $LOAD_PATH, aliased as :$. This is how Rubygems and Bundler manage your gems.

How can I load other scripts into the main script? [duplicate]

Is there any major difference between load and require in the Ruby on Rails applications? Or do they both have the same functionality?
require searches for the library in all the defined search paths and also appends
.rb or .so to the file name you enter. It also makes sure that a library is only
included once. So if your application requires library A and B and library B requries library A too A would be loaded only once.
With load you need to add the full name of the library and it gets loaded every time you
call load - even if it already is in memory.
Another difference between Kernel#require and Kernel#load is that Kernel#load takes an optional second argument that allows you to wrap the loaded code into an anonymous empty module.
Unfortunately, it's not very useful. First, it's easy for the loaded code to break out of the module, by just accessing the global namespace, i.e. they still can monkeypatch something like class ::String; def foo; end end. And second, load doesn't return the module it wraps the code into, so you basically have to fish it out of ObjectSpace::each_object(Module) by hand.
I was running a Rails application and in Gemfile, I had a specific custom gem I created with the option "require: false". Now when I loaded up rails server or rails console, I was able to require the gem in the initializer and the gem was loaded. However, when I ran a spec feature test with rspec and capybara, I got a load error. And I was completely bewildered why the Gem was not found in $LOAD_PATH when running a test.
So I reviewed all the different ways that load, require, rubygems and bundler interact. And these are a summary of my findings that helped me discover the solution to my particular problem:
load
1) You can pass it an absolute path to a ruby file and it will execute the code in that file.
load('/Users/myuser/foo.rb')
2) You can pass a relative path to load. If you are in same directory as file, it will find it:
> load('./foo.rb')
foo.rb loaded!
=> true
But if you try to load a file from different directory with load(), it will not find it with a relative path based on current working directory (e.g. ./):
> load('./foo.rb')
LoadError: cannot load such file -- foo.rb
3) As shown above, load always returns true (if the file could not be loaded it raises a LoadError).
4) Global variables, classes, constants and methods are all imported, but not local variables.
5) Calling load twice on the same file will execute the code in that file twice. If the specified file defines a constant, it will define that constant twice, which produces a warning.
6) $LOAD_PATH is an array of absolute paths. If you pass load just a file name, it will loop through $LOAD_PATH and search for the file in each directory.
> $LOAD_PATH.push("/Users/myuser")
> load('foo.rb')
foo.rb loaded!
=> true
require
1) Calling require on the same file twice will only execute it once. It’s also smart enough not to load the same file twice if you refer to it once with a relative path and once with an absolute path.
2) require returns true if the file was executed and false if it wasn’t.
3) require keeps track of which files have been loaded already in the global variable $LOADED_FEATURES.
4) You don’t need to include the file extension:
require 'foo'
5) require will look for foo.rb, but also dynamic library files, like foo.so, foo.o, or foo.dll. This is how you can call C code from ruby.
6) require does not check the current directory, since the current directory is by default not in $LOAD_PATH.
7) require_relative takes a path relative to the current file, not the working directory of the process.
Rubygems
1) Rubygems is a package manager designed to easily manage the installation of Ruby libraries called gems.
2) It packages its content as a zip file containing a bunch of ruby files and/or dynamic library files that can be imported by your code, along with some metadata.
3) Rubygems replaces the default require method with its own version. That version will look through your installed gems in addition to the directories in $LOAD_PATH. If Rubygems finds the file in your gems, it will add that gem to your $LOAD_PATH.
4) The gem install command figures out all of the dependencies of a gem and installs them. In fact, it installs all of a gem’s dependencies before it installs the gem itself.
Bundler
1) Bundler lets you specify all the gems your project needs, and optionally what versions of those gems. Then the bundle command installs all those gems and their dependencies.
2) You specify which gems you need in a file called Gemfile.
3) The bundle command also installs all the gems listed in Gemfile.lock at the specific versions listed.
4) Putting bundle exec before a command, e.g. bundle exec rspec, ensures that require will load the version of a gem specified in your Gemfile.lock.
Rails and Bundler
1) In config/boot.rb, require 'bundler/setup' is run. Bundler makes sure that Ruby can find all of the gems in the Gemfile (and all of their dependencies). require 'bundler/setup' will automatically discover your Gemfile, and make all of the gems in your Gemfile available to Ruby (in technical terms, it puts the gems “on the load path”). You can think of it as an adding some extra powers to require 'rubygems'.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
2) Now that your code is available to Ruby, you can require the gems that you need. For instance, you can require 'sinatra'. If you have a lot of dependencies, you might want to say “require all of the gems in my Gemfile”. To do this, put the following code immediately following require 'bundler/setup':
Bundler.require(:default)
3) By default, calling Bundler.require will require each gem in your Gemfile. If the line in the Gemfile says gem 'foo', :require => false then it will make sure foo is installed, but it won’t call require. You’ll have to call require('foo') if you want to use the gem.
So given this breadth of knowledge, I returned to the issue of my test and realized I had to explicitly require the gem in rails_helper.rb, since Bundler.setup added it to $LOAD_PATH but require: false precluded Bundler.require from requiring it explicitly. And then the issue was resolved.

How to I load a gem from source?

I have git cloned a repo from Github, now I want to experiment with it, as in I want to poke around the code and mess with it. I've created a file test.rb that should load this gem, but I want to load my locally checked out version, what's the right way to do this?
Right now I'm just using a bunch of "require_relative 'the_gem_name/lib/file'", which feels wrong.
When you require 'foo' Ruby checks all the directories in the load path for a file foo.rb and loads the first one it finds. If no file named foo.rb is found, and you’re not using Rubygems, a LoadError is raised.
If you are using Rubygems (which is likely given that it is included in Ruby 1.9+), then instead of immediately raising a LoadError all the installed Gems are searched to see if one contains a file foo.rb. If such a Gem is found, then it is added to the load path and the file is loaded.
You can manipulate the load path yourself if you want to ensure a particular version of a library is used. Normally this isn’t something that’s recommended, but this is the kind of situation that you’d want to do it.
There are two ways of adding directories to the load path. First you can do it in the actual code, using the $LOAD_PATH (or $:) global variable:
$LOAD_PATH.unshift '/path/to/the/gems/lib/'
require 'the_gem'
Note that you normally want to add the lib dir of the gem, not the top level dir of the gem (actually this can vary depending on the actual Gem, and it’s possible to need to add more than one dir, but lib is the norm).
The other way is to use the -I command line switch to the ruby executable:
$ ruby -I/path/to/the/gems/lib/ test.rb
This way might be a bit cleaner, as normally you don’t want to be messing with the load path from inside your code, but if you’re just testing the library it probably doesn’t matter much.
Following apneadiving's suggestion in the comments, I created a Gemfile and added this line
source "http://rubygems.org"
gem 'gem_name', path: '~/path/to/gem/source/folder'
Then bundle install, and bundle exec ruby test.rb and it worked.

New gem with Bundler, from a class

When we run the bundle gem new_gem command, a directory is created with those files:
create new_gem/Gemfile
create new_gem/Rakefile
create new_gem/.gitignore
create new_gem/new_gem.gemspec
create new_gem/lib/new_gem.rb
create new_gem/lib/new_gem/version.rb
By default, the file new_gem/lib/new_gem.rb is a module named NewGem.
My question is the following: how can I do if NewGem is a class? Rather then having NewGem::NewGem, I would like to just define this class (without a root module).
I tried to just replace module by class inside this file, and then make a local gem in order to test it, but after its installation, I can not load it in IRB (with require 'new_gem').
Thanks for your help.
You should ask yourself why you want to do this. The module is there to namespace your gem's code. Typically to provide a context for all the classes within, but even in a single class gem, this would help to provide conflicts with other code out in the world.
Unless your class is named SomethingThatCouldNeverPossiblyBeDefinedAnywhereElse, leaving that module in place is probably a good thing. And regardless of that, leaving the module intact is still a good thing as it's the convention, and what people expect when examining/using your code.
With that in mind, there are a few things you'd need to do if you wanted a single class gem.
The generated gemspec wants to require 'new_gem/version' to find it's version number. Change that to simply require 'new_gem'.
The gemspec also lists its contained files using git ls, and the generated gem package already has new_gem/version included in the pre-built git repo. Remove this:
git rm lib/new_gem/version.rb
Change your new_gem module to a class, as you did previously.
Remove the generated version.rb require from your class, and instead define the version there, e.g.:
class NewGem
VERSION = '0.0.1'
end
Finally install the gem via rake install. You won't be able to load it in IRB until you've done this.

Ruby - working with `require` command

I am editing a gem in which there are the usual require commands, pointing at the loaded gem (the gem I'm talking about is called nirvana, and the files in it contain require 'nirvana', require 'nirvana/shell' and so on).
When I use the bin-file of the application (/mypath/nirvana/bin/nirvana), I want the require 'nirvana' command written inside it to point to the files in the local fork of that gem (the ones I am editing), and I want not to load the original nirvana gem, that is installed with the classic gem install.
I don't want to substitute all the require 'nirvana' commands with
require File.dirname(File.expand_path(__FILE__)) + '/../lib/nirvana.rb'
... this would resolve my problem, but it's ugly! Is there a way to do not load nirvana gem, and to make require 'nirvana' load my libraries (maybe adding them in the $LOAD_PATH...) ?
You might be running into the require vs. require_relative conundrum in 1.9+.
require is good for loading a gem that is loaded via the normal gems paths, i.e., installed into Ruby's space.
require_relative is good for loading relative to a particular file, for instance, if you're loading a module you wrote and its in the same or a sub-directory or relative directory of yours.
`require_relative 'some/sub/dir/to/file'`
You should only be 'requiring' nirvana.rb once, if you're doing so from your gems binary executable. So this line only needs to appear once. It's quite common to see it appear in these files.
Do note your example can be better written as
require File.expand_path('../lib/nirvana.rb', __FILE__)
As File::expand_path takes an optional second argument (a directory String).
A lot of authors will also shift the lib directory into the $LOAD_PATH before executing the binary so the local files are loaded before attempting to load any installed gems.
If you're using rvm, have a look at gemsets. You can create a gemset that doesn't have the nirvana gem installed, then when you require 'nirvana' you'll only get your local libraries required, as there isn't a nirvana gem to include.
(I'm assuming you're using ruby 1.9, as if you're using 1.8 you could just omit require 'rubygems'.)

Resources