Polariscope is a Ruby gem to evaluate the overall health of your Ruby application by analyzing its dependencies. It calculates a health score based on which dependencies are outdated and vulnerable to security issues.
Keeping dependencies up-to-date is crucial for maintaining application security, performance, and compatibility. This gem provides a quick and easy way to gauge the state of your project's dependencies and take measures to improve its health (more on this in the Motivation section).
Think of this gem as a way to score outputs of bundle outdated
and bundle-audit check
.
Add it to your Gemfile:
$ bundle add polariscope
or install standalone:
$ gem install polariscope
Polariscope can be used on the CLI and in code.
Position yourself at the root of your Ruby application and run:
$ [bundle exec] polariscope scan
=> 87.4
The command will read the contents of Gemfile
, Gemfile.lock
and .bundler-audit.yml
(optional, to ignore advisories) in the current directory and output the calculated health score.
health_score = Polariscope.scan
Without arguments, it will do the same as above. Optionally, you can override various parameters:
Polariscope.scan(
gemfile_content: '', # e.g. File.read('Gemfile')
gemfile_lock_content: '', # e.g. File.read('Gemfile.lock')
bundler_audit_config_content: '', # e.g. File.read('.bundler-audit.yml')
spec_type: :latest, # see https://docs.ruby-lang.org/en/master/Gem/SpecFetcher.html#method-i-available_specs
dependency_priorities: { ruby: 5.0, devise: 10.0 }, # hash of dependency priorities
group_priorities: { default: 5.0, test: 2.0 }, # hash of bundler group priorities
default_dependency_priority: 2.0,
advisory_severity: 1.09, # number >= 1
advisory_penalties: { medium: 2.0, critical: 5.0 }, # hash of advisory penalties by criticality
fallback_advisory_penalty: 2.0, # used if value not found in previous hash
major_version_penalty: 0.5, # number in range [0, 1]
new_versions_severity: 1.09, # number >= 1
segment_severities: [1.7, 1.15, 1.01], # ordered by segments: [major, minor, patch]
fallback_segment_severity: 1.01, # in case dependency versions have more segments than in segment_severities
update_audit_database: false, # Polariscope by default updates audit DB if it's older more than one day
)
For details on what these parameters mean, consult this section.
Get the released or latest version of gems with:
# released versions
gem_specs = Polariscope.gem_versions(['devise', 'pundit'])
gem_specs.versions_for('devise')
# => returns potentially many versions
# latest version
gem_specs = Polariscope.gem_versions(['devise', 'punt'], spec_type: :latest)
gem_specs.versions_for('pundit')
# => returns only the latest version
Health score is calculated with a formula that takes the contents of Gemfile
and Gemfile.lock
and produces a decimal number in the
By design, health score is most useful as a relative measure of application health: if your health score suddenly drops one day from 100 to 90, it signals a serious issue (e.g. a new vulnerability in your Ruby version). If it drops from 100 to 95, it may signal that a new minor version of Rails has been released, for example. If it drops from 100 to 99.5, it may mean a gem like Pundit has a new patch version with a bug fix.
How much the score changes depends on various factors:
- dependency priority (by default, Ruby and Rails have a higher priority than other dependencies)
- bundler group priority (by default,
:default
and:production
groups have a higher priority) - number of versions between the current and the latest version of a dependency
- the kind of outdatedness according to SemVer; if there's a new major version, that will cause a sharper drop in the score than a new minor version
- the number of active security advisories
- advisory severity (e.g. a High severity advisory will cause a sharper drop in score than one that is Low)
This is the complete formula (it's simpler than it may seem):
It's comprised of several scores in the
Note that, by design, health score can never be higher than the lowest of its scores. For example, if your major versions score is 0.75, then health score can never be higher than 75, regardless of other scores being 1.
Score that signals how many dependencies have outdated major versions (it doesn't care about minor or patch versions). Score 1 means no dependency has an outdated major while score 0 means all have an outdated major. Other combinations fall in between those extremes.
The formula Gemfile
and not dependencies of dependencies present in Gemfile.lock
). The penalty controls how much the score drops when the major of a dependency is outdated, and the priority proportions that penalty in relation to other dependencies.
Dependency priority (weight)
Major version penalty
Score that represents how outdated direct dependencies are based on the number of new versions and the kind of outdatedness. Score 1 means all dependencies are up-to-date. As dependencies get outdated, it starts to lower. Unlike major versions score, this score can never reach 0, it only gravitates towards it.
The formula
Dependency health score
Both subscores use a version of the power function. See this section for more details on its interpretation.
Score in the
Segment penalty v1.0.0
, but v1.1.0
, v2.0.0
and v3.0.0
have been released in the meantime. The first outdated segment is major (minor is also outdated, but it comes after major, so it's not the first).
Segment severity [major, minor, patch]
). For example, if major is outdated, first value in the list is used.
Score in the
Penalty
Score in the Gemfile.lock
). Score 1 means no dependency has an active advisory, and it drops with each new advisory.
The formula
Advisory penalty
Function used for several scores is of type
See this graph for various values
$f(0)=1$ $f(x+1) \lt f(x)$ $\lim_{x \to \infty} f(x)=0$ - bigger
$S$ -> more severe "drop"
The function returns values in range
This can be used as a simple but an okay way to model certain scores. For scoring purposes we will refer to
Who is this tool for? What does it accomplish?
Agencies like Infinum are at any point in time working on multiple projects, e.g. multiple Ruby applications. Without a monitoring process, it would be necessary to manually check each project for security vulnerabilities and new dependency versions (e.g. a new major version with a breaking change). With scale, this becomes time-consuming.
Health score is a way to monitor these things. Instead of manually checking each project for outdated dependencies (output of bundle outdated
) and security advisories (output of bundle-audit check
), health score informs you whether those outputs require immediate action.
As was said above, health score is most useful as a relative measure. It starts at value 100 and it drops as new versions/security issues arise. Your project might have a score of 99 one day, but suddenly drop to 90 the next — this signals something significant happened, probably a security advisory in an important dependency like Rails, or a new major version of Ruby. On the other hand, if it drops from 99 to 97, it could mean some dependency has a new minor version.
It's up to you to decide when to take action: either when the score drops suddenly (to fix immediate issues) or when it drops below a certain threshold (to update multiple dependencies in one go).
At Infinum, Polariscope is used as part of a monitoring tool that (among other things) calculates health scores for all Ruby projects daily. Part of the project table looks like this:
The health score is also shown as a badge on the repository README:
After checking out the repo, run bin/setup
to install dependencies. Then, run rake spec
to run the tests. You can also run bin/console
for an interactive prompt that will allow you to experiment.
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/infinum/polariscope.
The gem is available as open source under the terms of the MIT License.