Update contributor docs, add maintainer docs

* Move section on documentation from README to CONTRIBUTING
* Add a setup script that contributors and maintainers can use to set up
  a dev environment

[skip ci]
This commit is contained in:
Elliot Winkler 2018-09-15 22:36:22 -06:00 committed by Gui Vieira
parent 40a86cc2bb
commit ff14d6ffc4
4 changed files with 589 additions and 130 deletions

View File

@ -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 <Appraisal name> rspec <path of test file to run>
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 <path of test file to run>
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!

250
MAINTAINING.md Normal file
View File

@ -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:
<https://matchers.shoulda.io/docs>
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 <https://shoulda.io>, 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 <email address>
```
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 <https://matchers.shoulda.io/docs/v4.0.0> but redirect
<https://matchers.shoulda.io/docs> 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
<https://matchers.shoulda.io/docs/v4.0.0>, but redirect
<https://matchers.shoulda.io/docs> to <https://matchers.shoulda.io/docs/v3.7.2>.
## 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**

View File

@ -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

190
bin/setup Executable file
View File

@ -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