Class Lafcadio::DomainObject
In: lafcadio/domain.rb
lafcadio/test.rb
Parent: Object

All classes that correspond to a table in the database need to be children of DomainObject.

Defining fields

There are three ways to define the fields of a DomainObject subclass.

  1. One-line field definitions: When defining the class, add fields with class methods like so:
      class User < Lafcadio::DomainObject
        string 'lastName'
        email  'email'
        string 'password'
        date   'birthday'
      end
    

    These can also be pluralized:

      class User < Lafcadio::DomainObject
        strings 'lastName', 'password'
        email   'email'
        date    'birthday'
      end
    

    The class methods you can use are binary, boolean, date, date_time, domain_object, email, enum, float, integer, month, state, string, text_list. These correspond to BinaryField, BooleanField, DateField, DateTimeField, DomainObjectField, EmailField, EnumField, FloatField, IntegerField, MonthField, StateField, StringField, and TextListField, respectively. Consult their individual RDoc entries for more on how to parameterize them through the class methods.

  2. Overriding DomainObject.get_class_fields: The method should return an Array of instances of ObjectField or its children. If you use this method, make sure to call the superclass (this is needed to define the primary key field). The order of fields in the Array is unimportant. For example:
      class User < DomainObject
        def User.get_class_fields
          fields = super
          fields << StringField.new(self, 'firstName')
          fields << StringField.new(self, 'lastName')
          fields << StringField.new(self, 'email')
          fields << StringField.new(self, 'password')
          fields << DateField.new(self, 'birthday')
          fields
        end
      end
    

    This method is probably not worth the trouble unless you end up defining your own field classes for some reason.

  3. Defining fields in an XML file: Note that this method may be removed in the future. To do this:
    1. Set one directory to contain all your XML files, by setting classDefinitionDir in your LafcadioConfig file.
    2. Write one XML file per domain class. For example, a User.xml file might look like:
        <lafcadio_class_definition name="User">
          <field name="lastName" class="StringField"/>
          <field name="email" class="StringField"/>
          <field name="password" class="StringField"/>
          <field name="birthday" class="DateField"/>
        </lafcadio_class_definition>
      

Setting and retrieving fields

Once your fields are defined, you can create an instance by passing in a hash of field names and values.

  john = User.new(
    :firstName => 'John', :lastName => 'Doe',
    :email => 'john.doe@email.com', :password => 'my_password',
    :birthday => Date.new( 1979, 1, 15 )
  )

You can read and write these fields like normal instance attributes.

  john.email # => 'john.doe@email.com'
  john.email = 'john.doe@mail.email.com'

If your domain class has fields that refer to other domain classes, or even to another row in the same table, you can use a DomainObjectField to express the relation.

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

  msg = Message.new(
    :subject => 'hi there',
    :body => 'You wanna go to the movies on Saturday?',
    :author => john, :recipient => jane, :dateSent => Date.today
  )

pk_id and committing

Lafcadio requires that each table has a numeric primary key. It assumes that this key is named pk_id in the database, though that can be overridden.

When you create a domain object by calling DomainObject.new, you should not assign a pk_id to the new instance. The pk_id will automatically be set when you commit the object by calling DomainObject#commit.

However, you may want to manually set pk_id when setting up a test case, so you can ensure that a domain object has a given primary key.

Naming assumptions, and how to override them

By default, Lafcadio assumes that every domain object is indexed by the field pk_id in the database schema. If you’re dealing with a table that uses a different field name, call DomainObject.sql_primary_key_name. However, you will always use pk_id in your Ruby code.

Lafcadio assumes that a domain class corresponds to a table whose name is the pluralized, lower-case, underscored version of the class name. A User class is assumed to be stored in a "users" table, while a ProductCategory class is assumed to be stored in a "product_categories" table. Call DomainObject.table_name to override this behavior.

  class LegacyThing < Lafcadio::DomainObject
    string 'some_field'
    sql_primary_key_name 'some_legacy_id'
    table_name 'some_legacy_table'
  end
  thing = LegacyThing[9909]
  thing.pk_id # => 9909

Inheritance

Domain classes can inherit from other domain classes; they have all the fields of any concrete superclasses plus any new fields defined for themselves. You can use normal inheritance to define this:

  class User < Lafcadio::DomainObject
    ...
  end

  class Administrator < User
    ...
  end

Lafcadio assumes that each concrete class has a corresponding table, and that each table has a pk_id field that is used to match rows between different levels.

Methods

Included Modules

DomainComparable

Classes and Modules

Class Lafcadio::DomainObject::ReadOnlyHash

Attributes

delete  [R] 
field_values  [RW] 
fields_set  [RW] 
last_commit_type  [RW] 

Public Class methods

Shortcut method for retrieving one domain object.

  User[7356] # => user with the pk_id 7536

Returns every committed instance of the domain class.

  User.all # => all users

Commits and returns a custom mock object of the given domain class. All the field values are set to defaults, except for the fields passed in through custom_args. This mock object will have a pk_id greater than 1, and each successive call to DomainObject.custom_mock will return an object with a unique pk_id.

This class method is only visible if you include lafcadio/test.rb.

  class User < Lafcadio::DomainObject
    strings :fname, :lname, :email
  end
  u1 = User.custom_mock
  u1.fname # => 'test text'
  u1.lname # => 'test text'
  u1.email # => 'test text'
  u1.pk_id # => probably 2, guaranteed to be greater than 1
  u2 = User.custom_mock( 'fname' => 'Francis', 'lname' => 'Hwang' )
  u2.fname # => 'Francis'
  u2.lname # => 'Hwang'
  u2.email # => 'test text'
  u2.pk_id # => probably 3, guaranteed to not be u1.pk_id and to be
           #    greater than 1

Returns a hash of default arguments for mock instances of this domain class. DomainObject.default_mock uses exactly these arguments to create the default mock for a given domain class, and DomainObject.custom_mock overrides some of the field arguments based on its custom arguments.

By default this will retrieve simple values based on the field type:

You can override this method, if you like. However, it will probably be simpler just to call DomainObject.mock_value.

This class method is only visible if you include lafcadio/test.rb.

Sets a default setup hash for a field of a certain class. Useful for mapping domain classes with lots of fields and unusual field configurations.

  class LotsOfBooleans < Lafcadio::DomainObject
    default_field_setup_hash(
      Lafcadio::BooleanField, { 'enum_type' => :capital_yes_no }
    )
    booleans 'this', 'that', 'the_other', 'and_another_one',
             'this_one_too'
  end

Commits and returns a mock object of the given domain class. All the field values are set to defaults. This mock object will have a pk_id of 1. Successive calls to DomainObject.default_mock will always return the same mock object.

This class method is only visible if you include lafcadio/test.rb.

  class User < Lafcadio::DomainObject
    strings :fname, :lname, :email
  end
  u1 = User.default_mock
  u1.fname # => 'test text'
  u1.lname # => 'test text'
  u1.email # => 'test text'
  u1.pk_id # => 1

Tests whether a given domain object exists.

  User.exist?( 8280 ) # => returns true iff there's a User 8280
  User.exist?( 'Hwang', :lastName ) # => returns true iff there's a User
                                    #    with the last name 'Hwang'

Returns the first domain object it can find.

  very_first_user = User.first

This class method has a few uses, depending on how you use it.

  1. Pass DomainObject.get a block in order to run a complex query:
      adult_hwangs = User.get { |user|
        user.lastName.equals( 'Hwang' ) &
          user.birthday.lte( Date.today - ( 365 * 18 ) )
      }
    

    See query.rb for more information about how to form these queries.

  2. Pass it a simple search term and a field name to retrieve every domain object matching the term. The field name will default to pk_id.
      hwangs = User.get( 'Hwang', :lastName )
      user123 = User.get( 123 ).first
    
  3. Pass it a { :group => :count } hash to retrieve a count result hash. This interface is fairly new and may be refined (that is, changed) in the future.
      num_users = User.get( :group => :count ).first[:count]
    

Returns an Array of ObjectField instances for this domain class, parsing them from XML if necessary. You can override this to define a domain class’ fields, though it will probably just be simpler to use one-line class methods instead.

Returns the last domain object.

  last_msg = Message.last

Sets the mock value for the given field. These mock values are used in DomainObject.default_mock and DomainObject.custom_mock

This class method is only visible if you include lafcadio/test.rb.

  class User < Lafcadio::DomainObject
    strings :fname, :lname, :email
  end
  User.mock_value :fname, 'Bill'
  User.mock_value :lname, 'Smith'
  u1 = User.default_mock
  u1.fname # => 'Bill'
  u1.lname # => 'Smith'
  u1.email # => 'test text'
  u1.pk_id # => 1

Sets the mock value for the fields in hash. These mock values are used in DomainObject.default_mock and DomainObject.custom_mock

This class method is only visible if you include lafcadio/test.rb.

  class User < Lafcadio::DomainObject
    strings :fname, :lname, :email
  end
  User.mock_values { :fname => 'Bill', :lname => 'Smith' }
  u1 = User.default_mock
  u1.fname # => 'Bill'
  u1.lname # => 'Smith'
  u1.email # => 'test text'
  u1.pk_id # => 1

field_hash should contain key-value associations for the different fields of this domain class. For example, instantiating a User class might look like:

  User.new(
    'firstNames' => 'John', 'lastName' => 'Doe',
    'email' => 'john.doe@email.com', 'password' => 'l33t'
  )

In normal usage any code you write that creates a domain object will not define the pk_id field. The system assumes that a domain object with an undefined pk_id has yet to be inserted into the database, and when you commit the domain object a pk_id will automatically be assigned.

If you’re creating mock objects for unit tests, you can explicitly set the pk_id to represent objects that already exist in the database.

Returns the only committed instance of the domain class. Will raise an IndexError if there are 0, or more than 1, domain objects.

  the_one_user = User.only

If set_db_field_name is nil, this will return the sql name of the primary key. If set_db_field_name isn’t nil, it will set the sql name.

  class User < Lafcadio::DomainObject
    string 'firstNames'
  end
  User.sql_primary_key_name # => 'pk_id'
  class User < Lafcadio::DomainObject
    sql_primary_key_name 'some_other_id'
  end
  User.sql_primary_key_name # => 'some_other_id'

If set_table_name is nil, DomainObject.table_name will return the table name. Lafcadio assumes that a domain class corresponds to a table whose name is the pluralized, lower-case, underscored version of the class name. A User class is assumed to be stored in a "users" table, while a ProductCategory class is assumed to be stored in a "product_categories" table.

If set_table_name is not nil, this will set the table name.

  class User < Lafcadio::DomainObject
    string 'firstNames'
  end
  User.table_name # => 'users'
  class User < Lafcadio::DomainObject
    table_name 'some_table'
  end
  User.table_name # => 'some_table'

Public Instance methods

Returns a clone, with all of the fields copied.

Commits this domain object to the database.

Deletes a domain object, committing the delete to the database immediately.

Set the delete value to true if you want this domain object to be deleted from the database during its next commit.

Returns the subclass of DomainObject that this instance represents. Because of the way that proxying works, clients should call this method instead of Object.class.

This template method is called after every commit. Subclasses can override it to ensure code is executed after a commit.

This template method is called before every commit. Subclasses can override it to ensure code is executed before a commit.

Updates a domain object and commits the changes to the database immediately.

  user99 = User[99]
  user99.update!( :firstNames => 'Bill', :password => 'n3wp4ssw0rd' )

If you’re running against a MockObjectStore, this will verify each field and raise an error if there’s any invalid fields.

[Validate]