How to find the directory of an app that is using your gem? - ruby

I'm writing a gem that will access a configuration yaml file in the ruby app that the gem is required in. I've already checked out Dir.pwd but that only gives the directory that I am running the script from. Am I thinking about this the wrong way? Here's sort of what I'm talking about.
The gem would have a constant which could be accessed by Gem::CONSTANT. The code that instantiates this constant would be
module Gem
config_file = #code to retrieve the yaml file.
CONSTANT = config_file[:constant]
end
Then other methods would perform operations on these constants. Any help would be appreciated let me know if I need to be more specific.

Look at File.dirname($0).
$0 is the absolute path/name of the currently running script.

Related

Why doesn't Ruby 'require' allow relative paths?

This SO post sort of answers the question by claiming that require will only search relative to the path that the script was run in. But this really does not appear to be true. I will elaborate.
I created a quick C extension and compiled it to mytest.so. Then, in the same directory I fired up irb.
irb(main):009:0> require 'mytest.so'
LoadError: cannot load such file -- mytest.so
This is expected because the claim in the other answer is that require is searching relative to where irb was run from. In my case that would be /usr/bin/irb. So I tried the required_relative solution from the other question:
irb(main):006:0> require_relative './mytest.so'
LoadError: cannot infer basepath
No luck. And FYI - mytest.so is tab-completing here so irb is clearly aware it is in the current working directory. Furthermore, I can easily prove this:
irb(main):004:0> system("pwd")
/home/mike/Documents/ruby_test
# => true
irb(main):005:0> File.expand_path("./")
# => "/home/mike/Documents/ruby_test"
Okay final test, I will assume that irb is being executed in /usr/bin despite the evidence pointing against that.
irb(main):011:0> require '../../home/mike/Documents/ruby_test/mytest.so'
LoadError: cannot load such file -- ../../home/mike/Documents/ruby_test/mytest.so
I would greatly appreciate if anyone can shed some light on what is going on with require?
BTW, I am aware I can solve this issue by giving the exact file path. This question is about trying understand what is happening beneath the surface.
require '/home/mike/Documents/ruby_test/mytest.so' # this works
tl;dr: IRB is special and has some odd rules. Ruby in general works just fine with relative paths.
require will search the load path (which you can see by inspecting $: or $LOAD_PATH). This will not include the directory that you launched IRB from:
> $:
=> ["/usr/local/rvm/rubies/jruby-head/lib/ruby/2.2/site_ruby", "/usr/local/rvm/rubies/jruby-head/lib/ruby/stdlib"]
So there's no joy there, unless you explicitly add your directory to the load path. This is what Rubygems and Bundler spends most of their time doing - they manage the load paths for gems so you don't have to worry about it. However, this doesn't help you with single files.
Additionally, require_relative will search from the directory that __FILE__ is in, but in IRB, this is a non-directory (irb) value! This is why you get the "can't infer basepath" issue when trying require_relative from IRB; since the currently executing file, __FILE__, isn't a proper path, require_relative can't figure out where to start from.
When you are not running from IRB, this isn't really an issue; require_relative 'mytest.so' should work just fine when you execute it in a script, since the currently-executing script will populate __FILE__. That is, if you have loader.rb and mytest.so and execute loader via ruby loader.rb, require_relative should work just fine.
If you want to run this in IRB, consider something like:
require "#{__dir__}/mytest.so"
which will expand out to the current working directory, which should by default be the directory you've launched it from. I would recommend that you not do this in a script, though, since it depends on __dir__ not having been changed, and that may be difficult to guarantee.
From the documentation:
Loads the given name, returning true if successful and false if the
feature is already loaded.
If the filename does not resolve to an absolute path, it will be
searched for in the directories listed in $LOAD_PATH ($:).
If the filename has the extension “.rb”, it is loaded as a source
file; if the extension is “.so”, “.o”, or “.dll”, or the default
shared library extension on the current platform, Ruby loads the
shared library as a Ruby extension. Otherwise, Ruby tries adding
“.rb”, “.so”, and so on to the name until found. If the file named
cannot be found, a LoadError will be raised.
For Ruby extensions the filename given may use any shared library
extension. For example, on Linux the socket extension is “socket.so”
and require 'socket.dll' will load the socket extension.
The absolute path of the loaded file is added to $LOADED_FEATURES
($"). A file will not be loaded again if its path already appears in
$". For example, require 'a'; require './a' will not load a.rb again.
The answer to your question is that it simply isn't designed to.
You could use require_relative if you want to require files relative to your files' path.
You could add your projects library folder to the $LOAD_PATH to get the functionality you are asking for, that is, require 'my_library'
In IRB for loading local files, you may want to use load instead, as it gives you the ability to load file/library multiple times, where require will load the file/library only once. The __FILE__ variable works in IRB just like it should. It is the identification of the file (in this case the open process) in which the variable is invoked.

uninitialized constant Twitter (NameError)

Hey guys i have a problem i am facing with the twitter gem. I have a file (twitter.rb) with this content
require "rubygems"
require "twitter"
puts Twitter.user_timeline("roykasa").first.text
puts Twitter.user("roykasa").location
search = Twitter::Search.new
search.containing("hate").to("StewieJokess").
result_type("recent").each do |r| puts r.text end
When i run the file i get this error :
uninitialized constant Twitter (NameError)
I read somewhere on SO where a user had a similar problem and he solved it by installing a new version of ruby and rubygems but the problem i am having is am running suse 12.1 and am running the latest versions of both ruby and ruby gems. No rpms can be found from 3rd parties anywhere. Atleast i have searched. Does anyone know another way round this?
If you are running Ruby 1.8.x you should be able to solve your problem by renaming your own script to anything different than twitter.rb.
This is because the main file in the twitter gem is named exactly like this and your file probably overrides it in combined virtual file system that the $LOAD_PATH order creates. Before Ruby 1.9.x, require did not only load from library directories, but preferred to load files relative to the current working directory of your process, in this case, the directory where your script lies in.
Do not name your file as twitter.rb, also make sure there is no other file in the same directory with the name twitter.rb

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.

ruby require not working

I'm new to ruby, but I'm working on my first ruby program. It currently has two files, one is a library of functions (xgync.rb stored in lib) the other is the executable xgync stored in 'bin'. (Project visible here https://bitbucket.org/jeffreycwitt/xgync/src) I've also created a symlink to my /usr/local/bin/xgync so that I can write the command xgync {arguments} from anywhere in the terminal.
The problem seems to be that bin/xgync depends on the library lib/xgync.rb. I've written this dependency in bin/xgync as follows:
$:.unshift(File.dirname(__FILE__) + '/../lib')
require "xgync"
However, i keep getting the following error:
/Users/JCWitt/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require': cannot load such file -- xgync (LoadError)
from /Users/JCWitt/.rvm/rubies/ruby-1.9.3-p194/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
from /usr/local/bin/xgync:4:in `<main>'
can you see anything wrong with what I've written? Could the symlink be somehow messing things up?
Thanks for your help :)
When using ruby 1.9.x you don't usually alter the path with the $:.unshift when requiring other files in your project.
Instead the best practice is to use require_relative instead.
require_relative '../lib/xgync.rb'
require_relative requires files relative to the file you are currently editing.
But the error you experience appears, because you require a file, which does not exist:
bin/xgync
lib/xgync.rb
These are the files in your project according to your question, and the code-excerpt is from bin/xgync you extended the path to look for files in lib/ but you try to require 'xgync' which is a file, that is not present in lib/, so if you wanted to use this method (instead of require_relative you would have to use require 'xgync.rb'.

Can I determine which paths RubyGems added to the load path to make my command line app work?

In a gemspec, I can specify the require_paths, which represent paths I want to be able to require from at runtime. These get put into the $LOAD_PATH by RubyGems.
My question is: is there a way I can determine what these paths are at runtime? Can I examine elements of $LOAD_PATH and know which ones were added just for my gem?
Update: Ultimately, I would like to dynamically load ruby files from inside the gem, e.g.
load_from 'foo/bar'
And have that find $MY_GEMS_LIB_DIR/foo/bar/*.rb. I can certainly go through the entire $LOAD_PATH looking for foo/bar, but I'd rather restrict it just to the gem.
I don't know if I understood your needing (my english is bad :-/ ); anyway, if the problem is to determine the directories that will be loaded when you require a gem you can use Gem::Specification.lib_dirs_glob:
Gem::Specification.find_by_name('irbtools').lib_dirs_glob
#=> "/home/my_user/.rvm/gems/ruby-1.9.3-p125/gems/irbtools-1.2.2/lib"
Gem::Specification.find_by_name('xyz').lib_dirs_glob
# raises a Gem::LoadError
So a possible implementation of load_from could be:
def load_from(gem_name, path)
path_to_load = File.join(Gem::Specification.find_by_name(gem_name).lib_dirs_glob, path)
Dir.glob(path_to_load).each(&method(:load))
end
Trying to load Thor::CoreExt :
Thor::CoreExt #=> NameError: uninitialized constant Thor
load_from 'thor', 'thor/core_ext/*.rb'
Thor::CoreExt #=> Thor::CoreExt
This works on my machine with ruby 1.9.3 and gem 1.8.21 .
If I understand you correctly, this should do (Ruby 1.9.3):
before = $LOAD_PATH.dup
require 'the_gem'
added_paths = $LOAD_PATH - before
Of course, this will include the paths added by the dependencies.
You can use the $: global in irb. There is also the gem which command which gives you a library path, but I'm not sure if that includes exactly what you want.
Looks like Gem.find_files may help you.

Resources