Models

class App.User extends Tower.Model
  @field "firstName"

Tower.Model.Attributes

Consider a simple class for modeling a user in an application. A user may have a first name, last name, and middle name. We can define these attributes on a user by using the fields macro.

class App.User extends Tower.Model
  @field "firstName", type: "String"
  @field "middleName", type: "String"
  @field "lastName", type: "String"

Below is a list of valid types for fields.

If you decide not to specify the field's type, Tower.js will treat it as a JavaScript Object and will try not typecast it when sending the values to the data store. This means that by default you can easily store things like arbitrary JavaScript objects in MongoDB without worrying about it.

Getting and Setting Field Values

When a field is defined, Tower provides several different ways of accessing the field.

# Get the value of the first name field.
user.get("firstName")
user.firstName # if get/set support is available

# Set the value for the first name field.
user.set("firstName", "Jean")
user.firstName = "Jean" # if get/set support is available

In cases where you want to set multiple field values at once, there are a few different ways of handling this as well.

# Get the field values as a hash.
user.attributes

# Set the field values in the document.
User.new(firstName: "Jean-Baptiste", middleName: "Emmanuel")
user.attributes = { firstName: "Jean-Baptiste", middleName: "Emmanuel" }

Note: Unlike frameworks such as Spine.js and Backbone.js, changing an attribute's value does not dispatch an event. Tower.js does not come stocked with event dispatching, for many reasons (more on this later). There is a simple module Tower.Model.Events which you can include in the base model which will add event dispatching. For simple apps, you don't need event dispatching using the Controller model from Rails that Tower.js implements. For complex apps that need data-binding, client-side frameworks like Ember.js, Knockout.js, and Angular.js complement Tower.js perfectly and can definitely be used with it.

Defaults

You can tell a field in Tower to always have a default value if nothing has been provided. Defaults are either static values or callback functions.

class App.User extends Tower.Model
  @field "bloodAlcoholLevel", type: "Float", default: 0.40
  @field "lastLogin", type: "Time", default: -> 10.minutes.ago

Be wary that default values that are not defined as functions are evaluated at class load time, so the following 2 definitions are not equivalent. (You probably would prefer the second, which is at document creation time.)

class App.User extends Tower.Model
  @field "dob", type: "Time", default: Time.now
  @field "dob", type: "Time", default: -> Time.now

If you want to set a default with a dependency on the document's state, this inside a callback evaluates to the document instance.

class App.User extends Tower.Model
  @field "joinedAt", type: "Time", default: -> if @isNew() then 2.hours.ago else Time.now

Custom Field Serialization

You can define custom types in Tower and determine how they are serialized and deserialized. You simply need to define the class, include Tower::Fields::Serializable, and override the serialize and deserialize methods as needed. Deserialization is used to convert from the value that is stored in the database to a value that is used when accessed. Serialization is used to convert the object to a MongoDB friendly value.

class Profile extends Tower.Model
  @field location, type: "Point"

class Point
  include Tower::Fields::Serializable

  decode: (object) ->
    [ object["x"], object["y"] ]

  encode: (object) ->
    { "x" : object[0], "y" : object[1] }

Reserved Names

If you define a field on your document that conflicts with a reserved method name in Tower, the configuration will raise an error. For a list of these you may look at Tower.destructiveFields.

Tower.Model.Callbacks

Tower supports 3 main callbacks:

The following callbacks are implemented:

Callbacks are available on any model.

Define a callback with the callback phase helpers

class App.Post extends Tower.Model
  @field "title", type: "String"
  @field "slug", type: "String"

  @before "save", "generateSlug"

  generateSlug:  ->
    @set "slug", @get("title").replace(/[^a-z0-9]+/, '-').toLowerCase()

Define the phase and callback directly

class App.Post extends Tower.Model
  @field "title", type: "String"
  @field "slug", type: "String"

  @callback "save", "before", "generateSlug"

  generateSlug:  ->
    @set "slug", @get("title").replace(/[^a-z0-9]+/, '-').toLowerCase()

Define callbacks with anonymous functions

class App.Post extends Tower.Model
  @field "title", type: "String"
  @field "slug", type: "String"

  @before "save", ->
    @set "slug", @get("title").replace(/[^a-z0-9]+/, '-').toLowerCase()

Callbacks can be asynchronous

If you have a callback that executes asynchronous code, you can add the callback argument to your function, and call it when complete:

class App.Post extends Tower.Model
  @field "title", type: "String"
  @field "url", type: "String"

  @before "save", "scrapeWebsite"

  scrapeWebsite: (callback) ->
    SomeCrawler.scrapeHTML @get("url"), (error, html) ->
      callback(error)

Dirty Tracking

Tower supports tracking of changed or "dirty" fields with an API that mirrors that of Active Model. If a defined field has been modified in a model the model will be marked as dirty and some additional behaviour comes into play.

Viewing Changes

There are various ways to view what has been altered on a model. Changes are recorded from the time a document is instantiated, either as a new document or via loading from the database up to the time it is saved. Any persistence operation clears the changes.

class App.User extends Tower.Model
  @field "name", type: "String"

user = App.User.first()
user.set "name", "Alan Garner"

# Check to see if the document has changed.
user.isDirty() #=> true

# Get a hash of the old and changed values for each field.
user.changes #=> { "name" : [ "Alan Parsons", "Alan Garner" ] }

# Get the changes for a specific field.
user.attributeChange("name") #=> [ "Alan Parsons", "Alan Garner" ]

# Get the previous value for a field.
user.attributeWas("name") #=> "Alan Parsons"

Resetting Changes

You can reset changes of a field to it's previous value by calling the reset method.

user = App.User.first()

user.set "name", "Alan Garner"

# Reset the changed name back to the original
user.resetAttribute("name")
user.get("name") #=> "Alan Parsons"

Notes on Persistence

Tower uses dirty tracking as the core of its persistence operations. It looks at the changes on a document and atomically updates only what has changed unlike other frameworks that write the entire document on each save. If no changes have been made, Tower will not hit the database on a call to Model#save.

Finders - Tower.Model.Scopes Part 1

Here are the methods used to query models in a datastore:

These methods are delegated to a method of the same name a Tower.Model.Scope instance. By delegating all query and persistence calls to the Tower.Model.Scope object, there's one place in the Tower.js code to build out a very powerful API for chainable scopes (more on that later). This means you can do:

User.all()

or create a reusable scope:

User.where(firstName: /^[aA]/).limit(10).all()

By calling one of the finder methods, the scope's criteria are compiled into an optimized query and the models are queried.

Model.all

Returns an array of models. It only takes one argument, the callback. If you're using the memory store, it will also return an array of models so you don't need to pass in a callback. This makes TDD much easier. BUT, don't count on that, as the other stores return sometimes random things. Use the callback whenever you can. As usual, the first argument in the callback is an error.

User.all (error, models) ->
  for model in models
    console.log model.get("id")

Model.find

Provides the ability to find one or many models given a set of ids. This is a more all-inclusive API than all.

The first way to use this method is for finding a single record given the provided id. If no record is found this will raise an error unless the configuration option is changed. You can call this method on a scope as well, so you can find all users with a last name of "Black" who have this id.

User.find(id)
User.find("4baa56f1230048567300485c")
User.where(lastName: "Black").find(id)

You may also find multiple records given the provided array of ids. If a single record is not found the error will get raised.

User.find([idOne, idTwo])
User.find(["4baa56f1230048567300485c","4baa56f1230048567300485d"])
User.where(lastName: "Black").find([idOne, idTwo])

If multiple ids are passed, you will get an array back. If you only pass 1 id, then you get a record back. The complete signature looks like this:

User.find "4baa56f1230048567300485c", (error, record) ->
User.find ["4baa56f1230048567300485c", "4baa56f1230048567300485d"], (error, records) ->

Model.first

Find the first record in the datastore given the provided criteria. Will return a record or null if nothing is found and defaults to the natural sorting of records in the datastore. You can provide sort criteria as well if you want to dictate the exact record that would be returned first.

User.first (error, record) ->

Model.last

Find the last record in the datastore given the provided criteria. Will return a record or null if nothing is found and defaults to to sorting by id in descending order. You may provide sort criteria as well if you want to dictate the exact record that would be returned - Tower will invert the sort criteria you provide.

User.last (error, record) ->

Model.count

Get the count of records given the provided criteria.

User.count (error, count) ->

Model.exists

Returns true if any records in the datastore exist given the provided criteria and false if there are none.

# Do any records exist in the datastore for the provided conditions?
User.exists (error, exists) ->

Model.batch (todo)

This will grab records from the datastore in chunks, to prevent a memory usage explosion if you have a lot of records.

User.batch(20).each (user) ->

Querying - Tower.Model.Scopes Part 2

The following are a list of chainable query methods in Tower.js. Shown alongside each example are the generated query parameters and options which are passed to the store object. The stores then convert these normalized criteria into the datastore-specific format.

Please note that criteria are lazy evaluated, and with each chained method it will be cloned and return a new criteria copy.

Query Methods

Model.allIn

Adds a criterion that specifies values that must all match in order to return results.

Model

# Match all people with Bond and 007 as aliases.
User.allIn(aliases: ["Bond", "007"])

Criteria

{ "aliases" : { "$all" : [ "Bond", "007" ] }}

Model.allOf

Adds a criterion that specifies expressions that must all match in order to return results.

Model

# Match all crazy old people.
User.allOf(age: {">=": 60 }, mentalState: "crazy mofos")

Criteria

{ "$and" : [{ "age" : { "$gt" : 60 }}, { "mentalState" : "crazy mofos" }] }

Model.alsoIn

Adds a criterion that specifies values where any value can be matched in order to return results. This is similar to Criteria#anyIn with the exception here that if if it chained with values for the same field it performs a union of the values where anyIn perform an intersection.

Model

# Match all people with either Bond or 007 as aliases.
User.alsoIn(aliases: [ "Bond", "007" ])
User.anyIn(aliases: [ "Bond" ]).alsoIn(aliases: [ "007" ])

Criteria

{ "aliases" : { "$in" : [ "Bond", "007" ] }}

Model.anyIn

Adds a criterion that specifies values where any value can be matched in order to return results. This is similar to Criteria#alsoIn with the exception here that if if it chained with values for the same field it performs an intersection of the values where alsoIn perform a union.

Model

# Match all people with either Bond or 007 as aliases.
User.anyIn(aliases: [ "Bond", "007" ])
User
  .anyIn(aliases: [ "Bond", "007", "James" ])
  .anyIn(aliases: [ "Bond", "007" ])

Criteria

{ "aliases" : { "$in" : [ "Bond", "007" ] }}

Model.anyOf

Adds a criterion that specifies a set of expressions that any can match in order to return results. The underlying MongoDB expression is $or.

Model

# Match all people with either last name Penn or Teller
User.anyOf({ lastName: "Penn" }, { lastName: "Teller" })

Criteria

{ "lastName" :
  { "$or" :
    [ { "lastName" : "Penn" }, { "lastName" : "Teller" } ]
  }
}

Model.asc

Adds ascending sort options for the provided fields.

Model

# Sort people by first and last name ascending.
User.asc("firstName", "lastName")

Criteria

{ "sort" :
    {[ [ "firstName", "asc" ],
      [ "lastName", "asc" ] ]} }

Model.desc

Adds descending sort options for the provided fields.

Model

# Sort people by first and last name descending.
User.desc("firstName", "lastName")

Criteria

{ "sort" :
    {[ [ "firstName", "desc" ],
      [ "lastName", "desc" ] ]} }

Model.distinct(name)

Get the distinct values for the provided field.

Model

# Get the distinct values for last names
User.distinct("lastName")

Criteria

{ "distinct" : "lastName" }

Model.excludes

Adds a criterion that specifies a set of expressions that cannot match in order to return results. The underlying MongoDB expression is $ne.

Model

# Match all people without either last name Teller and first name Bob.
User.excludes(lastName: "Teller", firstName: "Bob")

Criteria

{{ "lastName" : { "$ne" : "Teller" } }, { "firstName" : { "$ne" : "Bob" } }}

Model.includes

Adds a criterion that specifies a list of relational associations to eager load when executing the query. This is to prevent the n+1 issue when iterating over documents that access their relations during the iteration.

This only works with hasMany, hasOne, and belongsTo relations and only 1 level deep at the current moment. If you try to eager load a many to many an exception will get raised. Many to many is not supported due to the performance actually being slower despite lowering the number of datastore calls.

Model

# Eager load the posts and games when retrieving the people.
User.includes("posts", "comments")

Criteria

peopleIds = people.find({}, { "fields" : { "_id" : 1 }})
posts.find({ "personId" : { "$in" : peopleIds }})
comments.find({ "personId" : { "$in" : peopleIds }})

Model.limit

Limits the number of returned results by the provided value.

Model

# Only return 20 documents.
User.limit(20)

Criteria

{ "limit" : 20 }

Model.near

Adds a criterion to find locations that are near the supplied coordinates. This performs a MongoDB $near selection and requires a 2d index to be on the provided field.

Model

# Match all bars near Berlin
Bar.near(location: [ 52.30, 13.25 ])

Criteria

{ "location" : { "$near" : [ 52.30, 13.25 ] }}

Model.notIn

Adds a criterion that specifies a set of expressions that cannot match in order to return results. The underlying MongoDB expression is $nin.

Model

# Match all people without last names Zorg and Dallas
User.notIn(lastName: [ "Zorg", "Dallas" ])

Criteria

{{ "lastName" : { "$nin" : [ "Zorg", "Dallas" ] } }}

Model.only

Limits the fields returned from the datastore to those supplied to the method. Extremely useful for list views where the entire documents are not needed. Cannot be used in conjunction with #without.

Model

# Return only the first and last names of each person.
User.only("firstName", "lastName")

Criteria

options: { "fields" : { "firstName" : 1, "lastName" : 1 }}

Model.order

Sorts the results given the arguments that must match the MongoDB driver sorting syntax (key/value pairs of field and direction).

Model

# Provide the sorting options.
User.order("firstName", "asc").order("lastName", "desc")

Criteria

{ "sort" :
    {[ [ "firstName", "asc" ],
      [ "lastName", "desc" ] ]} }

Model.skip

Skips the number of the results given the provided value, similar to a SQL "offset".

Model

# Skip 20 documents.
User.skip(20)

Criteria

{ "skip" : 20 }

Model.where

Adds a criterion that must match in order to return results. If provided a string it interperets it as a javascript function and converts it to the proper $where clause. Tower also provides convenience h4s on Symbol to make advanced queries simpler to write.

Model

# Match all people with first name Emmanuel
User.where(firstName: "Emmanuel")

# Match all people who live in Berlin, where address is embedded.
User.where("addresses.city": "Berlin")

# Same as above but with a hash.
User.where(addresses: city: "Berlin")

# Match all people who live at an address in Berlin or
# Munich where address is embedded.
User.where("addresses.city": {"$in": ["Berlin", "Munich"]})

# Example complex queries
User.where(age: ">": 21)
User.where(age: $gt: 21)
User.where(age: ">=": 21)
User.where(age: $gte: 21)
User.where(title: $in: ["Sir", "Madam"])
User.where(age: "<": 55)
User.where(age: $lt: 55)
User.where(age: "<=": 55)
User.where(age: $lte: 55)
User.where(title: $ne: "Mr")
User.where(title: $nin: ["Esquire"])
User.where(age: ">=": 18, "<=": 55)

Criteria

# Match all people with first name Emmanuel
{ "firstName" : "Emmanuel" }

# Match all people who live in Berlin, where address is embedded.
{ "addresses.city" : "Berlin" }

# Example queries using symbol h4s to perform more complex criteria.
{ "age" : { "$gt" : 18 }}
{ "age" : { "$gt" : 18 }}
{ "age" : { "$gte" : 18 }}
{ "age" : { "$gte" : 18 }}
{ "title" : { "$in" : [ "Sir", "Madam" ] }}
{ "age" : { "$lt" : 55 }}
{ "age" : { "$lt" : 55 }}
{ "age" : { "$lte" : 55 }}
{ "age" : { "$lte" : 55 }}
{ "title" : { "$ne" : "Mr" }}
{ "title" : { "$nin" : [ "Esquire" ] }}
{ "age" : { "$gte" : 18, "$lte" : 55 }}

Model.without

Limits the fields returned from the datastore to those NOT supplied to the method. Extremely useful for list views where the entire documents are not needed. Cannot be used in conjunction with #only.

Model

# Return all fields except first name and last name
User.without("firstName", "lastName")

Criteria

{ "fields" : { "firstName" : 0, "lastName" : 0 }}
toParam: "blog-post"
toKey: "blogPost"
toUrl: "/sites/10/blog-posts"
metadata =
  parameterCase:        "blog-post"
  parameterCasePlural:  "blog-posts"
  camelCase:            "blogPost"
  camelCasePlural:      "blogPosts"
  typeCase:             "BlogPost"
  typeCasePlural:       "BlogPosts"

Tower.Model.Persistence

Tower's standard persistence methods come in the form of common methods you would find in other mapping frameworks.

Model.create

Inserts a new document into the database given the provided attributes. This will run validations and will return the document whether it was persisted or not. You can check Model#persisted? to see if it was successful.

Model

# Insert a new German poet to the db.
Person.create(firstName: "Heinrich", lastName: "Heine")
# This can also take a block.
Person.create firstName: "Heinrich", (record) ->
  doc.lastName = "Heine"

Store

store.create { "firstName" : "Heinrich", "lastName" : "Heine" }

Model#save

Saves the document to the database. If the document is new then the entire document will be inserted. If the document is already saved then only changes to the document will the persisted. This runs validations by default, however they can be switched off by providing an option to the method. Returns true if validation passed and false if not.

Model

# Insert a new German poet to the db.
person = Person.new(firstName: "Heinrich", lastName: "Heine")
person.save()

# Save without running validations.
person.save(validate: false)

# Save an existing document's changed fields.
person.firstName = "Christian Johan"
person.save()

Store

# Insert command for the new document.
collections["people"].insert({
  "_id" : ..., "firstName" : "Heinrich", "lastName" : "Heine"
})

# Update command for the changed document.
collections["people"].update({
  { "_id" : ... },
  { "$set" : { "firstName" : "Christian Johan" } }
})

Model#updateAttributes

Modifies the provided attributes to new values and persists them in a single call. This runs validations and will return true if they passed, false if not.

Model

# Update the provided attributes.
person.updateAttributes(firstName: "Jean", lastName: "Zorg")

Store

# Update command for the changed document.
collections["people"].update({
  { "_id" : ... },
  { "$set" : { "firstName" : "Jean", "lastName" : "Zorg" } }
})

Model#updateAttribute

Updates a single attribute in the database without going through the normal validation procedure, but does fire callbacks. Returns true if save was successful, false if not.

Model

# Update the provided attribute.
person.updateAttribute(:firstName, "Jean")

Store

# Update command for the changed document.
collections["people"].update({
  { "_id" : ... },
  { "$set" : { "firstName" : "Jean" } }
})

Model#delete

Deletes the document from the database without running callbacks.

Model

person.delete()

Store

collections["people"].remove("_id" : ... )

Model#destroy

Deletes the document from the database while running destroy callbacks.

Model

person.destroy()

Store

collections["people"].remove("_id" : ... )

Model.delete

Deletes all matching documents in the database given the supplied conditions. See the criteria section on deletion for preferred ways to perform these actions. This does not run any callbacks on the matching documents.

Model

# Delete all the documents from the collection.
Person.delete()

# Delete all matching documents.
Person.where(firstName: "Heinrich").delete()

Store

# Delete all command.
collections["people"].remove

# Delete all matching command.
collections["people"].remove("firstName" : "Heinrich")

Model.destroy

Deletes all matching documents in the database given the supplied conditions. See the criteria section on deletion for preferred ways to perform these actions. This runs destroy callbacks on all matching documents.

Model

# Destroy all the documents from the collection.
Person.destroy()

# Destroy all matching documents.
Person.where(firstName: "Heinrich").destroy()

Store

# Destroy all command.
collections["people"].remove

# Destroy all matching command.
collections["people"].remove("firstName" : "Heinrich")

Model.update

User.update 1, 2, 3, name: "John", (error, records)
User.update 1, 2, 3, name: "John", (error)
User.update [1, 2, 3], name: "John", (error, records)
User.update [1, 2, 3], name: "John", (error)
User.update name: "John", (error, records)
User.update name: "John", (error)
User.update 1, 2, 3, {name: "John"}, {instantiate: false, validate: false}, (error)
User.update {name: "John"}, {instantiate: false, validate: false}, (error)

Other Examples

# Model.create
User.create(firstName: "Lance")
User.where(firstName: "Lance").create()
User.where(firstName: "Lance").create([{lastName: "Pollard"}, {lastName: "Smith"}])
User.where(firstName: "Lance").create(new User(lastName: "Pollard"))

# Model.update
User.where(firstName: "Lance").update(1, 2, 3)
User.update(User.first(), User.last(), firstName: "Lance")
User.update([User.first(), User.last()], firstName: "Lance")
User.update([1, 2], firstName: "Lance")

Validations

The Errors Object

Validation Helpers

Tower offers many pre-defined validation helpers that you can use directly inside your model class definitions. These helpers provide common validation rules. Every time a validation fails, an error message is added to the object's errors collection, and this message is associated with the field being validated.

Each helper accepts an arbitrary number of attribute names, so with a single line of code you can add the same kind of validation to several attributes.

All of them accept the :on and :message options, which define when the validation should be run and what message should be added to the errors collection if it fails, respectively. The :on option takes one of the values :save (the default), :create or :update. There is a default error message for each one of the validation helpers. These messages are used when the :message option isn't specified. Let's take a look at each one of the available helpers.

Acceptance

Validates that a checkbox on the user interface was checked when a form was submitted. This is typically used when the user needs to agree to your application's terms of service, confirm reading some text, or any similar concept. This validation is very specific to web applications and this 'acceptance' does not need to be recorded anywhere in your database (if you don't have a field for it, the helper will just create a virtual attribute).

class User extends Tower.Model
  @validates "termsOfService", acceptance: true

The default error message for this helper is "must be accepted".

It can receive an :accept option, which determines the value that will be considered acceptance. It defaults to "1" and can be easily changed.

class User extends Tower.Model
  validates "termsOfService", acceptance: { accept: 'yes' }

Associated

You should use this helper when your model has associations with other models and they also need to be validated. When you try to save your object, valid? will be called upon each one of the associated objects.

class Library extends Tower.Model
  @hasMany "books"
  @validates associated: "books"

This validation will work with all of the association types.

Don't use validatesAssociated on both ends of your associations. They would call each other in an infinite loop.

The default error message for validatesAssociated is "is invalid". Note that each associated object will contain its own errors collection; errors do not bubble up to the calling model.

Confirmation

You should use this helper when you have two text fields that should receive exactly the same content. For example, you may want to confirm an email address or a password. This validation creates a virtual attribute whose name is the name of the field that has to be confirmed with "_confirmation" appended.

class User extends Tower.Model
  @validates "email", confirmation: true

In your view template you could use something like

<%= textField :person, :email %> <%= textField :person, :emailConfirmation %>

This check is performed only if emailConfirmation is not nil. To require confirmation, make sure to add a presence check for the confirmation attribute (we'll take a look at presence later on this guide):

class User extends Tower.Model
  @validates "email", :confirmation: true
  @validates "emailConfirmation", :presence: true

The default error message for this helper is "doesn't match confirmation".

Exclusion

This helper validates that the attributes' values are not included in a given set. In fact, this set can be any enumerable object.

class Account extends Tower.Model
  @validates "subdomain", exclusion: { in: ["www", "us", "ca", "jp"], message: "Subdomain %{value} is reserved." }

The exclusion helper has an option :in that receives the set of values that will not be accepted for the validated attributes. The :in option has an alias called :within that you can use for the same purpose, if you'd like to. This example uses the :message option to show how you can include the attribute's value.

The default error message is "is reserved".

Format

This helper validates the attributes' values by testing whether they match a given regular expression, which is specified using the :with option.

class Product extends Tower.Model
  @validates "legacyCode", format: { with: /\A[a-zA-Z]+\z/, message: "Only letters allowed" }

The default error message is "is invalid".

Inclusion

This helper validates that the attributes' values are included in a given set. In fact, this set can be any enumerable object.

class Coffee extends Tower.Model
  @validates "size", inclusion: { in: ["small", "medium", "large"], message: "%{value} is not a valid size" }

The inclusion helper has an option :in that receives the set of values that will be accepted. The :in option has an alias called :within that you can use for the same purpose, if you'd like to. The previous example uses the :message option to show how you can include the attribute's value.

The default error message for this helper is "is not included in the list".

Length

This helper validates the length of the attributes' values. It provides a variety of options, so you can specify length constraints in different ways:

class User extends Tower.Model
  @validates "name", length: { minimum: 2 }
  @validates "bio", length: { maximum: 500 }
  @validates "password", length: { in: 6..20 }
  @validates "registrationNumber", length: 6

The possible length constraint options are:

:minimum – The attribute cannot have less than the specified length. :maximum – The attribute cannot have more than the specified length. :in (or :within) – The attribute length must be included in a given interval. The value for this option must be a range. :is – The attribute length must be equal to the given value. The default error messages depend on the type of length validation being performed. You can personalize these messages using the :wrongLength, :tooLong, and :tooShort options and %{count} as a placeholder for the number corresponding to the length constraint being used. You can still use the :message option to specify an error message.

class User extends Tower.Model
  @validates "bio", length: { :maximum: 1000, tooLong: "%{count} characters is the maximum allowed" }

This helper counts characters by default, but you can split the value in a different way using the :tokenizer option:

class Essay extends Tower.Model
  @validates "content", length:
    minimum:   300,
    maximum:   400,
    tokenizer: lambda { |str| str.scan(/\w+/) },
    tooShort:  "must have at least %{count} words",
    tooLong:   "must have at most %{count} words"

Note that the default error messages are plural (e.g., "is too short (minimum is %{count} characters)"). For this reason, when :minimum is 1 you should provide a personalized message or use validatesPresenceOf instead. When :in or :within have a lower limit of 1, you should either provide a personalized message or call presence prior to length.

The size helper is an alias for length.

Numericality

This helper validates that your attributes have only numeric values. By default, it will match an optional sign followed by an integral or floating point number. To specify that only integral numbers are allowed set :onlyInteger to true.

If you set :onlyInteger to true, then it will use the

/\A[+-]?\d+\Z/

regular expression to validate the attribute's value. Otherwise, it will try to convert the value to a number using Float.

Note that the regular expression above allows a trailing newline character.

class Player extends Tower.Model
  @validates "points", numericality: true
  @validates "gamesPlayed", numericality: { onlyInteger: true }

Besides onlyInteger, this helper also accepts the following options to add constraints to acceptable values:

:greaterThan – Specifies the value must be greater than the supplied value. The default error message for this option is "must be greater than %{count}". :greaterThanOrEqualTo – Specifies the value must be greater than or equal to the supplied value. The default error message for this option is "must be greater than or equal to %{count}". :equalTo – Specifies the value must be equal to the supplied value. The default error message for this option is "must be equal to %{count}". :lessThan – Specifies the value must be less than the supplied value. The default error message for this option is "must be less than %{count}". :lessThanOrEqualTo – Specifies the value must be less than or equal the supplied value. The default error message for this option is "must be less than or equal to %{count}". :odd – Specifies the value must be an odd number if set to true. The default error message for this option is "must be odd". :even – Specifies the value must be an even number if set to true. The default error message for this option is "must be even". The default error message is "is not a number".

Presence

This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either nil or a blank string, that is, a string that is either empty or consists of whitespace.

class User extends Tower.Model
  @validates "name", "login", "email", presence: true

If you want to be sure that an association is present, you'll need to test whether the foreign key used to map the association is present, and not the associated object itself.

class LineItem extends Tower.Model
  @belongsTo "order"
  @validates "orderId", presence: true

Since false.blank? is true, if you want to validate the presence of a boolean field you should use validates :fieldName, :inclusion: { :in: [true, false] }.

The default error message is "can't be empty".

Uniqueness

This helper validates that the attribute's value is unique right before the object gets saved. It does not create a uniqueness constraint in the database, so it may happen that two different database connections create two records with the same value for a column that you intend to be unique. To avoid that, you must create a unique index in your database.

class Account extends Tower.Model
  @validates "email", uniqueness: true

The validation happens by performing an SQL query into the model's table, searching for an existing record with the same value in that attribute.

There is a :scope option that you can use to specify other attributes that are used to limit the uniqueness check:

class Holiday extends Tower.Model
  validates "name", uniqueness: { scope: "year", message: "should happen once per year" }

There is also a :caseSensitive option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to true.

class User extends Tower.Model
  @validates "name", uniqueness: { caseSensitive: false }

Note that some databases are configured to perform case-insensitive searches anyway.

The default error message is "has already been taken".