PageRenderTime 64ms CodeModel.GetById 30ms app.highlight 3ms RepoModel.GetById 23ms app.codeStats 4ms

/wheels/model/nestedproperties.cfm

http://cfwheels.googlecode.com/
ColdFusion | 305 lines | 292 code | 12 blank | 1 comment | 41 complexity | 403901c4c370762e9a839f841b4b4f27 MD5 | raw file
  1<!--- class methods --->
  2<cffunction name="nestedProperties" output="false" access="public" returntype="void" hint="Allows for nested objects, structs, and arrays to be set from params and other generated data."
  3	examples='
  4		<!--- In `models/User.cfc`, allow for `groupEntitlements` to be saved and deleted through the `user` object --->
  5		<cffunction name="init">
  6			<cfset hasMany("groupEntitlements")>
  7			<cfset nestedProperties(association="groupEntitlements", allowDelete=true)>
  8		</cffunction>
  9	'
 10	categories="model-initialization,nested-properties" chapters="nested-properties" functions="belongsTo,hasOne,hasMany,hasManyCheckBox,hasManyRadioButton,includedInObject">
 11	<cfargument name="association" type="string" required="false" default="" hint="The association (or list of associations) you want to allow to be set through the params. This argument is also aliased as `associations`." />
 12	<cfargument name="autoSave" type="boolean" required="false" hint="Whether to save the association(s) when the parent object is saved." />
 13	<cfargument name="allowDelete" type="boolean" required="false" hint="Set `allowDelete` to `true` to tell Wheels to look for the property `_delete` in your model. If present and set to a value that evaluates to `true`, the model will be deleted when saving the parent." />
 14	<cfargument name="sortProperty" type="string" required="false" hint="Set `sortProperty` to a property on the object that you would like to sort by. The property should be numeric, should start with 1, and should be consecutive. Only valid with `hasMany` associations." />
 15	<cfargument name="rejectIfBlank" type="string" required="false" hint="A list of properties that should not be blank. If any of the properties are blank, any CRUD operations will be rejected." />
 16	<cfscript>
 17		var loc = {};
 18		$args(args=arguments, name="nestedProperties", combine="association/associations");
 19		arguments.association = $listClean(arguments.association);
 20		loc.iEnd = ListLen(arguments.association);
 21		for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
 22		{
 23			loc.association = ListGetAt(arguments.association, loc.i);
 24			if (StructKeyExists(variables.wheels.class.associations, loc.association))
 25			{
 26				variables.wheels.class.associations[loc.association].nested.allow = true;
 27				variables.wheels.class.associations[loc.association].nested.delete = arguments.allowDelete;
 28				variables.wheels.class.associations[loc.association].nested.autoSave = arguments.autoSave;
 29				variables.wheels.class.associations[loc.association].nested.sortProperty = arguments.sortProperty;
 30				variables.wheels.class.associations[loc.association].nested.rejectIfBlank = $listClean(arguments.rejectIfBlank);
 31				// add to the white list if it exists
 32				if (StructKeyExists(variables.wheels.class.accessibleProperties, "whiteList"))
 33					variables.wheels.class.accessibleProperties.whiteList = ListAppend(variables.wheels.class.accessibleProperties.whiteList, loc.association, ",");
 34			}
 35			else if (application.wheels.showErrorInformation)
 36			{
 37				$throw(type="Wheels.AssociationNotFound", message="The `#loc.association#` assocation was not found on the #variables.wheels.class.modelName# model.", extendedInfo="Make sure you have called `hasMany()`, `hasOne()`, or `belongsTo()` before calling the `nestedProperties()` method.");
 38			}
 39		}
 40	</cfscript>
 41</cffunction>
 42
 43<cffunction name="$validateAssociations" returntype="boolean" access="public" output="false">
 44	<cfscript>
 45		var loc = {};
 46		loc.associations = variables.wheels.class.associations;
 47		for (loc.association in loc.associations)
 48		{
 49			if (loc.associations[loc.association].nested.allow && loc.associations[loc.association].nested.autoSave && StructKeyExists(this, loc.association))
 50			{
 51				loc.array = this[loc.association];
 52
 53				if (IsObject(this[loc.association]))
 54					loc.array = [ this[loc.association] ];
 55
 56				if (IsArray(loc.array))
 57					for (loc.i = 1; loc.i lte ArrayLen(loc.array); loc.i++)
 58						$invoke(componentReference=loc.array[loc.i], method="valid");
 59			}
 60		}
 61	</cfscript>
 62	<cfreturn true />
 63</cffunction>
 64
 65<cffunction name="$saveAssociations" returntype="boolean" access="public" output="false">
 66	<cfargument name="parameterize" type="any" required="true" />
 67	<cfargument name="reload" type="boolean" required="true" />
 68	<cfargument name="validate" type="boolean" required="true" />
 69	<cfargument name="callbacks" type="boolean" required="true" />
 70	<cfscript>
 71		var loc = {};
 72		loc.returnValue = true;
 73		loc.associations = variables.wheels.class.associations;
 74		for (loc.association in loc.associations)
 75		{
 76			if (loc.associations[loc.association].nested.allow && loc.associations[loc.association].nested.autoSave && StructKeyExists(this, loc.association))
 77			{
 78				loc.array = this[loc.association];
 79
 80				if (IsObject(this[loc.association]))
 81					loc.array = [ this[loc.association] ];
 82
 83				if (IsArray(loc.array))
 84				{
 85					// get our expanded information for this association
 86					loc.info = $expandedAssociations(include=loc.association);
 87					loc.info = loc.info[1];
 88
 89					for (loc.i = 1; loc.i lte ArrayLen(loc.array); loc.i++)
 90					{
 91						if (ListFindNoCase("hasMany,hasOne", loc.associations[loc.association].type))
 92							$setForeignKeyValues(missingMethodArguments=loc.array[loc.i], keys=loc.info.foreignKey);
 93						loc.saveResult = $invoke(componentReference=loc.array[loc.i], method="save", invokeArgs=arguments);
 94						if (loc.returnValue) // don't change the return value if we have already received a false
 95							loc.returnValue = loc.saveResult;
 96					}
 97				}
 98			}
 99		}
100	</cfscript>
101	<cfreturn loc.returnValue />
102</cffunction>
103
104<cffunction name="$setAssociations" returntype="boolean" access="public" output="false">
105	<cfscript>
106		var loc = {};
107		loc.associations = variables.wheels.class.associations;
108		for (loc.item in loc.associations)
109		{
110			loc.association = loc.associations[loc.item];
111			if (loc.association.nested.allow && loc.association.nested.autoSave && StructKeyExists(this, loc.item))
112			{
113				if (ListFindNoCase("belongsTo,hasOne", loc.association.type) && IsStruct(this[loc.item]))
114					$setOneToOneAssociationProperty(property=loc.item, value=this[loc.item], association=loc.association, delete=true);
115				else if (loc.association.type == "hasMany" && IsArray(this[loc.item]) && ArrayLen(this[loc.item]))
116					$setCollectionAssociationProperty(property=loc.item, value=this[loc.item], association=loc.association, delete=true);
117			}
118		}
119	</cfscript>
120	<cfreturn true />
121</cffunction>
122
123<cffunction name="$setOneToOneAssociationProperty" returntype="void" access="public" output="false">
124	<cfargument name="property" type="string" required="true" />
125	<cfargument name="value" type="struct" required="true" />
126	<cfargument name="association" type="struct" required="true" />
127	<cfargument name="delete" type="boolean" required="false" default="false" />
128	<cfscript>
129		if (!StructKeyExists(this, arguments.property) || !IsObject(this[arguments.property]) || StructKeyExists(this[arguments.property], "_delete"))
130			this[arguments.property] = $getAssociationObject(argumentCollection=arguments);
131
132		if (IsObject(this[arguments.property]))
133			this[arguments.property].setProperties(properties=arguments.value);
134		else
135			StructDelete(this, arguments.property, false);
136	</cfscript>
137	<cfreturn />
138</cffunction>
139
140<cffunction name="$setCollectionAssociationProperty" returntype="void" access="public" output="false">
141	<cfargument name="property" type="string" required="true" />
142	<cfargument name="value" type="any" required="true" />
143	<cfargument name="association" type="struct" required="true" />
144	<cfargument name="delete" type="boolean" required="false" default="false" />
145	<cfscript>
146		var loc = {};
147		loc.model = model(arguments.association.modelName);
148
149		if (!StructKeyExists(this, arguments.property) || !IsArray(this[arguments.property]))
150			this[arguments.property] = [];
151
152		if (IsStruct(arguments.value))
153		{
154			for (loc.item in arguments.value)
155			{
156				// check to see if the id is a tickcount, if so the object is new
157				if (IsNumeric(loc.item) && Ceiling(GetTickCount() / 900000000) == Ceiling(loc.item / 900000000))
158				{
159					ArrayAppend(this[arguments.property], $getAssociationObject(property=arguments.property, value=arguments.value[loc.item], association=arguments.association, delete=arguments.delete));
160					$updateCollectionObject(property=arguments.property, value=arguments.value[loc.item]);
161				}
162				else
163				{
164					// get our primary keys
165					loc.keys = loc.model.primaryKey();
166					loc.itemArray = ListToArray(loc.item, ",", true);
167					loc.iEnd = ListLen(loc.keys);
168					for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
169						arguments.value[loc.item][ListGetAt(loc.keys, loc.i)] = loc.itemArray[loc.i];
170					ArrayAppend(this[arguments.property], $getAssociationObject(property=arguments.property, value=arguments.value[loc.item], association=arguments.association, delete=arguments.delete));
171					$updateCollectionObject(property=arguments.property, value=arguments.value[loc.item]);
172				}
173			}
174		}
175		else if (IsArray(arguments.value))
176		{
177			for (loc.i = 1; loc.i lte ArrayLen(arguments.value); loc.i++)
178			{
179				if (IsObject(arguments.value[loc.i]) && ArrayLen(this[arguments.property]) gte loc.i && IsObject(this[arguments.property][loc.i]) && this[arguments.property][loc.i].compareTo(arguments.value[loc.i]))
180				{
181					this[arguments.property][loc.i] = $getAssociationObject(property=arguments.property, value=arguments.value[loc.i], association=arguments.association, delete=arguments.delete);
182					if (!IsStruct(this[arguments.property][loc.i]) && !this[arguments.property][loc.i])
183					{
184						ArrayDeleteAt(this[arguments.property], loc.i);
185						loc.i--;
186					}
187					else
188					{
189						$updateCollectionObject(property=arguments.property, value=arguments.value[loc.i], position=loc.i);
190					}
191				}
192				else if (IsStruct(arguments.value[loc.i]) && ArrayLen(this[arguments.property]) gte loc.i && IsObject(this[arguments.property][loc.i]))
193				{
194					this[arguments.property][loc.i] = $getAssociationObject(property=arguments.property, value=arguments.value[loc.i], association=arguments.association, delete=arguments.delete);
195					if (!IsStruct(this[arguments.property][loc.i]) && !this[arguments.property][loc.i])
196					{
197						ArrayDeleteAt(this[arguments.property], loc.i);
198						loc.i--;
199					}
200					else
201					{
202						$updateCollectionObject(property=arguments.property, value=arguments.value[loc.i], position=loc.i);
203					}
204				}
205				else
206				{
207					ArrayAppend(this[arguments.property], $getAssociationObject(property=arguments.property, value=arguments.value[loc.i], association=arguments.association, delete=arguments.delete));
208					$updateCollectionObject(property=arguments.property, value=arguments.value[loc.i]);
209				}
210			}
211		}
212		// sort the order of the objects in the array if the property is set
213		if (Len(arguments.association.nested.sortProperty))
214		{
215			loc.sortedArray = [];
216			loc.iEnd = ArrayLen(this[arguments.property]);
217
218			for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
219			{
220				if (!IsNumeric(this[arguments.property][loc.i][arguments.association.nested.sortProperty]))
221					return;
222				loc.sortedArray[this[arguments.property][loc.i][arguments.association.nested.sortProperty]] = this[arguments.property][loc.i];
223			}
224
225			this[arguments.property] = loc.sortedArray;
226		}
227	</cfscript>
228	<cfreturn />
229</cffunction>
230
231<cffunction name="$updateCollectionObject" returntype="void" output="false" access="public">
232	<cfargument name="property" type="string" required="true" />
233	<cfargument name="value" type="struct" required="true" />
234	<cfargument name="position" type="numeric" required="false" default="0" />
235	<cfscript>
236		var loc = {};
237		if (!arguments.position)
238			arguments.position = ArrayLen(this[arguments.property]);
239		if (IsObject(this[arguments.property][arguments.position]))
240			this[arguments.property][arguments.position].setProperties(properties=arguments.value);
241		else
242			ArrayDeleteAt(this[arguments.property], arguments.position);
243	</cfscript>
244</cffunction>
245
246<cffunction name="$getAssociationObject" returntype="any" access="public" output="false">
247	<cfargument name="property" type="string" required="true" />
248	<cfargument name="value" type="struct" required="true" />
249	<cfargument name="association" type="struct" required="true" />
250	<cfargument name="delete" type="boolean" required="true" />
251	<cfscript>
252		var loc = {};
253		loc.method = "";
254		loc.object = false;
255		loc.delete = false;
256		loc.arguments = {};
257		loc.model = model(arguments.association.modelName);
258
259		// check to see if the struct has all of the keys we need from rejectIfBlank
260		if ($structKeysExist(struct=arguments.value, properties=arguments.association.nested.rejectIfBlank))
261		{
262			// get our primary keys, if they don't exist, then we create a new object
263			loc.arguments.key = $createPrimaryKeyList(params=arguments.value, keys=loc.model.primaryKey());
264
265			if (IsObject(arguments.value))
266				loc.object = arguments.value;
267			else if (Len(loc.arguments.key))
268				loc.object = loc.model.findByKey(argumentCollection=loc.arguments);
269
270			if (StructKeyExists(arguments.value, "_delete") && IsBoolean(arguments.value["_delete"]) && arguments.value["_delete"])
271				loc.delete = true;
272
273			if (!IsObject(loc.object) && !loc.delete)
274			{
275				StructDelete(loc.arguments, "key", false);
276				return $invoke(componentReference=loc.model, method="new", invokeArgs=loc.arguments);
277			}
278			else if (Len(loc.arguments.key) && loc.delete && arguments.association.nested.delete && arguments.delete)
279			{
280				$invoke(componentReference=loc.model, method="deleteByKey", invokeArgs=loc.arguments);
281				return false;
282			}
283		}
284	</cfscript>
285	<cfreturn loc.object />
286</cffunction>
287
288<cffunction name="$createPrimaryKeyList" returntype="string" access="public" output="false">
289	<cfargument name="params" type="struct" required="true" />
290	<cfargument name="keys" type="string" required="true" />
291	<cfscript>
292		var loc = {};
293		loc.returnValue = "";
294
295		loc.iEnd = ListLen(arguments.keys);
296		for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
297		{
298			loc.key = ListGetAt(arguments.keys, loc.i);
299			if (!StructKeyExists(arguments.params, loc.key) || !Len(arguments.params[loc.key]))
300				return "";
301			loc.returnValue = ListAppend(loc.returnValue, arguments.params[loc.key]);
302		}
303	</cfscript>
304	<cfreturn loc.returnValue />
305</cffunction>