Gem Development Workflow - ruby

I'm working on a fork of someone's gem that is a command line utility. Here's a general overview of the directory structure:
bin/
bin/foo
lib/
lib/foo.rb
lib/foo/bar.rb (etc)
To test it, I normally do something like this:
cd bin/
./foo <args>
But I want to be able to use it from any directory (like it would be once installed). My question is if it's possible to achieve this without installing the gem on my system each time.
My first attempt at this was to create a symbolic link to the foo script that was on my PATH, but this messes with the require 'foo' line in the script since File.dirname(__FILE__) now refers to wherever the symbolic link was created.
Is there a common way of doing this?
(Oh, and here's the relevant lines from the foo script)
#!/usr/bin/env ruby
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
require 'rubygems'
require 'foo'
(EDIT)
I'm aware of the normal ways of testing a library (ie rake test, etc)--I'm specifically interested in using the script from any directory without reinstalling the gem with every change (if possible).

In almost every gem I've looked into, there is a rakefile of some sort. In which case you go to the root of the gem, and go:
rake test
For a list of tasks, use:
rake -T
(This assumes you have rake installed in the first place, obviously: gem install rake if not.)
Also, many a gem also features a gemfile. In this case you can use bundler to install applicable dependencies (in particular, test suites and development-related gems).

Related

Handling require in a project meant to be provided as a gem

I'm completely lost with ruby "require" (and I don't have the option for require_relative since my code is meant to run with ruby 1.8.7.
First some sample code.
requester.rb
require './column/main_column'
....
column/main_column.rb
require './helper' # helper.rb being in the root dir
....
column/segment_column
require './column/main_column'
require './helper' # helper.rb being in the root dir
....
I'm completely lost in how should I use my requires to make my code usable inside a gem and in a standalone way (without assuming a previous gem installation). I was reading at Jekyll's code and every dependencies seems to be loaded in the main rb file which to my opinion is a bit dirty and still do resolve the problem if column/segment_column.rb needs to be called independently.
Please try to argument about different approaches and why one is the best.
You should require files without the extension and relative to your gem project's lib folder.
For example, given this structure:
example-gem/
lib/
example/
gem.rb
example.rb
example-gem.gemspec
You'd require the main example module using:
require 'example'
And in the example.rb file you'd find:
require 'example/gem'
This set of conventions is adopted by the community and is documented on the RubyGems Guides. If they fail, it is often because your gem's lib directory is not in Ruby's $LOAD_PATH, which usually means the gem is not installed.
Building and installing gems during development is cumbersome. For that reason, gems like Bundler became part of the toolboxes of many Rubyists. Bundler lets you use your gem without having to build or install it locally. You can simply bundle exec a script that uses your gem or use bundle console in order to interact with it.

Easier way to test gem

I am in the middle of writing a Ruby Gem, but was wondering if there is an easier way to "test" your gem without having to build it, install it and require it to test it out in an IRB console?
Git clone it in an arbitrary folder, add its lib path to $: in the rakefile if needed (you actually don't in this case, as RakeTest should add it for you), and run rake test directly.
From irb, proceed similarly: add the lib path to $: to bypass the packaged gem. But note that you'll need to reload it when you change it, so it's less convenient than rake.
For completeness, in case you or a future visitor is unfamiliar with Rake:
Running Ruby unit tests with Rake

How do command line utility gems work?

How do gems like "rails", "rspec", and "cucumber" allow user to use commands that start with their gem name??
rails new project
rspec spec
cucumber features
Not all gems have this ability. For example, when I type json even though I have it installed, I get
-bash: json: command not found
Gem's .gemspec file looks like this:
Gem::Specification.new do |s|
s.name = "haml"
s.version = "3.1.8"
....
s.executables = ["haml", "html2haml"]
end
This means that when installing this Gem (haml-3.1.8 in this case) also links to executables (also called "binstubs") will be created for the files haml and html2haml which are found inside the gem's bin/ directory.
In this case, for example the file bin/haml could look like:
#!/usr/bin/env ruby
require 'rubygems'
require 'haml'
puts Haml::VERSION
From rubygems.org documentation on building Gems:
In addition to providing libraries of Ruby code, gems can also expose
one or many executable files to your shell’s PATH. Probably the best
known example of this is rake. Another very useful one is
prettify_json.rb, included with the JSON gem, which formats JSON in a
readable manner (and is included with Ruby 1.9).
[...]
Adding an executable to a gem is a simple process. You just need to
place the file in your gem’s bin directory, and then add it to the
list of executables in the gemspec. Let’s add one for the Hola gem.
[...]
The executable file itself just needs a shebang in order to figure out
what program to run it with.
[...]
All it’s doing is loading up the gem, and passing the first command
line argument as the language to say hello with.
These gems have binaries that can be executed from the CLI. Most gems do not need this functionality and only provide code extensions.
Edit: They may not be 'binaries'. They can be just executable Ruby code as well. Thanks #holger

How to use gems not in a Gemfile when working with bundler?

When using bundler with a project in general and Rails specifically, you have access only to gems defined in your Gemfile. While this makes sense, it can be limiting. Mostly I find it limiting when I want to use a certain RSpec formatter that the rest of the team doesn't use. Unless it's in the Gemfile, it isn't accessible.
Any way around it or I have to add it to Gemfile?
Update: my problem wasn't Bundler but Spork. When running RSpec without Spork I had no problem of using whatever formatter I wanted.
Update #2: it looks like that using Bundler is still the cause of the problem. The difference between using Spork and not using Spork, is that when running RSpec without Spork, it loads the formatter before loading your project and getting into the Bundler "sandbox".
With Bundler:
$ bundle exec irb
>> require 'fivemat'
LoadError: cannot load such file -- fivemat
from (irb):1:in `require'
from (irb):1
from /Users/arikfr/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
Without Bundler:
$ irb
>> require 'fivemat'
=> true
In ChiliProject we allow users to create a Gemfile.local which is included into the main Gemfile on load. This allows users to specify additional gems without having to change our Gemfile to ease updates.
For that, we have included the following code at the bottom of our Gemfile.
gemfile_local = File.expand_path('Gemfile.local', __dir__)
if File.readable?(gemfile_local)
puts "Loading #{gemfile_local}..." if $DEBUG
instance_eval(File.read(gemfile_local))
end
The Gemfile.local itself is excluded from the repository via .gitignore.
I assume that none of these answers have been chosen as correct because they don't do a great job of solving the problem: having additional gems that you can use that by default don't require any changes to files already in the repository to achieve. That is, you don't have to modify any files, and you don't have to live with remembering not to check in your local changes. Here's how I do it.
The idea is basically inverting the dependencies of Holger's answer, such that there's no need to modify the shared Gemfile. Bundler allows one to specify which file is to be used as the gemfile, but strangely the documented methods do not apparently work with its configuration file and will not be fixed. There is a somewhat obscured feature of Bundler that any of the configuration options can be set in an environment variable or passed on the command line. Running all of your commands as bundle [command] --gemfile [yourgemfile] or BUNDLE_GEMFILE="[yourgemfile]" bundle [command] will cause Bundler to read whatever gemfile you want it to. I highly recommend using the environment variable approach, and either creating an alias or exporting the variable for your current session, particularly as I was unable to use the command line switch with the "exec" command.
Therefore, I run rspec like this: BUNDLE_GEMFILE="[mygemfile]" bundle exec rspec [filename], and I have the first part of this aliased as bem in my bashrc. Works like a charm.
Then, you should setup your source control to ignore your Gemfile, either in the project's .gitignore or, to keep the project entirely hygienic without changing even its .gitignore, to your personal global ignore file (which is by default in ~/.config/git/ignore and has the same format as a project's gitignore file).
One other thing to note is that Bundler will create a lockfile based on the Gemfile's name. This is super handy, as it keeps you from overwriting your project's Gemfile.lock if it's checked in, but you need to ignore this new lock file as well. If your gemfile is Foo.bar, look for Foo.bar.lock.
Finally, you can do something similar to Holger's suggestion in your custom Gemfile:
source "http://rubygems.org"
gem "fivemat"
instance_eval(File.read(File.dirname(__FILE__) + "/Gemfile"))
and you're good to go, as long as you remember to specify your Gemfile.
You can use something like this in your Gemfile:
gem 'foo' if ENV['ENABLE_FOO_GEM']
Then just set ENABLE_FOO_GEM in your environment.
export ENABLE_FOO_GEM=1
The gem will be disabled by default, but easily turned on (permanently) by anyone who wants to use it.
Add to .gitignore
Gemfile.local
Gemfile.local.lock
Add to the project a Gemfile.local.sample file with the following content:
# Include gems that are note meant to be part of the project but for development purposes
# That's why Gemfile.local and Gemfile.local.lock must be git-ignored
# To use these gems:
# 1. Create a "Gemfile.local" file (at same level of "Gemfile")
# 2. Prepend "BUNDLE_GEMFILE=Gemfile.local" before "bundle install" or "bundle exec rails c" and so forth.
eval_gemfile "./Gemfile"
group :development, :test do
# Suggested gems
gem "awesome_print", require:"ap"
gem "hirb"
gem "pry"
gem "pry-byebug"
gem "pry-rails"
gem "meta_request"
# My gems
gem "fivemat"
end
I believe the gem Devpack provides the functionality you are looking for.
The gem allows you to add a single gem to your Gemfile which will permit any developer to configure their own preferred set of development gems either for an individual project or globally by creating a .devpack file containing a list of gems.
(I am the author of this gem; I came across this post while developing it so thought it may be worth adding).
In case you still decide to do this (horrible idea):
You can add ruby code to your Gemfile to load a ~/.gemfile (or such) if it exists.
Something like:
eval(IO.read('~/.gemfile'), binding) if FileTest.exists?("~/.gemfile")

User-level bundler Gemfile

I'd love to have a Gemfile in Bundler that just sets my own personal Gemfiles to always be bult into bundles...
aka ruby-debug, interactive-editor, and so forth.
Any idea how to do this?
We use this technique.
Puth this in your Gemfile:
eval File.read(File.expand_path("Gemfile.personal")) if File.exists?(File.expand_path("Gemfile.personal"))
And then add your personal gems to Gemfile.personal. Of course exclude Gemfile.personal from your version control.
One way to do this is to create different evnironments
group :scott do
end
Then
bundle --with-env=scott
I'm not 100% sure what it is you are trying to achieve, but;
If you just want to specify a number of development-only gems, you can specify a development group that can be excluded from deployments:
group :development do
gem "ruby-debug"
gem "interactive-editor"
end
Then on production or test you would do:
bundle install --without development
The cleanest solution I found so far is to use a separate Gemfile.personal and use a custom Gemfile path. I like this solution because you can use it in any project without modifying project code at all.
1. Add Gemfile.personal in to project root dir
# /path/to/your_ruby_project/Gemfile.personal
eval File.read('Gemfile') # use all gems from Gemfile
gem 'personal-gem1'
gem 'personal-gem2'
2. Install gems using Gemfile.personal file
BUNDLE_GEMFILE="Gemfile.personal" bundle install
# or
bundle install --gemfile=Gemfile.personal
Just remember to specify BUNDLE_GEMFILE every time you execute commands with bundler.
I personally put BUNDLE_GEMFILE=Gemfile.personal env variable in .env file using dotenv which ensures that Gemfile.personal is always used when I execute any command with bundler so I do not need to put it manually every time.
3. Put Gemfile.personal and Gemfile.personal.lock to .gitignore
For linux users:
touch ~/.gitignore
echo "Gemfile.personal\nGemfile.personal.lock" >> ~/.gitignore
This will affect all projects, so you do not need to update each project .gitignore separately.
My proposition does not depend on Bundler. As such does not clutter Gemfile* with your private gems for the price being a bit less convenient than answer by #ScottSchulthess.
How Bundler works
There is an array stored in $LOAD_PATH global variable which is a "load path for scripts and binary modules by load or require" (see Ruby docs) and Bundler modifies this array.
If you're developing a gem, $LOAD_PATH it will contain paths to all gems in the system. You can simply write e.g. require "pry" somewhere and pry gem will be loaded properly even if it's not mentioned in gemspec nor Gemfile. You don't have to add it to dependencies. (Of course it has to be already installed with gem install pry.)
A very different strategy Bundler takes when you're developing an application. In such case most of $LOAD_PATH will be removed on require bundler/setup (Rails calls it in config/boot.rb). Only essential paths and those pointing to gems specified in Gemfile.lock will remain there. So if you want to use pry without adding it to Gemfile, you got to append it to $LOAD_PATH before requiring it.
Solution for applications
gems_root = $LOAD_PATH.detect{ |p| %r{/bundler-} =~ p}.sub(%r{/bundler-.*}, "")
additional_gems = {
"pry" => "pry-0.10.1/lib",
"pry-rails" => "pry-rails-0.3.2/lib",
}
load_paths = additional_gems.values.map{ |p| File.join gems_root, p }
$LOAD_PATH.unshift *load_paths
additional_gems.keys.each{ |r| require r }
If you're using Rails, then save it in /config/initializers/00_custom_gems.rb and that's all. Outside Rails you additionally need to require it, preferably right after require "bundler/setup":
require "path/to/it" if File.exists? "path/to/it"
Remember to mention this file in .gitignore.
Sometimes proper gem path does not end with /lib but with architecture name. The easiest way to learn it is to add it for a moment to Gemfile and do puts $LOAD_PATH in aforementioned initializer. You can also learn those dirs from gemspec.
Solution for gems
When developing gem, you don't need to enhance $LOAD_PATH, only to require gems you want. If you need custom gems in tests and you're using RSpec, it can be done somewhere in /spec/support.
Another (less sane) idea is to add a file lib/development.rb:
require_relative "my_gem_name"
require "path/to/private/requires" if File.exists? "path/to/private/requires"
and refer to this file instead of to "my_gem_name" in your tests, demo application etc..

Resources