---
url: /reference/validators.md
---
# Validators

Validators available to any Active Record or Active Model class of the app (`User`, a sign-up, a contact or newsletter form…): Trek's own, and those of the gems Trek depends on, which it loads.

| Validator | Key | From |
| --- | --- | --- |
| [Disposable email addresses](#disposable-email-addresses) | `not_disposable` | Trek |
| [Email format](#email-format) | `email` | [email_validator](https://github.com/K-and-R/email_validator) |
| [Email checks](#email-checks) | `"valid_email_2/email"` | [valid_email2](https://github.com/micke/valid_email2) |
| [Phone numbers](#phone-numbers) | `phone` | [Phonelib](https://github.com/daddyz/phonelib) |
| [Uniqueness of a translated attribute](#uniqueness-of-a-translated-attribute) | `uniqueness` | [Mobility](https://github.com/shioyama/mobility) |

## Disposable email addresses

`NotDisposableValidator` refuses an email address from a throwaway email provider (mailinator.com, yopmail.com…), or a subdomain of one, whatever its case:

```ruby
class Contact < ApplicationRecord
  validates :email, presence: true, email: { mode: :strict }, not_disposable: true
end
```

It is opt-in: Trek doesn't add it to `User`, whose accounts admins create. A blank address is left to the presence validation.

The error is `:disposable`, « is a disposable email address, use a permanent one » (« est une adresse e-mail jetable, utilisez-en une permanente »). Change it in the app's locales (`errors.messages.disposable`), or per model:

```ruby
validates :email, not_disposable: { message: "must be your work address" }
```

The domains come from the [valid_email2](https://github.com/micke/valid_email2) gem, whose maintainers keep the list up to date: updating the gem updates the list, with no data to refresh in the app. Only the list is checked, without looking up the domain's mail servers. To let a domain through, list it in `config/allow_listed_email_domains.yml`:

```yaml
- legit-provider.example
```

The list (about 180,000 domains) is loaded on the first check, as a single string of 2.6 MB searched by bisection, rather than valid_email2's own Set of about 35 MB: about 5 µs per address.

## Email format

`EmailValidator` checks an address's shape, without any network lookup:

```ruby
validates :email, email: { mode: :strict }
```

| Option | Default | |
| --- | --- | --- |
| `mode` | `:loose` | `:loose` only asks for something on each side of an `@`; `:strict` refuses consecutive or leading/trailing dots and asks for a domain with a TLD; `:rfc` follows RFC 5322 |
| `require_fqdn` | — (true in `:strict` mode) | a domain with a TLD |
| `domain` | — | only this domain, e.g. `domain: "example.com"` |
| `allow_nil` | `false` | |

`User` uses `:strict` mode.

## Email checks

valid_email2's validator checks an address's shape too, and optionally more: its domain's mail servers (`mx: true`, `strict_mx: true`), a deny list (`deny_list: true`, from `config/deny_listed_email_domains.yml`), or refusing `+tag` addresses (`disallow_subaddressing: true`). See [its options](https://github.com/micke/valid_email2#usage).

```ruby
validates :email, "valid_email_2/email": { mx: true }
```

* `mx` and `strict_mx` look the domain up in the DNS on every validation, which can take up to `dns_timeout` (5 seconds) and fails offline, in specs too.
* For disposable addresses, use [`not_disposable`](#disposable-email-addresses) rather than its `disposable` options: they load its Set of 180,000 domains, which grows each process by about 35 MB.

## Phone numbers

`PhoneValidator` checks a phone number with Phonelib, against `Phonelib.default_country` when the number has no country code:

```ruby
validates :phone, phone: { possible: true, allow_blank: true }
```

| Option | |
| --- | --- |
| `possible` | `true` to only ask whether the digits could form a number for the country; otherwise they must fall in a range actually assigned (see [Strictness](/reference/concerns/phonable#strictness)) |
| `types` | e.g. `:mobile`, `[:mobile, :fixed]` |
| `countries` | e.g. `[:fr, :be]` |
| `allow_blank` | a blank value fails otherwise |

[`Trek::Phonable`](/reference/concerns/phonable) adds it for you, with the number's normalization and formatting.

## Uniqueness of a translated attribute

On a model with [translated attributes](/reference/concerns/translatable), Mobility replaces `uniqueness`, so it works on them too, in the current locale: a title taken in English is still free in French.

```ruby
class Fragment < ApplicationRecord
  include Trek::Translatable
  translate_attributes :title, :content
  validates :title, uniqueness: true
end
```
