Add properties to existing model files.
Synopsis
Copy wheels generate property [model] [properties] [options]
wheels g property [model] [properties] [options]
Description
The wheels generate property
command adds new properties to existing model files. It can add simple properties, associations, calculated properties, and validations while maintaining proper code formatting and structure.
Arguments
Argument
Description
Default
Model name to add properties to
Property definitions (name:type:options)
Options
Generate migration for database changes
Generate property callbacks
Overwrite without confirmation
Property Syntax
Copy propertyName:type:option1:option2
Supported Types
Property Options
unique
- Unique constraint
default=value
- Default value
limit=n
- Character limit
precision=n
- Decimal precision
Examples
Add single property
Copy wheels generate property user email:string:required:unique
Add multiple properties
Copy wheels generate property product "sku:string:required:unique price:float:required stock:integer:default=0"
Add text property with validation
Copy wheels generate property post content:text:required:limit=5000
Add association
Copy wheels generate property order userId:integer:required:belongsTo=user
Add calculated property
Copy wheels generate property user "fullName:calculated"
Generated Code Examples
Basic Property Addition
Before:
Copy component extends="Model" {
function init() {
// Existing code
}
}
After:
Copy component extends="Model" {
function init() {
// Existing code
// Properties
property(name="email", sql="email");
// Validations
validatesPresenceOf(properties="email");
validatesUniquenessOf(properties="email");
validatesFormatOf(property="email", regEx="^[^@\s]+@[^@\s]+\.[^@\s]+$");
}
}
Multiple Properties
Command:
Copy wheels generate property product "name:string:required description:text price:float:required:default=0.00 inStock:boolean:default=true"
Generated:
Copy component extends="Model" {
function init() {
// Properties
property(name="name", sql="name");
property(name="description", sql="description");
property(name="price", sql="price", default=0.00);
property(name="inStock", sql="in_stock", default=true);
// Validations
validatesPresenceOf(properties="name,price");
validatesNumericalityOf(property="price", allowBlank=false, greaterThanOrEqualTo=0);
}
}
Association Property
Command:
Copy wheels generate property comment "userId:integer:required:belongsTo=user postId:integer:required:belongsTo=post"
Generated:
Copy component extends="Model" {
function init() {
// Associations
belongsTo(name="user", foreignKey="userId");
belongsTo(name="post", foreignKey="postId");
// Properties
property(name="userId", sql="user_id");
property(name="postId", sql="post_id");
// Validations
validatesPresenceOf(properties="userId,postId");
}
}
Calculated Property
Command:
Copy wheels generate property user fullName:calculated --callbacks
Generated:
Copy component extends="Model" {
function init() {
// Properties
property(name="fullName", sql="", calculated=true);
}
// Calculated property getter
function getFullName() {
return this.firstName & " " & this.lastName;
}
}
Migration Generation
When --migrate=true
(default), generates migration:
Migration File
db/migrate/[timestamp]_add_properties_to_[model].cfc
:
Copy component extends="wheels.migrator.Migration" hint="Add properties to product" {
function up() {
transaction {
addColumn(table="products", columnName="sku", columnType="string", limit=50, null=false);
addColumn(table="products", columnName="price", columnType="decimal", precision=10, scale=2, null=false, default=0.00);
addColumn(table="products", columnName="stock", columnType="integer", null=true, default=0);
addIndex(table="products", columnNames="sku", unique=true);
}
}
function down() {
transaction {
removeIndex(table="products", columnNames="sku");
removeColumn(table="products", columnName="stock");
removeColumn(table="products", columnName="price");
removeColumn(table="products", columnName="sku");
}
}
}
Validation Rules
Automatic Validations
Based on property type and options:
validatesPresenceOf, validatesLengthOf
validatesFormatOf with email regex
validatesNumericalityOf(onlyInteger=true)
validatesInclusionOf(list="true,false,0,1")
validatesFormatOf with date pattern
Custom Validations
Add custom validation rules:
Copy wheels generate property user "age:integer:min=18:max=120"
Generated:
Copy validatesNumericalityOf(property="age", greaterThanOrEqualTo=18, lessThanOrEqualTo=120);
Property Callbacks
Generate with callbacks:
Copy wheels generate property user lastLoginAt:datetime --callbacks
Generated:
Copy function init() {
// Properties
property(name="lastLoginAt", sql="last_login_at");
// Callbacks
beforeUpdate("updateLastLoginAt");
}
private function updateLastLoginAt() {
if (hasChanged("lastLoginAt")) {
// Custom logic here
}
}
Complex Properties
Enum-like Property
Copy wheels generate property order "status:string:default=pending:inclusion=pending,processing,shipped,delivered"
Generated:
Copy property(name="status", sql="status", default="pending");
validatesInclusionOf(property="status", list="pending,processing,shipped,delivered");
File Upload Property
Copy wheels generate property user "avatar:string:fileField"
Generated:
Copy property(name="avatar", sql="avatar");
// In the init() method
afterSave("processAvatarUpload");
beforeDelete("deleteAvatarFile");
private function processAvatarUpload() {
if (hasChanged("avatar") && isUploadedFile("avatar")) {
// Handle file upload
}
}
JSON Property
Copy wheels generate property user "preferences:text:json"
Generated:
Copy property(name="preferences", sql="preferences");
function getPreferences() {
if (isJSON(this.preferences)) {
return deserializeJSON(this.preferences);
}
return {};
}
function setPreferences(required struct value) {
this.preferences = serializeJSON(arguments.value);
}
Property Modifiers
Encrypted Property
Copy wheels generate property user "ssn:string:encrypted"
Generated:
Copy property(name="ssn", sql="ssn");
beforeSave("encryptSSN");
afterFind("decryptSSN");
private function encryptSSN() {
if (hasChanged("ssn") && Len(this.ssn)) {
this.ssn = encrypt(this.ssn, application.encryptionKey);
}
}
private function decryptSSN() {
if (Len(this.ssn)) {
this.ssn = decrypt(this.ssn, application.encryptionKey);
}
}
Slugged Property
Copy wheels generate property post "slug:string:unique:fromProperty=title"
Generated:
Copy property(name="slug", sql="slug");
validatesUniquenessOf(property="slug");
beforeValidation("generateSlug");
private function generateSlug() {
if (!Len(this.slug) && Len(this.title)) {
this.slug = createSlug(this.title);
}
}
private function createSlug(required string text) {
return reReplace(
lCase(trim(arguments.text)),
"[^a-z0-9]+",
"-",
"all"
);
}
Batch Operations
Copy wheels generate property user "
profile.bio:text
profile.website:string
profile.twitter:string
profile.github:string
" --nested
Add Timestamped Properties
Copy wheels generate property post "publishedAt:timestamp deletedAt:timestamp:nullable"
Integration with Existing Code
Preserve Existing Structure
The command intelligently adds properties without disrupting:
Conflict Resolution
Copy wheels generate property user email:string
> Property 'email' already exists. Options:
> 1. Skip this property
> 2. Update existing property
> 3. Add with different name
> Choice:
Best Practices
Add properties incrementally
Always generate migrations
Include appropriate validations
Use semantic property names
Add indexes for query performance
Consider default values carefully
Document complex properties
Common Patterns
Soft Delete
Copy wheels generate property model deletedAt:timestamp:nullable
Versioning
Copy wheels generate property document "version:integer:default=1 versionedAt:timestamp"
Status Tracking
Copy wheels generate property order "status:string:default=pending statusChangedAt:timestamp"
Audit Fields
Copy wheels generate property model "createdBy:integer:belongsTo=user updatedBy:integer:belongsTo=user"
Testing
After adding properties:
Copy # Run migration
wheels dbmigrate up
# Generate property tests
wheels generate test model user
# Run tests
wheels test
See Also