Chapter 4. Matching database tables to domain classes

Table of Contents

Defining domain class fields
binary
boolean
date
date_time
domain_object
email
enum
float
integer
month
state
string
text_list
Naming assumptions, and how to override them
Domain class inheritance

Defining domain class fields

Lafcadio takes a Ruby-centric approach to object-relational mapping, as opposed to a database-centric approach. That means it relies on information in the Ruby code, as opposed to a live database schema, to tell it what fields tables have. The tradeoffs to this are significant. On one hand, Lafcadio has a test mode that runs in-memory, and in tandem with this test mode, defining domain classes fully in-Ruby means that you can write and test an application using Lafcadio without even having a database installed. On the other hand, there's definitely more typing up-front, as you have to define individual fields in Lafcadio classes.

Domain class fields are set with one-line class methods which look something like:

class Message < Lafcadio::DomainObject
  string        'subject'
  string        'body'
  domain_object User, 'author'
  domain_object User, 'recipient'
  date          'date_sent'
end

Note

Earlier versions of Lafcadio involved overriding DomainObject.get_class_fields and writing XML configuration files to determine fields. Such methods of specifying domain class fields are deprecated and may break in future releases.

When specifying fields, additional arguments can be passed this way:

class Message < Lafcadio::DomainObject
  string 'subject', { 'not_nil' => false }
  string 'body', { 'db_field_name' => 'b' }
end

The following arguments apply across all field types:

  • db_field_name: By default, fields are assumed to have the same name in the database, but you can override this assumption using db_field_name.
  • not_nil: This is true by default. Set it to false to avoid checking for nil values in tests. (For more on testing, see the "Testing" chapter below.)

Every field can be specified using the pluralized method as well:

class User < Lafcadio::DomainObject
	strings    'fname', 'lname'
	date_times 'created', 'modified'
end

Many of the fields map directly to common types of fields in SQL databases, but a few are also specialized extensions that might be convenient. The fields are:

binary

Maps to a binary field in the database. This acts fairly the same as string, below.

boolean

A field representing a boolean value. By default, it assumes that the table field represents true and false with the integers 1 and 0. To change this default, pass a hash with the keys true and false:

class User < Lafcadio::DomainObject
  boolean 'administrator', { 'enums' => { true => 'yin', false => 'yang' } }
end

date

A field representing a date.

date_time

A field representing a time.

domain_object

A field representing a relation to another domain object. To add such an association in a class definition, call DomainObject.domain_object:

class Invoice < Lafcadio::DomainObject
  domain_object Client
end

Note that the syntax domain_object differs from that of other field definitions, in that the argument passed is a domain class, not a field name. By default, the field name is assumed to be the same as the class name, only lower-cased and camel-case.

class LineItem < Lafcadio::DomainObject
  domain_object Product         # field name 'product'
  domain_object CatalogOrder    # field name 'catalog_order'
end

The field name can be explicitly set as the 2nd argument of DomainObject.domain_object.

class Message < Lafcadio::DomainObject
  domain_object User, 'sender'
  domain_object User, 'recipient'
end

Setting delete_cascade to true means that if the domain object being associated to is deleted, this domain object will also be deleted.

class Invoice < Lafcadio::DomainObject
  domain_object Client, 'client', { 'delete_cascade' => true }
end
cli = Client.new( 'name' => 'big company' ).commit
inv = Invoice.new( 'client' => cli ).commit
cli.delete!
inv_prime = Invoice[inv.pk_id] # => will raise DomainObjectNotFoundError

email

email takes a text value that is expected to be formatted as a single valid email address. It acts the same as text in production code, but if you're validating field types in tests, this will check to ensure it's a valid email address. See the "Testing" chapter for more on validating domain object fields.

enum

enum represents an enumerated field that can only be set to one of a set range of string values. To set the enumeration, pass in an Array of values as "enums". These enumerations are tested against during field validation.

class IceCream < Lafcadio::DomainObject
  enum 'flavor', { 'enums' => %w( Vanilla Chocolate Lychee ) }
end

float

float represents a Float value.

integer

integer represents an Integer value.

month

Accepts a Month as a value. (This convenience class is imported from the Ruby Month library.) Values of this field type will be saved in the database as the first day of the month.

state

A special enum field whose possible values are any of the 50 states of the United States, stored as each state's two-letter postal code.

string

Contains a String value.

text_list

Maps to any String SQL field that tries to represent a quick-and-dirty list with a comma-separated string. It returns an Array. For example, a SQL field with the value "john,bill,dave", then the Ruby field will have the value [ "john", "bill", "dave" ].