Skip to content

Active Row

Ke edited this page May 11, 2015 · 34 revisions

activerow is a hybrid implementation of the Active Record design pattern. Unlike ds_row which typically represents an existing row, activerow may not yet exist as a row in the data source. It can either be used stand alone or as the parent for your own types — it expands on the functionality of ds_row.

Rows returned from ds can be cast as activerow types (or other types based on it) like so:

	with row #ds->rows(::activerow) do { 
	    #row->isa(::activerow) // true
	}

Or:

	with row in #ds->rows(::product) do { // product inherits from activerow
	    #row->isa(::activerow) // true
	}

This allows you to work with your own type definitions in an OOP fashion with minimal overhead and is one of the key strengths of the Datasource suite of tools.

You can also create a new row / object like so:

	local(product) = activerow(::store.product)

	#product->save(
		::brand       = 'ACME',
		::description = 'Example Product'
		::price       = 9.95
	)

Finally you can specify a memberstream when returning rows — although you will need to specify an ->oncreate method that accepts ds_row as the first parameter:

	with product in result->rows(\product) do {
		#product->isa(::activerow) // true
	}

##Creators

The activerow type can be created with the following creators:

-> oncreate(database.table::tag,...optionalkeys)
-> oncreate(ds::ds,...optionalkeys)
-> oncreate(row::ds_row)

-> oncreate(keyvalue::integer)
-> oncreate(keyvalue::string)
-> oncreate(keyvalues::pair,...morepairs)

####Configuration via oncreate

Alternatively you can specific the database and table with the oncreate methods like so:

	local(product) = activerow(::store.product,#id)

Or:

	local(product) = activerow(::store.product,::uuid = #uuid)

Or:

	local(product) = activerow(product_ds,::uuid = #uuid)

####Using oncreate within your own types

Within your own types you can also leverage the oncreate methods:

	define product => type {
	   parent activerow
	   public oncreate(id::integer=0) => ..oncreate(::store.product,#id)
	}

Or:

	define product => type {
	   parent activerow
	   public oncreate(...keys) => ..oncreate(::store.product,#keys)
	}

Or using a ds definition:

	define product => type {
	   parent activerow
	   public oncreate(mpn::string) => ..oncreate(product_ds,::mpn = #mpn)
	}

And finally a complete catch all:

	define product => type {
	   parent activerow
	   public oncreate(...) => ..oncreate(:#rest)
	}

####Configuration via Data Members & Methods

If you prefer you can configure you sub-types by providing either the database name or ds definition via .database and .ds respectively. When using either approach you can also specify the table with .table — by default a pluralised version of your type will be used for the table name.

	define product => type {
	   parent activerow
	   public ds => ds(::store.product)
	}

Or:

	define product => type {
	    parent activerow
	    data
		public database = 'store',
		public table = 'products'
	}

When .table is blank activerow will use the types name suffixed with "s" as the table — you can change this with the following code:

define activerow_pluralise_tables => false

##Accessing row data — activerow

Data can be accessed in the same fashion as ds_row with the added benefit of being able leverage your own methods defined in your types. This allows you to format the data in a particular way or create another accessor for the data.

Return the value of the specified column:

(column::tag)
-> find(column::tag)
-> find(column::string)

#activerow(::mycolumn)
#activerow->find('mycolumn')

When used in your own types you can create one to one methods like so:

public mycolumn => .find(::mycolumn)

Or you can use methods with different names like so:

public firstname => .find(::user_firstname)
public lastname  => .find('user_lastname') // also valid

Bringing it all together:

public qty => .find(::item_qty)
public price => .find(::item_price)
public subtotal => .price * .qty

##Modifying row data — activerow

####(column::tag) = value, (column::string) = value Updates internal data — does not write to data source.

#activerow(::mycol) = 'Value'
#activerow('mycol') = 'Value'

####-> updatedata -> updatedata(data::trait_foreachpair)
-> updatedata(p::pair,...)

Updates internal data only — does not write to data source.

#activerow->updatedata(map('price' = 9.95, 'qty' = 3))

####-> update -> update(data::trait_foreachpair)
-> update(p::pair,...)

Updates internal data and writes to data source.

#activerow->update(::price = 9.95, ::qty = 3)

// Or
#activerow->update(
    map(
        'price' = 9.95, 
        'qty' = 3
    )
)

####-> set -> set(p::pair)
-> set(column::tag) = value
-> set(column::string) = value

Update internal data and writes specified value to the data source.

// These all have the same effect
#activerow->set(::mycol) = 'Value'
#activerow->set('mycol') = 'Value'

// Supplied as a pair
#activerow->set(::mycol = 'Value')
#activerow->set('mycol' = 'Value')

####-> create Creates new row in the data source and assigns the newly created row to the type.

####-> save Either updates or creates a row depending if it's new or not (based on keyvalue).

####-> delete Clears row data and deletes the row from the data source.

####-> revert Reverts unsaved changes.

Other activerow methods

####-> isnew Returns true if new.

####-> asnew Returns copy of the activerow with a null key value (considered new).

##Changing activerow behavior

You can change the default behaviour of the above by redefining them in your type. For example you may want to perform additional tasks on save or disable deleting by replacing it with an update.

	define example => type {
		parent activerow

		// Extend default save method
		public save => {
			// Do something else
			log(.type + ' saved by ' + current_user->name)

			// Now actually save
			..save
		}

		// Override default delete method
		public delete => .update(::status = -1)
	}