Manage gem dependencies based on Ruby version - ruby-on-rails

I'm working on a gem that works on Ruby 1.9.3, but installing an up-to-date Gemfile works only on Ruby 2.2. Is there a way of separating dependencies based on Ruby versions?
I've seen this approach in the past:
pry_debugger = RUBY_VERSION < '2.0.0' ? 'pry-debugger' : 'pry-byebug'
spec.add_development_dependency pry_debugger
Or should I just consider supporting one Ruby version (let's say 2.0) and find the supported gems? What's the easiest way of finding what gems work on my local Ruby version?

Logic like you show will work fine for development dependencies since it will be evaluated at bundle install time.
It will not work for runtime dependencies since the logic will be evaluated at gem-build time and not when the gem runs in another environment.
RubyGems does not provide a way for you to specify different gems based on the runtime environment, so the only way you could support that would be to release two differently named versions of your gem with different gemspecs.
Personally, I don't see the point of putting development dependencies in the .gemspec, so I always just add these to my Gemfile and reserve the gemspec for runtime dependencies only.
This separates the concerns better and makes it clear that gem selection logic can be used in the Gemfile, but not in the gemspec.

Related

Running a ruby on rails app locally - Different versions of ruby

I'm using RVM to manage the different ruby versions I have. One particular application is using an older ruby version (2.3.1), and I've noticed that, once I've changed to that version and run rails server on it, it doesn't work because I'm required to change a whole cascade of Gems or other files, such as nokogiri, to make it run.
Generally, from what I've read online, I should just do a simple bundle install to do all of this before running rails server. However, it doesn't work as there are more conflicting things in this file, specifically that the versions are hard coded into it.
Based on this, how can I run this app on my local server, if the above steps I've done, just doesn't work? I'm using Ubuntu, if that helps.
You're dealing with what is known as dependency issues. The point of Gemfile and Gemfile.lock is to insure that there will be no dependency issues for the application and bundle install will handle that. However it is common for versions to be set in the Gemfile to lock to a specific major release version which might allow for minor version updates. This will look something like:
#Gemfile
gem 'rails', '4.2.10'
gem 'pg', '0.20.0'
gem 'after_party', '~> 1.10' #minor version updates will run here
gem 'kaminari', '~> 1.1'
ruby '2.3.6'
This ia a brief example. Now when you run bundle install it will make sure everything is compatible with these versions. While running bundle update will only update the versions with ~> before the version and will upgrade only minor semantic versions as they are not supposed to have breaking changes.
So, why is your app not working? Well the Gemfile should have contained a ruby version. RVM should determine your ruby version in .ruby-version file in base of your rails app and should match the version in Gemfile. If you need to upgrade your ruby version bundler will help insure all your gems are compatible with that version and with each-other. You'll first need to upgrade your ruby version with RVM, then set it in Gemfile.
However, there is no guarantee that out of date gems will be compatible. That's the whole point of locking them so that you know which versions are stable at a give point in time. Updates / upgrades to gems have to be tested for compatibility which can sometimes be a project.
Also see Rails Bundle, gems conflicts, best way to solve it
You can create a .rvmrc file or .ruby-version and .ruby-gemset files for isolating gems for your projects. Here's the official documentation on that - https://rvm.io/workflow/projects#project-file-ruby-version
you can add
echo '2.3.1' > .ruby-version and echo 'newgemset' > .ruby-gemset into working folder
then run
cd ./
rvm install ruby-2.3.1
gem install bundle
bundle install

Does Bundler Gem take in consideration your Ruby environment?

My question is simple one, does gem bundler considers your ruby environment (e.g. 1.8.7 | 1.9.2) before deciding which gem to take based on gem file?
Let's say your gemfile contains
gem 'thor'
gem 'json'
gem 'grit'
When you run bundle install will take versions of the gem that are compatible with your current ruby environment or just latest gems?
It depends! Bundler relies on the configuration of the Gemspecs that each Gem provides.
Gemspecs offer the posibility to provide different or additional dependencies based on the runtime environment. IE you can change the dependencies for JRuby or provide different binaries for i386 architectures.
As far as i know, it's not possible to declare a gem as 1.9 or 1.8 compatible (which would have made sense to me). I think it's partly so, because 1.9 is 99% downward compatible.
You are always forced to have a look at the gems themselves. Because of this, there are sites like http://isitruby19.com/
As you might see, it's not an issue of Bundler, but RubyGems.

Why prawnto gem installs rails 3.x when there is already a rails 2.x?

I have rails (2.3.5) and prawn (0.12.0) installed. When I install prawnto, gem installs rails version 3.2.6 also.
The dependency of prawnto is:
prawn >= 0
rails >= 2.1
Why gem install Rails 3.x when the prawnto dependency is already there?
tl;dr Use Bundler. It rocks.
Alright, this is basically down to how dependency resolution works in RubyGems. If you're not terribly familiar with it, get up to speed real quick like with the Primer panel from this XKCD comic. RubyGems dependency management and the Primer storyline are very similar in terms of complexity.
When a gem specifies a dependency of, say rails >= 2.1, when you go to install that gem, RubyGems conveniently ignores all the gems that you've currently got installed and then queries the web API to find the absolute latest version of Rails that's greater than or equal to 2.1.
It will find, as of this writing, version 3.2.6, and so will dutifully install that version of Rails because it fits the dependency requirements. It will also install every single dependency of Rails, and their dependencies, and the sub-sub-sub-sub-dependencies all the way down until there's not a gem left without a dependency installed.
I won't go into exactly how that works because it makes my vision go blurry when I think about it.
Now, if you were using something that's not pure-RubyGems such as Bundler, you'd be able to have a Gemfile like this:
source 'http://rubygems.org'
gem 'rails', '2.3.4'
gem 'prawnto', '0.1.1'
And then run bundle install and then something magical will happen. Bundler will figure out the dependencies for all the gems specified in the Gemfile, as well as the gems that they depend on, and then install only those gems.
This means that if you have prawnto wanting Rails >= 2.1, it won't install 3.2.6 because there's another dependency saying that Rails must precisely be 2.3.4. So therefore Rails 2.3.4 will be installed.
If you have conflicting versions, with a gem A specifying a dependency on gem B of ~> 1.0, but then gem C specifying a dependency that gem B must be '= 0.5.0', Bundler won't be very happy and will raise an error because the dependencies can't be resolved.
I'd really recommend using Bundler for all your Rails projects. Even those that are running on Rails 2. There's a page on the Bundler website which will get you started with a Rails 2.3 project and Bundler.

Beginning with Ruby on Rails. How do Gemfile and how gems work when specifying versions to download?

I tried reading the documentation but this still wasn't clear to me:
Suppose my I have the following statements in my Gemfile:
source 'https://rubygems.org'
gem 'rails', '3.2.2'
# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'
gem 'sqlite3'
gem 'json'
...
I'm trying to learn how to use Ruby on Rails this website: http://ruby.railstutorial.org/ and it says that I need to have the sqlite3 gem for this version of rails (3.2.2).
My question is: if I don't specify the exact version of the sqlite3 gem for the system to download when I run bundle install then will it install the latest version that works with the version of rails and ruby that I'm working with (suppose it's 3.2.2 and 1.8.7 p-352 respectively) or will it just download the latest version of the sqlite3 gem available whether it works with what I have installed or not?
I'm not really sure how this aspect of the Gemfile works and the documentation hasn't been clear or I haven't found where it explicitly says this or otherwise.
So, it will install the latest version of sqlite3 that is compatible with the dependencies specified by the other gems you've listed.
Say, rails said it only worked with sqlite3 that was greater than 1.0 but less than 4.0, it might install 3.9.5. I'm making all these numbers up, just an example.
In fact, I don't know if rails does specify any particular versions of sqlite3 it requires. Presumably 'json' doesn't either, since it's got nothing to do with databases. If none of the other gems you list specify any requirements for sqlite3, it'll just install the latest version available.
'bundle install' is using bundler, some more about how it resolves dependencies is here.
So bundler will download the latest gem that meets the requirements of all the other gems you list. Say you listed a gem A which required sqlite3 earlier than 2.0 and a gem B that required sqlite3 later than 2.1 -- 'bundle install' would complain and say it can't satisfy them all.
This relies on the gems own advertisements of each of their own requirements for the other gems they rely on though. Sometimes they can be wrong, or missing. It may be that rails doesn't actually specify what version of sqlite3 it requires -- since it doesn't actually require sqlite3 at all (you don't have to use sqlite with rails), this may very well be. In which case bundler can't do much but get the latest version of sqlite3. Which doesn't guarantee that version will work. But it can only do so much.
You also mention the issue of compatibility with specific versions of ruby, like 1.8.7-p352. Bundler is less capable there, you can't generally count on it knowing what version is compatible with a particular version of ruby (because there's not a great way for gems to express this in a way that bundler can use, alas).
But in general, things work out. If you don't know or care what version of sqlite3 (or any other gem) you want, but know you want one -- do what you did, run 'bundle install', see if it works. It probably will. If it doesn't, google the error message and you'll probably find someone explaining why it didn't and what you need to do instead.
PS: If that wasn't enough. sqlite3 is sort of an odd case, because while Rails is designed to work with it, it's not required. But Rails by default, when creating a new app with latest version of rails rails new my_app, will already put sqlite3 in your Gemfile for you. And it'll do it just like you did, without a version constraint. That's reason to feel pretty confident it will work fine. Note that in rails 3.2.2, rails new my_app will put some other things in your Gemfile that do have more specific versions listed, like gem 'sass-rails', '~> 3.2.3', because while those things aren't absolutely required for Rails (or else they'd be expressed as requirements and not need to be explicitly in your app's Gemfile), the rails installer knows that only certain versions will work.
Fairly new to Rails as well so correct me if I am wrong.
The Gemfile looks to the gems directory for the gems listed and has no impact on actually "installing" them. Performing a "gem install" command without a version will grab the most recent version listed no matter what your Gemfile says. When you "bundle install", that is when version-specific lookups happen in your gems directory.
Bundler always looks at your Gemfile.lock file first when you're doing bundle install. If you open that file, you'll see the gems as well as the version used by your application. This is convenient for existing projects because you're always sure that every other developer involved with the project will be working with the same gem versions.
If it doesn't find that file (i.e. doing rails new projectname), or the specified gem or version in Gemfile isn't consistent with Gemfile.lock, it will download the latest or specified version of the gem.

relationship bundler and gem

Coming from the java world if
rake == ant
gem == maven #at least the dependency part
then what the heck is bundler?
It says "managing your application's dependencies", but isn't that what gem is doing by fetching them for me?
Gem fetches a library and installs it in one of the predefined places, like vendor gem, system or user gem directory at that point. That installed version will then be used by your program. So if you have two programs requiring two different versions of a gem you may be out of luck, since when you install the more current one the apps that require the older can have problems. You can specify version numbers for the gems, but those will need to be available on the target server(s) going forward.
There is where bundler comes - it manages exact dependencies including gem versions. So when you deploy an application and have specified exact gem versions that are required, bundler takes care of getting and installing those exact versions at that point. Your app will now have the specified versions 'bundled in' so that you it won't break, when, on the target system, gems have different, incompatible versions.
Rake = ant
gem = jar
Bundler = maven

Resources