diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9ee050cd..bd18754e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,92 +1,172 @@ -# Contributing to shoulda-matchers +# Contributing to Shoulda Matchers -We love contributions from the community! Here's a quick guide to making a pull -request. +We've put a lot of work into making improvements to Shoulda Matchers, but we +always welcome changes and improvements from the community! -1. If you haven't contributed before, please read and understand the [Code of - Conduct]. - -1. Ensure that you have a [working Ruby environment]. - -1. Fork the repo on GitHub, then clone it to your machine. - -1. Now that you've cloned the repo, navigate to it and install dependencies by - running: - - bundle install - -1. All tests should be passing, but it's a good idea to run them anyway - before starting any work: - - bundle exec rake - -1. If you're adding functionality or fixing a bug, you'll want to add a - failing test for the issue first. - -1. Now you can implement the feature or bugfix. - -1. Since we only accept pull requests with passing tests, it's a good idea to - run the tests again. Since you're probably working on a single file, you can - run the tests for that file with the following command: - - bundle exec appraisal rspec - - You can find a list of valid Appraisals by running: - - bundle exec rake appraisal:list - - (If you're familiar with Zeus, you can also run unit tests by running `zeus - start` in one shell, and then running the following in another:) - - zeus rspec - - In any case, to run the entire test suite again, say: - - bundle exec rake +If you'd like to propose a change to the gem, whether it's a fix for a problem +you've been running into or an idea for a new feature you think would be useful, +here's how the process works: +1. [Read and understand the Code of Conduct](#code-of-conduct). +1. Fork this repo and clone your fork to somewhere on your machine. +1. [Ensure that you have a working environment](#setting-up-your-environment). +1. Read up on the [architecture of the gem](#architecture), [how to run + tests](#running-tests), and [the code style we use in this + project](#code-style). +1. Cut a new branch and write a failing test for the feature or bugfix you plan + on implementing. +1. [Make sure your branch is well managed as you go + along](#managing-your-branch). +1. [Update the inline documentation if you're making a change to the + API](#documentation). +1. [Refrain from updating the changelog.](#a-word-on-the-changelog) 1. Finally, push to your fork and submit a pull request. -At this point, you're waiting on us. We may suggest some changes to make to your -code to fit within the project style, or discuss alternate ways of addressing -the issue in question. When we're happy with everything, we'll bring your -changes into master. Now you're a contributor! +Although we maintain the gem in our free time, we try to respond within a day or +so. After submitting your PR, we may give you feedback. For instance, we may +suggest some changes to make to your code to fit within the project style or +discuss alternate ways of addressing the issue in question. Assuming we're happy +with everything, we'll bring your changes into master! -## Addendum: Setting up your environment +--- -### Installing Ruby +## Code of Conduct -shoulda-matchers is only compatible with Ruby 2.x. A `.ruby-version` is included -in the repo, so if you're using one of the Ruby version manager tools, then you -should be using (or have been prompted to install) the latest version of Ruby. -If not, you'll want to do that. +If this is your first time contributing, please read the [Code of Conduct]. We +want to create a space in which everyone is allowed to contribute, and we +enforce the policies outline in this document. -[working Ruby environment]: #addendum-setting-up-your-environment [Code of Conduct]: https://thoughtbot.com/open-source-code-of-conduct -[execjs]: https://github.com/sstephenson/execjs -### Linux-specific instructions +## Setting up your environment -#### Debian/Ubuntu +The setup script will install all dependencies necessary for working on the +project: -Run this command to install necessary dependencies: - -``` -sudo apt-get install -y ruby-dev libpq-dev libsqlite3-dev nodejs +```bash +bin/setup ``` -#### RedHat +## Architecture -Run this command to install necessary dependencies: +This project follows the typical structure for a gem: code is located in `lib` +and tests are in `spec`. + +All of the matchers are broken up by the type of example group they apply to: + +* `{lib,spec/unit}/shoulda/matchers/action_controller*` for ActionController + matchers +* `{lib,spec/unit}/shoulda/matchers/active_model*` for ActiveModel matchers +* `{lib,spec/unit}/shoulda/matchers/active_record*` for ActiveRecord matchers +* `{lib,spec/unit}/shoulda/matchers/independent*` for matchers that can be used + in any example group + +There are other files in the project, of course, but there are likely the ones +that you'll be interested in. + +In addition, tests are broken up into two categories: + +* `spec/unit` +* `spec/acceptance` + +A word about the tests, by the way: they're admittedly the most complicated part +of this gem, and there are a few different strategies that we've introduced at +various points in time to set up objects for tests across all specs, some of +which are old and some of which are new. The best approach for writing tests is +probably to copy an existing test in the same file as where you want to add a +new test. + +## Code style + +We follow a derivative of the [unofficial Ruby style guide] created by the +Rubocop developers. You can view our Rubocop configuration [here], but here are +some key differences: + +* Use single quotes for strings. +* When breaking up methods across multiple lines, place the `.` at the end of + the line instead of the beginning. +* Don't use conditional modifiers (i.e. `x if y`); place the beginning and + ending of conditionals on their own lines. +* Use an 80-character line-length except for `describe`, `context`, `it`, and + `specify` lines in tests. +* For arrays, hashes, and method arguments that span multiple lines, place a + trailing comma at the end of the last item. +* Collection methods are spelled `detect`, `inject`, `map`, and `select`. + +[unofficial Ruby style guide]: https://github.com/rubocop-hq/ruby-style-guide +[here]: .rubocop.yml + +## Running tests + +### Unit tests + +Unit tests are the most common kind of tests in the gem. They exercise matcher +code file by file in the context of a real Rails application. This application +is created and loaded every time you run `rspec`. Because of this, it can be +expensive to run individual tests. To save time, the best way to run unit tests +is by using [Zeus]. + +[Zeus]: https://github.com/burke/zeus + +You'll want to start by running `zeus start` in one shell. Then in another +shell, instead of using `bundle exec rspec` to run tests, you'll use `bundle +exec zeus rspec`. So for instance, you might say: ``` -sudo yum install -y ruby-devel postgresql-devel sqlite-devel zlib-devel +bundle exec zeus rspec spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb ``` -Then, install one of the JavaScript runtimes supported by [execjs]. For -instance, to install node.js: +### Acceptance tests + +The acceptance tests exercise matchers in the context of a real Ruby or Rails +application. Unlike unit tests, this application is set up and torn down for +each test. + +Whereas you make use of Zeus to run unit tests, you make use of Appraisal for +acceptance tests. [Appraisal] lets you run tests against multiple versions of +Rails and Ruby, and in fact, this is baked into the test suite. This means that +if you're trying to run a single test file, you'll need to specify which +appraisal to use. For instance, you can't simply say: + +[Appraisal]: https://github.com/thoughtbot/appraisal ``` -sudo su -curl -sL https://rpm.nodesource.com/setup | bash - -yum install -y nodejs +bundle exec rspec spec/acceptance/active_model_integration_spec.rb ``` + +Instead, you need to say + +``` +bundle exec appraisal 5.1 rspec spec/acceptance/active_model_integration_spec.rb +``` + +## Managing your branch + +* Use well-crafted commit messages, providing context if possible. [tpope's + guide] was a wonderful piece on this topic when it came out and we still find + it to be helpful even today. +* Squash "WIP" commits and remove merge commits by rebasing. We try to keep our + commit history as clean as possible. + +[tpope's guide]: https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html + +## Documentation + +As you navigate the codebase, you may notice that each class and method in the +public API is prefaced with inline documentation, which can be viewed +[online][rubydocs]. This documentation is written and generated using +[YARD][yard]. + +[rubydocs]: https://matchers.shoulda.io/docs +[yard]: https://github.com/lsegal/yard + +We ensure that the documentation is up to date before we issue a release, but +sometimes we don't catch everything. So if your changes end up extending or +updating the API, it's a big help if you can update the documentation to match +and submit those changes in your PR. + +## A word on the changelog + +You may also notice that we have a changelog in the form of [NEWS.md](NEWS.md). +You may be tempted to include changes to this in your branch, but don't worry +about this -- we'll take care of it! diff --git a/MAINTAINING.md b/MAINTAINING.md new file mode 100644 index 00000000..8eefdd54 --- /dev/null +++ b/MAINTAINING.md @@ -0,0 +1,250 @@ +# Maintaining Shoulda Matchers + +As maintainers of the gem, this is our guide. Most of the steps and guidelines +in the [Contributing](CONTRIBUTING.md) document apply here, including how to set +up your environment, write code to fit the code style, run tests, craft commits +and manage branches. Beyond this, this document provides some details that would +be too low-level for contributors. + +## Communication + +We use a combination of methods to communicate with each other: + +* In planning major releases, it can be helpful to create a **new issue** + outlining the changes as well as steps needed to launch the release. This + serves both as an announcement to the community as well as an area to keep a + checklist. +* To track progress for the next release, **GitHub milestones** are useful. +* To track progress on the movement of issues, [**labels**](#addendum-labels) + are useful. +* To communicate small-scale changes, **pull requests** are effective, as + mentioned above. +* To communicate large-scale changes or explain topics, **email** is best. + +## Managing the community + +As anyone who has played a sim game before, it's important to make your patrons +happy. We do this by: + +* Answering questions from members of the community +* Closing stale issues and feature requests +* Keeping the community informed by ensuring that the changelog is up to date +* Ensuring that the inline documentation, as well as the docsite, is kept up to + date + +## Workflow + +We generally follow [GitHub Flow]. The `master` branch is the main line, and all +branches are cut from and get merged back into this branch. Generally, the +workflow is as follows: + +[GitHub Flow]: https://help.github.com/articles/github-flow/ + +* Cut a feature or bugfix branch from this branch. +* Upon completing a branch, create a PR and ask another maintainer to approve + it. +* Try to keep the commit history as clean as possible. Before merging, squash + "WIP" or related commits together and rebase as needed. +* Once your PR is approved and you've cleaned up your branch, you're free to + merge it in. + +## Architecture + +Besides the matchers, there are files in `lib` which you may need to reference +or update: + +* `lib/shoulda/matchers/doublespeak*` -- a small handrolled mocking library + which is used by the `permit` matcher +* `lib/shoulda/matchers/util*` -- extra methods which are used in various places + to detect library versions, wrap/indent text, and more + +## Updating the changelog + +After every user-facing change makes it into master, we make a note of it in the +changelog, which for historical reasons is kept in `NEWS.md`. The changelog is +sorted in reverse order by release version, with the topmost version as the next +release (tagged as "(Unreleased)"). + +Within each version, there are five available categories you can divide changes +into. They are all optional but they should appear in this order: + +1. Backward-compatible changes +1. Deprecations +1. Bug fixes +1. Features +1. Improvements + +Within each category section, the changes relevant to that category are listed +in chronological order. + +For each change, provide a human-readable description of the change as well as a +linked reference to the PR where that change emerged (or the commit ID if no +such PR is available). This helps users cross-reference changes if they need to. + +## Documentation + +### Generating documentation + +As mentioned in the Contributing document, we use YARD for documentation. YARD +is configured via `.yardopts` to process the Ruby files in `lib/` as well as +`NEWS.md` and the Markdown files in `docs/` and write the documentation in HTML +form to `doc`. This command will do exactly that: + +```bash +bundle exec yard doc +``` + +However, if you're actively updating the documentation, it's more helpful to +launch a process that will watch the aforementioned source files for changes and +generate the HTML for you automatically: + +```bash +bundle exec rake docs:autogenerate +``` + +Whichever approach you take, you can view the generated docs locally by running: + +```bash +open doc/index.html +``` + +### About the docsite + +The docfiles that YARD generates are published to the docsite, which is located +at: + + + +The docsite is hosted on GitHub Pages*. As such, the `gh-pages` branch hosts the +code for the docsite. This branch is written to automatically by the +`docs:publish` and `docs:publish_latest` tasks. + +The URL above actually links to a bare-bones HTML page which merely serves to +automatically redirect the visitor to the docs for the latest published version +of the gem. This version is hardcoded in the HTML page, but is also updated +automatically by the `docs:publish` and `docs:publish_latest` tasks. + +*\* thoughtbot owns , and +they've got `matchers.shoulda.io` set up on the DNS level as an alias for +`thoughtbot.github.io/shoulda-matchers`.* + +## Versioning + +### Naming a new version + +As designated in the README, we follow [SemVer 2.0][semver]. This offers a +meaningful baseline for deciding how to name versions. Generally speaking: + +[semver]: https://semver.org/spec/v2.0.0.html + +* We bump the "major" part of the version if we're introducing + backward-incompatible changes (e.g. changing the API or core behavior, + removing parts of the API, or dropping support for a version of Ruby). +* We bump the "minor" part if we're adding a new feature (e.g. adding a new + matcher or adding a new qualifier to a matcher). +* We bump the "patch" part if we're merely including bugfixes. + +In addition to major, minor, and patch levels, you can also append a +suffix to the version for pre-release versions. We usually use this to issue +release candidates prior to an actual release. A version number in this case +might look like `4.0.0.rc1`. + +### Releasing a new version + +Releasing a new version is very simple: + +1. First, you'll want to be given ownership permissions for the Ruby gem itself. + If you want to give someone else these rights, you can use: + + ```bash + gem owner shoulda-matchers -a + ``` +1. Next, you'll want to update the `VERSION` constant in + `lib/shoulda/matchers/version.rb`. This constant is referenced in the gemspec + and is used in the Rake tasks to publish the gem on RubyGems as well as + generate documentation. +1. Finally, you'll want to run: + + ```bash + rake release + ``` + + This will not only push the gem to RubyGems, but also update the docsite. + +### Re-publishing docs + +In general you'll use the `release` task to update the docsite, but there may be +a situation where you'll need to do it manually. + +You can re-publish the docs for the latest version (as governed by +`lib/shoulda/matchers/version.rb`) by running: + +```bash +bundle exec rake docs:publish_latest +``` + +This will update the version to which the docsite auto-redirects to the latest +version. For instance, if the latest version were 4.0.0, this command would +publish the docs at but redirect + to this location. + +However, if you want to publish the docs for a version and at the same +time manually set the auto-redirected version, you can run this instead: + +```bash +bundle exec rake docs:publish[version, latest_version] +``` + +Here, `version` and `latest_version` are both version strings. For instance, you +might say: + +```bash +bundle exec rake docs:publish[4.0.0, 3.7.2] +``` + +This would publish the docs for 4.0.0 at +, but redirect + to . + +## Addendum: Labels + +In order to corral the issue and PR backlog, we've found +[labels] to be useful for cataloguing and tracking progress purposes. Over time +we've added quite a collection of labels. Here's a quick list: + +[labels]: https://github.com/thoughtbot/shoulda-matchers/labels + +### Labels for issues + +* **Issue: Bug** +* **Issue: Feature Request** +* **Issue: Need to Investigate** -- if we don't know whether a bug is legitimate + or not +* **Issue: PR Needed** -- perhaps unnecessary, but it does signal to the + community that we'd love a PR + +### Labels for PRs + +* **PR: Bugfix** +* **PR: Feature** +* **PR: Good to Merge** -- most of the time not necessary, but can be helpful in + a code freeze before a release to mark PRs that we will include in the next + release +* **PR: In Progress** -- used to mark PRs that are still being worked on by the + PR author +* **PR: Needs Documentation** +* **PR: Needs Review** +* **PR: Needs Tests** +* **PR: Needs Updates Before Merge** -- along the same lines as the other + "Needs" tags, but more generic + +### Generic labels + +* **Blocked** +* **Documentation** +* **Needs Decision** +* **Needs Revisiting** +* **Question** +* **Rails X** +* **Ruby X.Y** +* **UX** diff --git a/README.md b/README.md index 5052c65e..26701c9e 100644 --- a/README.md +++ b/README.md @@ -232,67 +232,6 @@ class PersonTest < ActiveSupport::TestCase end ``` -## Running tests - -### Unit tests - -Unit tests are the most common kind of tests in this gem, and the best way to -run them is by using [Zeus]. - -You'll want to run `zeus start` in one shell, then in another shell, instead of -using `rspec` to run tests, you can use `zeus rspec`. So for instance, you might -say: - -``` -zeus rspec spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb -``` - -As a shortcut, you can also drop the initial part of the path and say this -instead: - -``` -zeus rspec active_model/validate_inclusion_of_matcher_spec.rb -``` - -### Acceptance tests - -The gem uses [Appraisal] to test against multiple versions of Rails and Ruby. -This means that if you're trying to run a single test file, you'll need to -specify which appraisal to use. For instance, you can't simply say: - -``` -rspec spec/acceptance/active_model_integration_spec.rb -``` - -Instead, you need to say - -``` -bundle exec appraisal 5.1 rspec spec/acceptance/active_model_integration_spec.rb -``` - -### All tests - -You can run all tests by saying: - -``` -bundle exec rake -``` - -## Generating documentation - -YARD is used to generate documentation, which can be viewed [online][rubydocs]. -You can preview changes you make to the documentation locally by running - - yard doc - -from this directory. Then, open `doc/index.html` in your browser. - -If you want to be able to regenerate the docs as you work without having to run -`yard doc` over and over again, keep this command running in a separate terminal -session: - - rake docs:autogenerate - ## Contributing Shoulda Matchers is open source, and we are grateful for diff --git a/bin/setup b/bin/setup new file mode 100755 index 00000000..564c5b27 --- /dev/null +++ b/bin/setup @@ -0,0 +1,190 @@ +#!/usr/bin/env bash + +set -euo pipefail + +RUBY_VERSION=$(script/supported_ruby_versions | xargs -n 1 echo | sort -V | tail -n 1) + +cd "$(dirname "$(dirname "$0")")" + +uname=$(uname) + +if [[ $uname == 'Darwin' ]]; then + platform='mac' +else + platform='linux' +fi + +banner() { + echo -e "\033[34m== $@ ==\033[0m" +} + +success() { + echo -e "\033[32m$@\033[0m" +} + +warning() { + echo -e "\033[33m$@\033[0m" +} + +error() { + echo -e "\033[31m$@\033[0m" +} + +has-executable() { + type "$1" &>/dev/null +} + +is-running() { + pgrep "$1" >/dev/null +} + +install() { + local apt_package="" + local rpm_package="" + local brew_package="" + local default_package="" + local package="" + + for arg in "$@"; do + case $arg in + apt=*) + apt_package="$arg" + ;; + rpm=*) + rpm_package="$arg" + ;; + brew=*) + brew_package="$arg" + ;; + *) + default_package="$arg" + ;; + esac + done + + if has-executable brew; then + package="${brew_package:-$default_package}" + + if [[ -n $package ]]; then + brew install "$package" + fi + elif has-executable apt-get; then + package="${apt_package:-$default_package}" + + if [[ -n $package ]]; then + sudo apt-get install -y "$package" + fi + elif has-executable yum; then + package="${yum_package:-$default_package}" + + if [[ -n $package ]]; then + sudo yum install -y "$package" + fi + else + error "Sorry, I'm not sure how to install $default_package." + exit 1 + fi +} + +check-for-build-tools() { + if [[ $platform == "linux" ]]; then + if ! has-executable apt-get; then + error "You don't seem to have a package manager installed." + echo "The setup script assumes you're using Debian or a Debian-derived flavor of Linux" + echo "(i.e. something with Apt). If this is not the case, then we would gladly take a" + echo "PR fixing this!" + exit 1 + fi + + # TODO: Check if build-essential is installed on Debian? + else + if ! has-executable brew; then + error "You don't seem to have Homebrew installed." + echo + echo "Follow the instructions here to do this:" + echo + echo "http://brew.sh" + exit 1 + fi + + # TODO: Check that OS X Command Line Tools are installed? + fi +} + +install-development-libraries() { + install apt=ruby-dev rpm=ruby-devel + install rpm=zlib-devel +} + +install-dependencies() { + if ! has-executable sqlite3; then + banner 'Installing SQLite 3' + install sqlite3 + install apt=libsqlite3-dev rpm=sqlite-devel + fi + + if ! has-executable psql; then + banner 'Installing PostgreSQL' + install postgresql + install apt=libpq-dev rpm=postgresql-devel + fi + + if ! is-running postgres; then + banner 'Starting PostgreSQL' + start postgresql + fi + + if ! has-executable heroku; then + banner 'Installing Heroku' + install heroku/brew/heroku heroku + fi + + if has-executable rbenv; then + if ! (rbenv versions | grep $RUBY_VERSION'\>' &>/dev/null); then + banner "Installing Ruby $RUBY_VERSION with rbenv" + rbenv install --skip-existing "$RUBY_VERSION" + fi + elif has-executable rvm; then + if ! (rvm ls | grep $RUBY_VERSION'\>' &>/dev/null); then + banner "Installing Ruby $RUBY_VERSION with rvm" + error "You don't seem to have Ruby $RUBY_VERSION installed." + echo + echo "Use RVM to do so, and then re-run this command." + echo + fi + else + error "You don't seem to have a Ruby manager installed." + echo + echo 'We recommend using rbenv. You can find installation instructions here:' + echo + echo 'http://github.com/rbenv/rbenv' + echo + echo "When you're done, simply re-run this script!" + exit 1 + fi + + banner 'Installing Ruby dependencies' + gem install bundler --conservative + bundle check || bundle install + bundle exec appraisal install + + if ! has-executable node; then + banner 'Installing Node' + + if [[ $platform == 'linux' ]]; then + curl -sL https://deb.nodesource.com/setup_10.x | sudo -E bash - + + install nodejs + + if ! has-executable npm; then + install npm + fi + else + install nodejs + fi + fi +} + +check-for-build-tools +install-development-libraries +install-dependencies