PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/wheels/model/nestedproperties.cfm

http://cfwheels.googlecode.com/
ColdFusion | 305 lines | 292 code | 12 blank | 1 comment | 41 complexity | 403901c4c370762e9a839f841b4b4f27 MD5 | raw file
Possible License(s): Apache-2.0, CPL-1.0
  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. <cffunction name="$validateAssociations" returntype="boolean" access="public" output="false">
  43. <cfscript>
  44. var loc = {};
  45. loc.associations = variables.wheels.class.associations;
  46. for (loc.association in loc.associations)
  47. {
  48. if (loc.associations[loc.association].nested.allow && loc.associations[loc.association].nested.autoSave && StructKeyExists(this, loc.association))
  49. {
  50. loc.array = this[loc.association];
  51. if (IsObject(this[loc.association]))
  52. loc.array = [ this[loc.association] ];
  53. if (IsArray(loc.array))
  54. for (loc.i = 1; loc.i lte ArrayLen(loc.array); loc.i++)
  55. $invoke(componentReference=loc.array[loc.i], method="valid");
  56. }
  57. }
  58. </cfscript>
  59. <cfreturn true />
  60. </cffunction>
  61. <cffunction name="$saveAssociations" returntype="boolean" access="public" output="false">
  62. <cfargument name="parameterize" type="any" required="true" />
  63. <cfargument name="reload" type="boolean" required="true" />
  64. <cfargument name="validate" type="boolean" required="true" />
  65. <cfargument name="callbacks" type="boolean" required="true" />
  66. <cfscript>
  67. var loc = {};
  68. loc.returnValue = true;
  69. loc.associations = variables.wheels.class.associations;
  70. for (loc.association in loc.associations)
  71. {
  72. if (loc.associations[loc.association].nested.allow && loc.associations[loc.association].nested.autoSave && StructKeyExists(this, loc.association))
  73. {
  74. loc.array = this[loc.association];
  75. if (IsObject(this[loc.association]))
  76. loc.array = [ this[loc.association] ];
  77. if (IsArray(loc.array))
  78. {
  79. // get our expanded information for this association
  80. loc.info = $expandedAssociations(include=loc.association);
  81. loc.info = loc.info[1];
  82. for (loc.i = 1; loc.i lte ArrayLen(loc.array); loc.i++)
  83. {
  84. if (ListFindNoCase("hasMany,hasOne", loc.associations[loc.association].type))
  85. $setForeignKeyValues(missingMethodArguments=loc.array[loc.i], keys=loc.info.foreignKey);
  86. loc.saveResult = $invoke(componentReference=loc.array[loc.i], method="save", invokeArgs=arguments);
  87. if (loc.returnValue) // don't change the return value if we have already received a false
  88. loc.returnValue = loc.saveResult;
  89. }
  90. }
  91. }
  92. }
  93. </cfscript>
  94. <cfreturn loc.returnValue />
  95. </cffunction>
  96. <cffunction name="$setAssociations" returntype="boolean" access="public" output="false">
  97. <cfscript>
  98. var loc = {};
  99. loc.associations = variables.wheels.class.associations;
  100. for (loc.item in loc.associations)
  101. {
  102. loc.association = loc.associations[loc.item];
  103. if (loc.association.nested.allow && loc.association.nested.autoSave && StructKeyExists(this, loc.item))
  104. {
  105. if (ListFindNoCase("belongsTo,hasOne", loc.association.type) && IsStruct(this[loc.item]))
  106. $setOneToOneAssociationProperty(property=loc.item, value=this[loc.item], association=loc.association, delete=true);
  107. else if (loc.association.type == "hasMany" && IsArray(this[loc.item]) && ArrayLen(this[loc.item]))
  108. $setCollectionAssociationProperty(property=loc.item, value=this[loc.item], association=loc.association, delete=true);
  109. }
  110. }
  111. </cfscript>
  112. <cfreturn true />
  113. </cffunction>
  114. <cffunction name="$setOneToOneAssociationProperty" returntype="void" access="public" output="false">
  115. <cfargument name="property" type="string" required="true" />
  116. <cfargument name="value" type="struct" required="true" />
  117. <cfargument name="association" type="struct" required="true" />
  118. <cfargument name="delete" type="boolean" required="false" default="false" />
  119. <cfscript>
  120. if (!StructKeyExists(this, arguments.property) || !IsObject(this[arguments.property]) || StructKeyExists(this[arguments.property], "_delete"))
  121. this[arguments.property] = $getAssociationObject(argumentCollection=arguments);
  122. if (IsObject(this[arguments.property]))
  123. this[arguments.property].setProperties(properties=arguments.value);
  124. else
  125. StructDelete(this, arguments.property, false);
  126. </cfscript>
  127. <cfreturn />
  128. </cffunction>
  129. <cffunction name="$setCollectionAssociationProperty" returntype="void" access="public" output="false">
  130. <cfargument name="property" type="string" required="true" />
  131. <cfargument name="value" type="any" required="true" />
  132. <cfargument name="association" type="struct" required="true" />
  133. <cfargument name="delete" type="boolean" required="false" default="false" />
  134. <cfscript>
  135. var loc = {};
  136. loc.model = model(arguments.association.modelName);
  137. if (!StructKeyExists(this, arguments.property) || !IsArray(this[arguments.property]))
  138. this[arguments.property] = [];
  139. if (IsStruct(arguments.value))
  140. {
  141. for (loc.item in arguments.value)
  142. {
  143. // check to see if the id is a tickcount, if so the object is new
  144. if (IsNumeric(loc.item) && Ceiling(GetTickCount() / 900000000) == Ceiling(loc.item / 900000000))
  145. {
  146. ArrayAppend(this[arguments.property], $getAssociationObject(property=arguments.property, value=arguments.value[loc.item], association=arguments.association, delete=arguments.delete));
  147. $updateCollectionObject(property=arguments.property, value=arguments.value[loc.item]);
  148. }
  149. else
  150. {
  151. // get our primary keys
  152. loc.keys = loc.model.primaryKey();
  153. loc.itemArray = ListToArray(loc.item, ",", true);
  154. loc.iEnd = ListLen(loc.keys);
  155. for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
  156. arguments.value[loc.item][ListGetAt(loc.keys, loc.i)] = loc.itemArray[loc.i];
  157. ArrayAppend(this[arguments.property], $getAssociationObject(property=arguments.property, value=arguments.value[loc.item], association=arguments.association, delete=arguments.delete));
  158. $updateCollectionObject(property=arguments.property, value=arguments.value[loc.item]);
  159. }
  160. }
  161. }
  162. else if (IsArray(arguments.value))
  163. {
  164. for (loc.i = 1; loc.i lte ArrayLen(arguments.value); loc.i++)
  165. {
  166. 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]))
  167. {
  168. this[arguments.property][loc.i] = $getAssociationObject(property=arguments.property, value=arguments.value[loc.i], association=arguments.association, delete=arguments.delete);
  169. if (!IsStruct(this[arguments.property][loc.i]) && !this[arguments.property][loc.i])
  170. {
  171. ArrayDeleteAt(this[arguments.property], loc.i);
  172. loc.i--;
  173. }
  174. else
  175. {
  176. $updateCollectionObject(property=arguments.property, value=arguments.value[loc.i], position=loc.i);
  177. }
  178. }
  179. else if (IsStruct(arguments.value[loc.i]) && ArrayLen(this[arguments.property]) gte loc.i && IsObject(this[arguments.property][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
  193. {
  194. ArrayAppend(this[arguments.property], $getAssociationObject(property=arguments.property, value=arguments.value[loc.i], association=arguments.association, delete=arguments.delete));
  195. $updateCollectionObject(property=arguments.property, value=arguments.value[loc.i]);
  196. }
  197. }
  198. }
  199. // sort the order of the objects in the array if the property is set
  200. if (Len(arguments.association.nested.sortProperty))
  201. {
  202. loc.sortedArray = [];
  203. loc.iEnd = ArrayLen(this[arguments.property]);
  204. for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
  205. {
  206. if (!IsNumeric(this[arguments.property][loc.i][arguments.association.nested.sortProperty]))
  207. return;
  208. loc.sortedArray[this[arguments.property][loc.i][arguments.association.nested.sortProperty]] = this[arguments.property][loc.i];
  209. }
  210. this[arguments.property] = loc.sortedArray;
  211. }
  212. </cfscript>
  213. <cfreturn />
  214. </cffunction>
  215. <cffunction name="$updateCollectionObject" returntype="void" output="false" access="public">
  216. <cfargument name="property" type="string" required="true" />
  217. <cfargument name="value" type="struct" required="true" />
  218. <cfargument name="position" type="numeric" required="false" default="0" />
  219. <cfscript>
  220. var loc = {};
  221. if (!arguments.position)
  222. arguments.position = ArrayLen(this[arguments.property]);
  223. if (IsObject(this[arguments.property][arguments.position]))
  224. this[arguments.property][arguments.position].setProperties(properties=arguments.value);
  225. else
  226. ArrayDeleteAt(this[arguments.property], arguments.position);
  227. </cfscript>
  228. </cffunction>
  229. <cffunction name="$getAssociationObject" returntype="any" access="public" output="false">
  230. <cfargument name="property" type="string" required="true" />
  231. <cfargument name="value" type="struct" required="true" />
  232. <cfargument name="association" type="struct" required="true" />
  233. <cfargument name="delete" type="boolean" required="true" />
  234. <cfscript>
  235. var loc = {};
  236. loc.method = "";
  237. loc.object = false;
  238. loc.delete = false;
  239. loc.arguments = {};
  240. loc.model = model(arguments.association.modelName);
  241. // check to see if the struct has all of the keys we need from rejectIfBlank
  242. if ($structKeysExist(struct=arguments.value, properties=arguments.association.nested.rejectIfBlank))
  243. {
  244. // get our primary keys, if they don't exist, then we create a new object
  245. loc.arguments.key = $createPrimaryKeyList(params=arguments.value, keys=loc.model.primaryKey());
  246. if (IsObject(arguments.value))
  247. loc.object = arguments.value;
  248. else if (Len(loc.arguments.key))
  249. loc.object = loc.model.findByKey(argumentCollection=loc.arguments);
  250. if (StructKeyExists(arguments.value, "_delete") && IsBoolean(arguments.value["_delete"]) && arguments.value["_delete"])
  251. loc.delete = true;
  252. if (!IsObject(loc.object) && !loc.delete)
  253. {
  254. StructDelete(loc.arguments, "key", false);
  255. return $invoke(componentReference=loc.model, method="new", invokeArgs=loc.arguments);
  256. }
  257. else if (Len(loc.arguments.key) && loc.delete && arguments.association.nested.delete && arguments.delete)
  258. {
  259. $invoke(componentReference=loc.model, method="deleteByKey", invokeArgs=loc.arguments);
  260. return false;
  261. }
  262. }
  263. </cfscript>
  264. <cfreturn loc.object />
  265. </cffunction>
  266. <cffunction name="$createPrimaryKeyList" returntype="string" access="public" output="false">
  267. <cfargument name="params" type="struct" required="true" />
  268. <cfargument name="keys" type="string" required="true" />
  269. <cfscript>
  270. var loc = {};
  271. loc.returnValue = "";
  272. loc.iEnd = ListLen(arguments.keys);
  273. for (loc.i = 1; loc.i lte loc.iEnd; loc.i++)
  274. {
  275. loc.key = ListGetAt(arguments.keys, loc.i);
  276. if (!StructKeyExists(arguments.params, loc.key) || !Len(arguments.params[loc.key]))
  277. return "";
  278. loc.returnValue = ListAppend(loc.returnValue, arguments.params[loc.key]);
  279. }
  280. </cfscript>
  281. <cfreturn loc.returnValue />
  282. </cffunction>