Is it possible to override gemfile for local development? - ruby

We have a Gemfile currently in our git repository. However, there's a gem I use only locally in my environment (my team doesn't use it). In order to use it, I have to add it to our Gemfile, but every time I check out to our master/dev main branch, I have to remove it because of conflicts with the tracked gemfile.
What I would like is something like a Gemfile.local which would inherit the gems imported from the Gemfile but to also allow new gems to be imported there to use on my machine only. This file would be ignored in .gitignore. Is this even possible?

Set BUNDLE_GEMFILE environment variable:
BUNDLE_GEMFILE=Gemfile.local bundle exec rails c
To provide a “delta” only in the Gemfile.local put require_relative 'Gemfile' on top of it (or Bundler::Dsl#eval_gemfile as suggested by #MichaelKohl in comments.)

Put below code at the top of your Gemfile.local to load existing gemfile:
if File.exists?('Gemfile') then
eval File.read('Gemfile')
end
It will load all gems from existing Gemfile. You can add new gems also as you need.
Run below commands to install gems from new Gemfile.local:
bundle install --gemfile=Gemfile.local

Related

Bundler config to either look for gems in custom path or download from custom source

How do I configure bundler so that when I run bundle install it looks for gems under /my/custom/path first and if it doesn't find there then try to fetch them from a ruby gem remote repository hosted under https://a.nice.host and downloads those into ./local/relative/path (relative to cwd for example). I would like to avoid the bundler looking at default gem installation system path or rubygems.org
The syntax for sourcing a gem from a local folder is:
gem 'some-gem-name', path: '/my/custom/path'
And the syntax for specifying a custom source is:
gem 'another-gem-name', source: 'https://a.nice.host'
And to install gems into a specific local folder, you can run:
bundle install --path ./local/relative/path
Now, that's probably all the tools you need, in truth... (And in fact, especially for that last requirement, you may instead wish to look into rvm gemsets, or using bundle install --deployment.)
But you did also ask about "looking in a local folder first, and only falling back to a remote source if it doesn't exist". That's quite an odd requirement (usually you'd only want to explicitly opt-in to fetching gems from a local path?!), but to answer this question as you've asked it...
A Gemfile is literally just ruby code! So you can define this logic using... You guessed it, ruby! For example:
if File.exists?('/my/custom/path')
gem 'some-gem-name', path: '/my/custom/path'
else
gem 'some-gem-name', source: 'https://a.nice.host'
end
If this (unusual) pattern needs to be repeated in multiple places, you could wrap it into some helper method.
For more information on the configuration options of bundler, please see the documentation.

Ruby gem equivalent of "pip install -e"?

In Python I can install a package from source in "editable" mode using pip install -e. Then I can carry on editing the code, and any changes will be automatically picked by other Python scripts that import library
Is there a comparable workflow for developing Ruby gems? What is the "Ruby way" of using libs as they are being developed rather than, for example, compiling and installing a gem every time I make a change to the source?
There are two common approaches one might use with bundler:
one executes bundle install --path vendor/bundle and does not run bundle update unless everything is tested.
one tells a bundler to use a local version of the gem:
in Gemfile (this is not supported in mymaingem.gemspec due to rubygems maintainence issues): gem 'mycutegem', :git => 'git://github.com/myname/mycutegem', :branch => 'master';
in command line: bundle config local.mycutegem /path_to_local_git/mycutegem.
The first approach will download everything into subfolder of your current project (here it’d be vendor/bundle.) Feel free to modify everything there, it’ll be reflected.
The second approach is likely better. You are to clone the gem from github and instruct bundle to use your local clone of the respective git repository. This approach provides you with an ability to publish the changes to your main gem into the repository. As soon as dependent repo is published as well, the up-to-date version will be retrieven by your gem subscribers, assuming they have not instructed their bundlers to use their locals.
Hope this helps.
Let's assume you have your gem code residing in a folder (say my_project/mygem/lib). You have some Ruby code in my_project that you want using the mygem code.
What I'd do is add mygem/lib to the $LOAD_PATH global variable in the beginning of said files. Kinda like this:
$LOAD_PATH << File.expand_path('lib', './mygem') # Resolve global paths
require 'a_file' # Would require "mygem/lib/a_file.rb"
I am not sure if this is exactly what you want to achieve, but from the description I infer that you want to have a local copy of some gem and reference that gem in your project.
If this is the case, you can (usually) achieve it in two steps:
Clone the gem from VCS (in most cases: git), e.g.
git clone url-of-the-gem-repo
Reference the local copy using Bundler :path, e.g.
gem "some-gem", :path => "/path/to/local/copy"
If the gem is stored at github, an even better way is to first fork it at github and then clone your own copy. Then, if you provide any improvements to the code in the local repo, you can easily share it with the world using a pull request.
I realize this is a 5 year old question, but I found all of these answers unsatisfying. Since I use ruby to develop CLI tools, using bundler for testing is not ideal. I need to be able to execute my test commands anywhere, and get the actual equivalent of pip install --editable.
Here's my solution.
Use RVM to setup a new gemset for development
rvm gemset use tools-dev --create
Install your gems
gem install mygem.gem
Locate the installation directory
gem list tool -d
In the output, it will say installed at ${rvm_gemset_path}. Copy that path. It will also be an environment variable GEM_HOME, but I include this step for completeness and clarity.
Delete the installed copy
rm -Rf ${rvm_gemset_path}/gems/mygem-${version}
Create a symlink to the git repository working directory.
ln -s ${PWD} ${rvm_gemset_path}/gems/mygem-${version}
This bash script will do it in a more automated fashion assuming that you are in the git directory and you've set GEM_NAME as your gem's name.
rvm gemset use --create ${GEM_NAME}-dev
gem build ${GEM_NAME}.gemspec
gem install ${GEM_NAME}*.gem
gem_install_path=$(ruby -e "puts Gem::Specification.find_by_name('${GEM_NAME}').full_gem_path")
mv $gem_install_path "${gem_install_path}.bak"
ln -s $PWD $gem_install_path

When developing a gem, do I have to keep installing after I make updates?

I am making a gem in the folder:
/Users/me/projects/ruby/gems/mygamename/
And its layout is like:
/Users/me/projects/ruby/gems/mygamename/mygamename.gemspec
/Users/me/projects/ruby/gems/mygamename/mygemname.rb
/Users/me/projects/ruby/gems/mygamename/lib/mygemname/file1.rb
/Users/me/projects/ruby/gems/mygamename/lib/mygemname/file2.rb
Now in my other ruby project, I want to reference this gem in my Gemfile so I did:
gem 'mygemname', :path => "/Users/me/projects/ruby/gems/mygamename"
I know I should be writing tests for my gem to test for functionaly, but I was curious if I could also do integration tests from my other ruby project (where I am referencing it in my Gemfile).
Do I have to re-run bundle if I make updates to the gem? Or does it keep reading from that folder and it will pickup the changes?
Any other advise on how I can test it from my other ruby project?
Again I will be writing tests in the gem itself, but wondering how I can do I this way also in case I want to.
You will have to run bundle install only once. Bundler does not cache or package your gem, it points to your folder directly.
Restarting rules are the same as in the main app. If you make changes to views, assets or autoloaded ruby files, they will be reloaded automatically. However, you will have to restart your app if you required some lib files in the app and changed them. By default Bundler will require "lib/mygemname.rb" of gem "mygemname", so this file will not be reloadable.
Gem classes/modules are cached so every time you make a change to your gemfile, you will have to restart your app.
Also, if you dont update gem version in your gemspec than you dont have to re-bundle. if you update gem version, than you might need to execute bundle update gemname
What I typically do when developing a gem is create Gemfile in a directory and work from there. In the Gemfile, I add a reference to my gem using the :path option.
gem 'mygemname', :path => '/path/to/my/gem'
Then I run bundle install. This way I don't have to keep reinstalling my gem.
Keep in mind that you need to use bundle exec when running my gem.

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