Sharing in development Ruby Gems - ruby

We use Rails for the majority of our web projects and in a lot of cases we share code we have as dependancies such as our API as Gems. We also have some projects which are similar in nature enough that we've built Engines and then have host applications that use these.
These are all hosted on our own servers and we share the Gems with the projects using Gem In A Box (this is because we can't upload the Gems to anywhere else due to security restrictions). During development we comment out the lines to reference either a specific version on our server or our local version if we are developing within the Gem itself.
So for example:
# version on the server
gem 'our_gem', '1.1.0', source: 'https://gems.domain.net'
# local version
gem 'our_gem', path: '../our_gem'
What happens is that when we do a build on our CI server we then have a script that reads through the Gemfile and finds any gems that have our server as the source and then vendorize them and reference them as the vendored path.
For example:
# this
gem 'our_gem', '1.1.0', source: 'https://gems.domain.net'
# becomes this
gem 'our_gem', path: 'vendor/gems/our_gem-1.1.0'
However the problem we have encountered is that when we are in development and we may have several features or bugs we want to push to our QA team to start testing it means we have to bump the version each time so we can reference it in our Gemfile's as a dependancy. We can't reference the branch due to the Git repository not accessible to our build setup and some other restrictions.
How do teams usually handle in-development dependancies that don't have a version yet? We've thought about having a development version of Gem In A Box and then not passing a version to it in our Gemfile...
# shared dev version of gem on our dev gem server
gem 'our_gem', source: 'https://dev.gems.domain.net'
But the problem here is that we still need to up the version every time in order to push to the Gem Server. Is there a standard way to push a package to a Package Manager whereby the version is always the same (or better yet is version-less but hosted by the manager)?
We also have the same issue with NPM (we run a local version of Vedaccio).

Related

Use local version of module from Github instead of installed

I am working on a project written in Ruby, which depends on another module using require modulename. I can change the required module to add the features I need, but I don't want to modify my installed version. I've downloaded the module from git, but I don't know how to point my project to this local version.
Assuming the required module is a gem, you can use the path: option in Gemfile to define a local version of a gem. So in Gemfile:
gem 'modulename', path: 'relative/path/to/modulename'
However, remember to remove the path: option before committing any changes to your git repository as you don't want that going into production.

Using Gemfiles with Budler with two projects

TL;TR
We have two projects (minitest project, and a page objects project). The Minitest project uses the page object project. When Jenkins runs the tests, we use a remote git path to the page objects project. When we run it locally we change the gem file in the minitest project to use a local path\or local git repo. This results in us needing to continuously change the gem file. We don't wan't to have to do this. How can we set this up so that we don't need to keep changing the gem file?
The long version
I have two Ruby projects, which are tightly coupled. Both projects are used to support testing with Capybara. One project contains Minitest tests. The other project contains page objects. The minitest project uses the page object project. The page object project is published as a gem so that other teams can use it. Quite often we need to edit both projects at once.
I am using Bundler to manage the gems. These projects used to be in two different git repos.
The tests run in Jenkins, and when we work on them we run them locally. When the tests run in Jenkins the minitest project can reference the page objects project either as a published gem or as a remote git repo.
The problem we had with this is that we kept needing to change the gem file of the minitest project. We would change it from pointing to a remote git repo, to a checked out (local) path. Or we would point to a local git repo. In this case we would still need to update the minitest project's gem file with the local branch name. We needed to edit the gem file with either approach, then we had to remember to change this back before checking in and merging.
We then moved both projects into a single repo. Now the gem file in the minitest project only uses the relative path e.g. path => .... This means that we don't need to constantly update the gem file in the minitest project. However, now when we run bundle install or bundle update, only the gem file from the minitest project is used (at least this is what I assume is happening).
How do I use a different gem path\git repo on my dev machine to what is used in Jenkins, without continuously changing the gem file?
I would like to know what approach should I be using in this case? Can avoid making changes to the minitest project's gem file?
Consider using bundler's groups (in Gemfile):
group :project1 do
gem 'rspec'
end
group :project2 do
gem 'minitest'
end
Then specify which group to use in application.rb, for example
Bundler.require(:project1) # or :project2 depending on the project
More info here: http://bundler.io/groups.html

how do I repackage a ruby gem with native extensions

I need to install a number of ruby gems (all with C extensions) on a production server which does not have any dev tools installed. I'd like to build gems on a dev server first and then repackage and install resulting native gems on production server.
However, there seems to be no standard methods to package gem with native extensions for redistribution. I am aware of rake-compiler, but none of the gems in concern works with it out of the box. Specifically, I am working with json-1.7.5, rb-inotify-0.8.8 and ffi-1.2.1 gems.
Any pointers on how to do this task or documentations on the subject are appreciated.
Using Jordan Sissel's fpm you can take various input archives (including gems) and compile and package them as (among others) DEBs or RPMs.
An example to compile the json gem into a deb package follows:
cd /tmp
fpm -s gem -t deb json
This will download the latest version of the json gem and create a rubygem-json-1.5.7-1.amd64.deb archive in /tmp which you can install on your server. Note that the compile box and the final server need to be rather identical. At least the distribution and bitness, the ruby version and its file layout, and the available loadable libraries should be the same. Basically all the constraints your upstream distribution deals with...
That said, in the long term I found it much easier to just install a compiler on the target servers and use rbenv or rvm on the server. For most small and mid-size installations, it's much easier to handle as you don't need to pre-compile and ship everything to your servers.
Hi You could do it with: gem-compiler
You need to tell RubyGems the filename of the gem you want to compile:
$ gem compile yajl-ruby-1.1.0.gem
The above command will unpack, compile any existing extensions found and repackage everything as a binary gem:
Unpacking gem: 'yajl-ruby-1.1.0' in temporary directory...
Building native extensions. This could take a while...
Successfully built RubyGem
Name: yajl-ruby
Version: 1.1.0
File: yajl-ruby-1.1.0-x86-mingw32.gem
It have well written documentations here:
https://github.com/luislavena/gem-compiler.
I am using it as well as we have own gem server. Just have to be careful with distribution because some gems compiled on wheezy won't work on jessie and so on.
You're going to have to build them on a system that's almost exactly the same for this to work. If you're linking against shared libraries that are in a different location or have slightly different versions it may not work at all. Sometimes you have some slack, where it will work with a range of versions, but this cannot be assured.
There's no way to package with native extensions for this very reason, there's just too many possible combinations of libraries.
You'll also need to make sure you're using the same architecture, including 32-bit or 64-bit as required.
Sometimes you'll get lucky and there's a package for your OS that can install these for you, but these won't work with rvm or rbenv.

Should Gemfile.lock be included in .gitignore?

I'm sort of new to bundler and the files it generates. I have a copy of a git repo from GitHub that is being contributed to by many people so I was surprised to find that bundler created a file that didn't exist in the repo and wasn't in the .gitignore list.
Since I have forked it, I know adding it to the repo won't break anything for the main repo, but if I do a pull request, will it cause a problem?
Should Gemfile.lock be included in the repository?
Update for 2022 from TrinitronX
Fast-forward to 2021 and now Bundler docs [web archive] now say to commit the Gemfile.lock inside a gem... ¯_(ツ)_/¯ I guess it makes sense for developers and ease of use when starting on a project. However, now CI jobs need to be sure to remove any stray Gemfile.lock files to test against other versions.
Legacy answer ~2010
Assuming you're not writing a rubygem, Gemfile.lock should be in your repository. It's used as a snapshot of all your required gems and their dependencies. This way bundler doesn't have to recalculate all the gem dependencies each time you deploy, etc.
From cowboycoded's comment below:
If you are working on a gem, then DO NOT check in your Gemfile.lock. If you are working on a Rails app, then DO check in your Gemfile.lock.
Here's a nice article explaining what the lock file is.
The real problem happens when you are working on an open-source Rails app that needs to have a configurable database adapter. I'm developing the Rails 3 branch of Fat Free CRM.
My preference is postgres, but we want the default database to be mysql2.
In this case, Gemfile.lock still needs be checked in with the default set of gems, but I need to ignore changes that I have made to it on my machine. To accomplish this, I run:
git update-index --assume-unchanged Gemfile.lock
and to reverse:
git update-index --no-assume-unchanged Gemfile.lock
It is also useful to include something like the following code in your Gemfile. This loads the appropriate database adapter gem, based on your database.yml.
# Loads the database adapter gem based on config/database.yml (Default: mysql2)
# -----------------------------------------------------------------------------
db_gems = {"mysql2" => ["mysql2", ">= 0.2.6"],
"postgresql" => ["pg", ">= 0.9.0"],
"sqlite3" => ["sqlite3"]}
adapter = if File.exists?(db_config = File.join(File.dirname(__FILE__),"config","database.yml"))
db = YAML.load_file(db_config)
# Fetch the first configured adapter from config/database.yml
(db["production"] || db["development"] || db["test"])["adapter"]
else
"mysql2"
end
gem *db_gems[adapter]
# -----------------------------------------------------------------------------
I can't say if this is an established best practice or not, but it works well for me.
My workmates and I have different Gemfile.lock, because we use different platforms, windows and mac, and our server is linux.
We decide to remove Gemfile.lock in repo and create Gemfile.lock.server in git repo, just like database.yml. Then before deploy it on server, we copy Gemfile.lock.server to Gemfile.lock on server using cap deploy hook
Agreeing with r-dub, keep it in source control, but to me, the real benefit is this:
collaboration in identical environments (disregarding the windohs and linux/mac stuff). Before Gemfile.lock, the next dude to install the project might see all kinds of confusing errors, blaming himself, but he was just that lucky guy getting the next version of super gem, breaking existing dependencies.
Worse, this happened on the servers, getting untested version unless being disciplined and install exact version. Gemfile.lock makes this explicit, and it will explicitly tell you that your versions are different.
Note: remember to group stuff, as :development and :test
Simple answer in the year 2021:
Gemfile.lock should be in the version control also for Rubygems. The accepted answer is now 11 years old.
Some reasoning here (cherry-picked from comments):
#josevalim https://github.com/heartcombo/devise/pull/3147#issuecomment-52193788
The Gemfile.lock should stay in the repository because contributors and developers should be able to fork the project and run it using versions that are guaranteed to work.
#rafaelfranca https://github.com/rails/rails/pull/18951#issuecomment-74888396
I don't think it is a good idea to ignore the lock file even for plugins.
This mean that a "git clone; bundle; rake test" sequence is not guarantee to be passing because one of yours dozens of dependencies were upgraded and made your code break. Also, as #chancancode said, it make a lot harder to bisect.
Also Rails has Gemfile.lock in git:
https://github.com/rails/rails/commit/0ad6d27643057f2eccfe8351409a75a6d1bbb9d0
The Bundler docs address this question as well:
ORIGINAL: http://gembundler.com/v1.3/rationale.html
EDIT: http://web.archive.org/web/20160309170442/http://bundler.io/v1.3/rationale.html
See the section called "Checking Your Code into Version Control":
After developing your application for a while, check in the
application together with the Gemfile and Gemfile.lock snapshot. Now,
your repository has a record of the exact versions of all of the gems
that you used the last time you know for sure that the application
worked. Keep in mind that while your Gemfile lists only three gems
(with varying degrees of version strictness), your application depends
on dozens of gems, once you take into consideration all of the
implicit requirements of the gems you depend on.
This is important: the Gemfile.lock makes your application a single
package of both your own code and the third-party code it ran the last
time you know for sure that everything worked. Specifying exact
versions of the third-party code you depend on in your Gemfile would
not provide the same guarantee, because gems usually declare a range
of versions for their dependencies.
The next time you run bundle install on the same machine, bundler will
see that it already has all of the dependencies you need, and skip the
installation process.
Do not check in the .bundle directory, or any of the files inside it.
Those files are specific to each particular machine, and are used to
persist installation options between runs of the bundle install
command.
If you have run bundle pack, the gems (although not the git gems)
required by your bundle will be downloaded into vendor/cache. Bundler
can run without connecting to the internet (or the RubyGems server) if
all the gems you need are present in that folder and checked in to
your source control. This is an optional step, and not recommended,
due to the increase in size of your source control repository.
No Gemfile.lock means:
new contributors cannot run tests because weird things fail, so they won't contribute or get failing PRs ... bad first experience.
you cannot go back to a x year old project and fix a bug without having to update/rewrite the project if you lost your local Gemfile.lock
-> Always check in Gemfile.lock, make travis delete it if you want to be extra thorough https://grosser.it/2015/08/14/check-in-your-gemfile-lock/
A little late to the party, but answers still took me time and foreign reads to understand this problem. So I want to summarize what I have find out about the Gemfile.lock.
When you are building a Rails App, you are using certain versions of gems in your local machine. If you want to avoid errors in the production mode and other branches, you have to use that one Gemfile.lock file everywhere and tell bundler to bundle for rebuilding gems every time it changes.
If Gemfile.lock has changed on your production machine and Git doesn't let you git pull, you should write git reset --hard to avoid that file change and write git pull again.
The other answers here are correct: Yes, your Ruby app (not your Ruby gem) should include Gemfile.lock in the repo. To expand on why it should do this, read on:
I was under the mistaken notion that each env (development, test, staging, prod...) each did a bundle install to build their own Gemfile.lock. My assumption was based on the fact that Gemfile.lock does not contain any grouping data, such as :test, :prod, etc. This assumption was wrong, as I found out in a painful local problem.
Upon closer investigation, I was confused why my Jenkins build showed fetching a particular gem (ffaker, FWIW) successfully, but when the app loaded and required ffaker, it said file not found. WTF?
A little more investigation and experimenting showed what the two files do:
First it uses Gemfile.lock to go fetch all the gems, even those that won't be used in this particular env. Then it uses Gemfile to choose which of those fetched gems to actually use in this env.
So, even though it fetched the gem in the first step based on Gemfile.lock, it did NOT include in my :test environment, based on the groups in Gemfile.
The fix (in my case) was to move gem 'ffaker' from the :development group to the main group, so all env's could use it. (Or, add it only to :development, :test, as appropriate)

How to develop a gem in staging environment?

I am trying to hack through a forked gem (buildr). As such I cloned it from github and began to butcher the code. The official gem is installed on my system (under /usr/lib/ruby.../gems/buildr...). There is an executable which I need to use in my dev process - buildr.
Now I want the buildr executable and the library to point to my forked repo and not the default gem installation. This would be for this gem only. As such, the changes I make against the forked repo is usable directly for testing and so forth.
I would guess I need to load my library prior to the system gem loading. Can somebody recommend the best way to do so?
I did something similar for work when the Spreadsheet gem broke backward compatibility. I put the previous versions code in it's own module and just renamed the gem my-spreadsheet and installed that (I really wanted some of the features of the new gem but I also didn't want to rewrite all my previous code at that point).
If it's just a binary you want to override you could always do some PATH magic, setting the directory of your binary first and thus make sure you always override. But personally I'd prefer making my own copy with a new name and installing that.
you could bump the version in the gemspec for your fork. Then when you install your version of the gem, it will use your (newer) version by default.
change buildr.gemspec
#...
spec.version = '1.3.4.dev'
#...
Then
$ gem build buildr.gemspec
$ sudo gem install buildr-1.3.4.dev.gem
and it should work.

Resources