class Gem::Version

The Version class processes string versions into comparable values. A version string should normally be a series of numbers separated by periods. Each part (digits separated by periods) is considered its own number, and these are used for sorting. So for instance, 3.10 sorts higher than 3.2 because ten is greater than two.

If any part contains letters (currently only a-z are supported) then that version is considered prerelease. Versions with a prerelease part in the Nth part sort less than versions with N-1 parts. Prerelease parts are sorted alphabetically using the normal Ruby string sorting rules. If a prerelease part contains both letters and numbers, it will be broken into multiple parts to provide expected sort behavior (1.0.a10 becomes 1.0.a.10, and is greater than 1.0.a9).

Prereleases sort between real releases (newest to oldest):

  1. 1.0

  2. 1.0.b1

  3. 1.0.a.2

  4. 0.9

If you want to specify a version restriction that includes both prereleases and regular releases of 1.x or later versions:

s.add_dependency 'example', '>= 1.0.0.a'

How Software Changes

Libraries generally change in 3 ways:

  1. The change is an implementation detail, bug fix, security fix, or optimization, and has no behavioral effect on the software using it.

  2. The change adds new features, and software using those new features is not compatible with previous versions of the library, but software using previous versions of the library is compatible with the change.

  3. The change modifies the public interface of some part of the library in such a way that software that uses that part of the library must be modified to work.

Optimistic Vs. Pessimistic Dependency Versioning

Users expect to be able to specify a version constraint that gives them a reasonable expectation that new versions of a library will work with their software if the version constraint is true, and not work with their software if the version constraint is false. In other words, the perfect system will accept all compatible versions of the library and reject all incompatible versions. Unfortunately, there is no perfect system, as you cannot predict the future. You can never know whether a future version of a library will contain which type of change.

There are two common outlooks on dependency versioning:

  1. Optimistic. This does not set an upper bound on a dependency. It is possible that a future version of a dependency will break the software, and in that case, the dependency version will need to be updated and changes will need to be made.

  2. Pessimistic. This assumes all major version changes of a dependency will break the software, and that patch or minor changes of a dependency will not break the software. If there is a major version of a dependency released, the dependency version must be updated in order to use it, even if no code changes are actually needed.

In general, optimistic versioning is superior to pessimistic versioning. Pessimistic versioning is often wrong in both directions. Dependencies can release patch or minor versions that contain incompatibilities. One common reason is that a security fix may require a backwards-incompatible API change. In this case, even though pessimistic versioning was used, it didn’t even save effort, as you still need to make code changes and adjust dependency versions. Similarly, for all but the smallest dependencies, just because the dependency made a backwards incompatible change to one interface doesn’t mean the dependency made a backwards incompatible change to an interface that the software is using. It is a common problem that a dependency will release a new major version and the software does not require any changes in order to use it. In this case, being pessimistic results in additional work for no benefit.

When a library uses pessimistic versioning of dependencies, it causes significant problems if that library is not diligent about updating dependency versions and any library is depending on that library. For example:

This type of situation brought on by pessimistic versioning is unfortunately both common and serious in practice.

This is not to say that optimistic versioning never causes a problem. However, with optimistic versioning, if there is a problem, it can be solved with the addition of a single dependency. For example, continuing the previous example:

Both optimistic versioning and pessimistic versioning have problems in certain cases. However, it’s significantly easier to fix optimistic versioning problems than to fix pessimistic versioning problems.

That is not to say that pessimistic versioning is never appropriate. If the dependency is a library that adds a single method, where any change resulting in a major version bump would probably break a library using it, then using pessimistic versioning may be warranted. Additionally, if a dependency has already announced or committed backwards incompatible changes that would break a library’s use of it, then having that library use a pessimistic version constraint would likely be warranted. However, outside of specific situations, you should avoid using pessimistic versioning, as the costs typically exceed the benefits.