NoBrainer

A Ruby ORM for RethinkDB

Fields

Field Declaration

With NoBrainer, persisted model attributes are called fields or attributes interchangeably in the documentation. Declaring a field is done by using the field method. For example, the following defines a User model with a first and last name:

class User
  include NoBrainer::Document

  field :first_name
  field :last_name
end

field accepts the following options:

  • :index to specify an index.
  • :default to specify a default value.
  • :type to enforce a type.
  • :readonly to specify if a field cannot be updated.
  • :primary_key to specify a custom primary key.
  • :store_as to specify an alias in the database.
  • :lazy_fetch to specify whether this field should be fetched on demand.

field also accepts the following validation options:

  • :validates to specify validations.
  • :required as a shorthand for the presence validation (except with Boolean types, for which the not_null validation is used).
  • :uniq (or :unique) as a shorthand for the uniqueness validation.
  • :format as a shorthand for the format validation.
  • :in as a shorthand for the inclusion validation.
  • :length as a shorthand for the length validation.
  • :min_length as a shorthand for the minimum length validation.
  • :max_length as a shorthand for the maximum length validation.

Accessing Fields

Defined fields can be accessed with the following methods:

Reading an attribute attr:

  • self.attr
  • self.read_attribute(attr)
  • self[attr]

Writing an attribute attr:

  • self.attr = value
  • self.write_attribute(attr, value)
  • self[attr] = value

Reading all attributes:

  • self.attributes: returns attributes. read_attribute() is used to compute the values.
  • self.inspectable_attributes: returns internal attributes used for debugging purposes.

Mass assignment:

  • self.assign_attributes(attrs_hash)

Methods lower in the list calls the method directly above it. For example, self[attr] calls read_attribute(attr) which calls self.attr.

Note that there is no attr_protected method to control mass assignments. When passing raw params from Rails controller to NoBrainer, an ActiveModel::ForbiddenAttributesError is raised. params must be sanitized with strong parameters.

Overriding attributes

If you wish to override an attribute getter or setter, you may define the attr and attr= methods in your class. super can be used as usual. The following shows an example that uses super in the setter:

class User
  include NoBrainer::Document

  field :email

  def email=(value)
    super(value.strip.downcase)
  end
end

Note that the setters are not used when reading a document from the database. Keep this in mind when your database does not match your schema.

Accessing raw attributes

self._read_attribute(attr) and self._write_attribute(attr, value) access internal attributes, bypassing any type checking. These methods should not be used for regular use. Do not use these methods when overriding getters/setters (see above).

Default Values

To assign a default value to a field, you may pass a default option. You can pass a value or a lambda. The latter will be evaluated at the time of the assignment in the context of the document, which is useful to set values depending on other already set attributes.

field :num_friends, :default => 0
field :created_at,  :default => ->{ Time.now }

Defaults values are assigned whenever a model is instantiated in memory, which happens when Model.new is called. Reading a model from the database calls Model.new and therefore performs default value assignments.

A default value is only assigned when the corresponding attribute has not been set. For example, calling Model.create(:created_at => nil) will not trigger the default value assignment on created_at. Please create a GitHub issue if this behavior is a problem for you.

Note that specifying a default value at some point in time does not apply the default value to existing documents in the database. Existing documents must be manually migrated.

Readonly Fields

When declaring a field with :readonly => true, the field cannot be reassigned once persisted to the database.

Primary Key

NoBrainer allows custom primary keys with the :primary_key => true option. The default primary key is id and has a format that matches [A-Za-z0-9]{14}. It has interesting property compared to UUIDs because these IDs are monotonically increasing with time. NoBrainer always sort by primary key by default to give predicable and repeatable results. For example, Model.last yields the latest created model, which can be quite handy in development mode.

When comparing two models with == or eql?, only the primary keys are compared, not the other attributes.

Specifying a custom primary key changes the default foreign key names in belongs_to associations.

Dynamic Attributes

Dynamic attributes are supported by NoBrainer, but are not enabled by default. You must include the NoBrainer::Document::DynamicAttributes mixin in your model.

By doing so, you will be able to read/write arbitrary attributes to your model with read_attribute()/ [] and write_attribute()/[]=.

Types

Field types can be declared as such:

field :email, :type => String

Read the Types section to learn more.

Validations

Validations can be declared directly on the field declaration:

field :email, :validates => { :format => { :with => /@/ } }

Read the Validations section to learn more.

Indexes

A index can be declared on a field as such:

field :email, :index => true

Read the indexes section to learn more.

Aliases

An alias can be specified on a given field as such:

field :email, :store_as => :e

NoBrainer will translate all the references to that field when compiling queries and reading models back from the database.

A simple index declared on an aliased field carries the name of alias in the database, unless specified otherwise by an :store_as option on the index.

Warning: When passing raw RQL to NoBrainer, aliases do not get translated.

Lazy Fetching

Some fields can have large content size, for example binary fields. It might be undesirable to fetch them all the time. NoBrainer can fetch certain fields on demand by declaring a field to be lazy fetched. For example:

class User
  field :email,  :type => String
  field :avatar, :type => Binary, :lazy_fetch => true
end

user = User.first
user.email  # In memory access.
user.avatar # Performs an extra query to fetch the data.

Virtual Attributes

NoBrainer can merge RQL values which appear as regular attributes on the model instance. Using virtual_field allows to specify a custom RQL expression that will be evaluated by the database when performing queries.

The following example adds a current_user_following virtual attribute to a User model that returns whether the current user follows that user or not.

class User
  virtual_field :current_user_following do |doc|
    if Thread.current[:current_user]
      Followship.where(:follower_id => doc[:id], :following_id => Thread.current[:current_user].id).to_rql.is_empty.not
    end
  end
end

Reflection

You can access the field definitions with Model.fields.
It returns a hash of the form {:field_name => options}.

You can undefine a previously defined field with Model.remove_field(field_name). This feature is needed when removing the default primary key id for example.

You may access the name of the current primary key with Model.pk_name. You may access the primary key value of a document with instance.pk_value.