By default, Quick supports basic component-level inheritance of entities, meaning that a child component inherits the properties ( and ability to overload ) its parent. A common, object-oriented relational database pattern, however is to provide additional definition on parent tables ( and classes ) within child tables which contain a foreign key.
Quick supports two types of child classes: Discriminated and Subclassed child entities. In both cases, loading any child class will also deliver the data of its parent class.
Let's say, for example, that I have a Media
entity, which is used to catalog and organize all media items loaded in to my application.
My Media
entity contains all of the properties which are common to every media item uploaded in to my application. Let's say, however, that I need to have specific attributes that are available on only media for my Book
entity ( e.g. whether the image is the cover photo, for example ). I can create a child class of BookMedia
which extends my Media
entity. When loaded, all of the properties of Media
will be loaded along with the custom attributes which apply to my BookMedia
object:
Note the additional component attribute joincolumn
. The presence of this attribute on a child class signifies that it is a child entity of the parent and that the parent's properties should be loaded whenever the BookMedia
entity is loaded. In addition, the primary key of the entity is that of the parent. Child entities can be retrieved by queries specific to their own properties:
Or properties on the parent class can be used as first-class properties within the query:
Child entities can be retreived, individually, using the value of the joinColumn
, which should be a foreign key to the parent identifier column:
Now my Book
entity can use its extended media class to retreive media items which are specific to its own purpose:
A discriminated child class functions, basically, in the same way as a subclassed entity, with one exception: The parent entity is aware of the discriminated child, due to a discriminatorValue
attribute and will return that specific class when a retreival is performed through the parent Entity. Let's take our BookMedia
class, again, but this time defining it as a discriminated entity.
The first step is to add the discriminatorColumn
attribute to the Media
entity:
Then we set a discriminatorValue
property on the child class, the value of which is stored in the parent entity table:
Once this is defined, any new BookMedia
entity will be saved with a type
value of book
in the media
table. As such, the following query will result in only entities of BookMedia
being returned:
If our Media
table contains a combination of non-book and book media, then the collection returned when querying all records will contain a mix of BookMedia
and Media
entities.
Loading a collection of BookMedia
entities, however, will always return a collection of BookMedia
entities, because the type
column value on the media
must be equal to book
.
Discriminated and child class entities, allow for a more Object oriented approach to entity-specific relationships by allowing you to eliminate pivot/join tables and extend the attributes of the base class.
To get started with Quick, you need an entity. You start by extending quick.models.BaseEntity
.
That's all that is needed to get started with Quick. There are a few defaults of Quick worth mentioning here.
You can generate Quick entities from CommandBox! Install quick-commands
and use quick entity create
to get started!
We don't need to tell Quick what table name to use for our entity. By default, Quick uses the pluralized name of the component for the table name. That means for our User
entity Quick will assume the table name is users
. You can override this by specifying a table
metadata attribute on the component.
For more information on using inheritance and child tables in your relational database model, see Subclass Entities.
By default, Quick assumes a primary key of id
. The name of this key can be configured by setting variables._key
in your component.
Quick also assumes a key type that is auto-incrementing. If you would like a different key type, override thekeyType
function and return the desired key type from that function.
Quick ships with the following key types:
AutoIncrementingKeyType
NullKeyType
ReturningKeyType
UUIDKeyType
keyType
can be any component that adheres to the keyType
interface, so feel free to create your own and distribute them via ForgeBox.
You specify what attributes are retrieved by adding properties to your component.
Now, only the id
, username
, and email
attributes will be retrieved.
Make sure to include the primary key (id
by default) as a property.
To prevent Quick from mapping a property to a database column add the persistent="false"
attribute to the property. This is needed mostly when using dependency injection.
If the column name in your table is not the column name you wish to use in Quick, you can specify the column name using the column
metadata attribute. The attribute will be available using the name
of the attribute.
To work around CFML's lack of null
, you can use the nullValue
and convertToNull
attributes.
nullValue
defines the value that is considered null
for a attribute. By default it is an empty string. (""
)
convertToNull
is a flag that, when false, will not try to insert null
in to the database. By default this flag is true
.
The readOnly
attribute will prevent setters, updates, and inserts to a attribute when set to true
.
In some cases you will need to specify an exact SQL type for your attribute. Any value set for the sqltype
attribute will be used when inserting or updating the attribute in the database. It will also be used when you use the attribute in a where
constraint.
The casts
attribute allows you to use a value in your CFML code as a certain type while being a different type in the database. A common example of this is a boolean
which is usually represented as a BIT
in the database.
Two casters ship with Quick: BooleanCast@quick
and JsonCast@quick
. You can add them using those mappings to any applicable columns.
The casts
attribute must point to a WireBox mapping that resolves to a component that implements the quick.models.Casts.CastsAttribute
interface. (The implements
keyword is optional.) This component defines how to get
a value from the database in to the casted value and how to set
a casted value back to the database. Below is an example of the built-in BooleanCast
, which comes bundled with Quick.
Casted values are lazily loaded and cached for the lifecycle of the component. Only cast values that have been loaded will have set
called on them when persisting to the database.
Casts can be composed of multiple fields as well. Take this Address
value object, for example:
This component is not a Quick entity. Instead it represents a combination of fields stored on our User
entity:
Noticed that the casted address
is neither persistent
nor does it have a getter
or setter
created for it.
The last piece of the puzzle is our AddressCast
component that handles casting the value to and from the native database values:
You can see that returning a struct of values from the set
function assigns multiple attributes from a single cast.
You can prevent inserting and updating a property by setting the insert
or update
attribute to false
.
Quick handles formula, computed, or subselect properties using query scopes and the addSubselect
helper method. Check out the docs in query scopes to learn more.
Quick uses a default datasource and default grammar, as described here. If you are using multiple datasources you can override default datasource by specifying a datasource
metadata attribute on the component. If your extra datasource has a different grammar you can override your grammar as well by specifying a grammar
attribute.
At the time of writing Valid grammar options are: MySQLGrammar@qb
, PostgresGrammar@qb
, SqlServerGrammar@qb
and OracleGrammar@qb
. Please check the qb docs for additional options.
You can compare entities using the isSameAs
and isNotSameAs
methods. Each method takes another entity and returns true
if the two objects represent the same entity.