PageRenderTime 70ms CodeModel.GetById 32ms RepoModel.GetById 0ms app.codeStats 0ms

/wheels/model/crud.cfm

http://cfwheels.googlecode.com/
ColdFusion | 1041 lines | 965 code | 39 blank | 37 comment | 107 complexity | a3896e23b531ae97d5cf4caff6380a41 MD5 | raw file
Possible License(s): Apache-2.0, CPL-1.0
  1. <!--- PUBLIC MODEL CLASS METHODS --->
  2. <!--- create --->
  3. <cffunction name="create" returntype="any" access="public" output="false" hint="Creates a new object, saves it to the database (if the validation permits it), and returns it. If the validation fails, the unsaved object (with errors added to it) is still returned. Property names and values can be passed in either using named arguments or as a struct to the `properties` argument."
  4. examples=
  5. '
  6. <!--- Create a new author and save it to the database --->
  7. <cfset newAuthor = model("author").create(params.author)>
  8. <!--- Same as above using named arguments --->
  9. <cfset newAuthor = model("author").create(firstName="John", lastName="Doe")>
  10. <!--- Same as above using both named arguments and a struct --->
  11. <cfset newAuthor = model("author").create(active=1, properties=params.author)>
  12. <!--- If you have a `hasOne` or `hasMany` association setup from `customer` to `order`, you can do a scoped call. (The `createOrder` method below will call `model("order").create(customerId=aCustomer.id, shipping=params.shipping)` internally.) --->
  13. <cfset aCustomer = model("customer").findByKey(params.customerId)>
  14. <cfset anOrder = aCustomer.createOrder(shipping=params.shipping)>
  15. '
  16. categories="model-class,create" chapters="creating-records,associations" functions="hasOne,hasMany,new">
  17. <cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
  18. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  19. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @save.">
  20. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  21. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  22. <cfscript>
  23. var loc = {};
  24. $args(name="create", args=arguments);
  25. loc.parameterize = arguments.parameterize;
  26. StructDelete(arguments, "parameterize");
  27. loc.returnValue = new(argumentCollection=arguments);
  28. loc.returnValue.save(parameterize=loc.parameterize, reload=arguments.reload, transaction=arguments.transaction, callbacks=arguments.callbacks);
  29. </cfscript>
  30. <cfreturn loc.returnValue>
  31. </cffunction>
  32. <cffunction name="new" returntype="any" access="public" output="false" hint="Creates a new object based on supplied properties and returns it. The object is not saved to the database; it only exists in memory. Property names and values can be passed in either using named arguments or as a struct to the `properties` argument."
  33. examples=
  34. '
  35. <!--- Create a new author in memory (not saved to the database) --->
  36. <cfset newAuthor = model("author").new()>
  37. <!--- Create a new author based on properties in a struct --->
  38. <cfset newAuthor = model("author").new(params.authorStruct)>
  39. <!--- Create a new author by passing in named arguments --->
  40. <cfset newAuthor = model("author").new(firstName="John", lastName="Doe")>
  41. <!--- If you have a `hasOne` or `hasMany` association setup from `customer` to `order`, you can do a scoped call. (The `newOrder` method below will call `model("order").new(customerId=aCustomer.id)` internally.) --->
  42. <cfset aCustomer = model("customer").findByKey(params.customerId)>
  43. <cfset anOrder = aCustomer.newOrder(shipping=params.shipping)>
  44. '
  45. categories="model-class,create" chapters="creating-records,associations" functions="create,hasMany,hasOne">
  46. <cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="The properties you want to set on the object (can also be passed in as named arguments).">
  47. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  48. <cfscript>
  49. var loc = {};
  50. arguments.properties = $setProperties(argumentCollection=arguments, filterList="properties,reload,transaction,callbacks", setOnModel=false);
  51. loc.returnValue = $createInstance(properties=arguments.properties, persisted=false, callbacks=arguments.callbacks);
  52. loc.returnValue.$setDefaultValues();
  53. </cfscript>
  54. <cfreturn loc.returnValue>
  55. </cffunction>
  56. <!--- read --->
  57. <cffunction name="findAll" returntype="any" access="public" output="false" hint="Returns records from the database table mapped to this model according to the arguments passed in. (Use the `where` argument to decide which records to get, use the `order` argument to set in what order those records should be returned, and so on). The records will be returned as either a `cfquery` result set or an array of objects (depending on what the `returnAs` argument is set to). Instead of using the `where` argument, you can create cleaner code by making use of a concept called dynamic finders."
  58. examples=
  59. '
  60. <!--- Getting only 5 users and ordering them randomly --->
  61. <cfset fiveRandomUsers = model("user").findAll(maxRows=5, order="random")>
  62. <!--- Including an association (which in this case needs to be setup as a `belongsTo` association to `author` on the `article` model first) --->
  63. <cfset articles = model("article").findAll(where="published=1", order="createdAt DESC", include="author")>
  64. <!--- Similar to the above but using the association in the opposite direction (which needs to be setup as a `hasMany` association to `article` on the `author` model) --->
  65. <cfset bobsArticles = model("author").findAll(where="firstName=''Bob''", include="articles")>
  66. <!--- Using pagination (getting records 26-50 in this case) and a more complex way to include associations (a song `belongsTo` an album, which in turn `belongsTo` an artist) --->
  67. <cfset songs = model("song").findAll(include="album(artist)", page=2, perPage=25)>
  68. <!--- Using a dynamic finder to get all books released a certain year. Same as calling model("book").findOne(where="releaseYear=##params.year##") --->
  69. <cfset books = model("book").findAllByReleaseYear(params.year)>
  70. <!--- Getting all books of a certain type from a specific year by using a dynamic finder. Same as calling model("book").findAll(where="releaseYear=##params.year## AND type=''##params.type##''") --->
  71. <cfset books = model("book").findAllByReleaseYearAndType("##params.year##,##params.type##")>
  72. <!--- If you have a `hasMany` association setup from `post` to `comment`, you can do a scoped call. (The `comments` method below will call `model("comment").findAll(where="postId=##post.id##")` internally) --->
  73. <cfset post = model("post").findByKey(params.postId)>
  74. <cfset comments = post.comments()>
  75. '
  76. categories="model-class,read" chapters="reading-records,associations" functions="findByKey,findOne,hasMany">
  77. <cfargument name="where" type="string" required="false" default="" hint="This argument maps to the `WHERE` clause of the query. The following operators are supported: `=`, `!=`, `<>`, `<`, `<=`, `>`, `>=`, `LIKE`, `NOT LIKE`, `IN`, `NOT IN`, `IS NULL`, `IS NOT NULL`, `AND`, and `OR`. (Note that the key words need to be written in upper case.) You can also use parentheses to group statements. You do not need to specify the table name(s); Wheels will do that for you.">
  78. <cfargument name="order" type="string" required="false" hint="Maps to the `ORDER BY` clause of the query. You do not need to specify the table name(s); Wheels will do that for you.">
  79. <cfargument name="group" type="string" required="false" hint="Maps to the `GROUP BY` clause of the query. You do not need to specify the table name(s); Wheels will do that for you.">
  80. <cfargument name="select" type="string" required="false" default="" hint="Determines how the `SELECT` clause for the query used to return data will look. You can pass in a list of the properties (which map to columns) that you want returned from your table(s). If you don't set this argument at all, Wheels will select all properties from your table(s). If you specify a table name (e.g. `users.email`) or alias a column (e.g. `fn AS firstName`) in the list, then the entire list will be passed through unchanged and used in the `SELECT` clause of the query. By default, all column names in tables `JOIN`ed via the `include` argument will be prepended with the singular version of the included table name.">
  81. <cfargument name="distinct" type="boolean" required="false" default="false" hint="Whether to add the `DISTINCT` keyword to your `SELECT` clause. Wheels will, when necessary, add this automatically (when using pagination and a `hasMany` association is used in the `include` argument, to name one example).">
  82. <cfargument name="include" type="string" required="false" default="" hint="Associations that should be included in the query using `INNER` or `LEFT OUTER` joins (which join type that is used depends on how the association has been set up in your model). If all included associations are set on the current model, you can specify them in a list (e.g. `department,addresses,emails`). You can build more complex `include` strings by using parentheses when the association is set on an included model, like `album(artist(genre))`, for example. These complex `include` strings only work when `returnAs` is set to `query` though.">
  83. <cfargument name="maxRows" type="numeric" required="false" default="-1" hint="Maximum number of records to retrieve. Passed on to the `maxRows` `cfquery` attribute. The default, `-1`, means that all records will be retrieved.">
  84. <cfargument name="page" type="numeric" required="false" default=0 hint="If you want to paginate records, you can do so by specifying a page number here. For example, getting records 11-20 would be page number 2 when `perPage` is kept at the default setting (10 records per page). The default, `0`, means that records won't be paginated and that the `perPage`, `count`, and `handle` arguments will be ignored.">
  85. <cfargument name="perPage" type="numeric" required="false" hint="When using pagination, you can specify how many records you want to fetch per page here. This argument is only used when the `page` argument has been passed in.">
  86. <cfargument name="count" type="numeric" required="false" default=0 hint="When using pagination and you know in advance how many records you want to paginate through, you can pass in that value here. Doing so will prevent Wheels from running a `COUNT` query to get this value. This argument is only used when the `page` argument has been passed in.">
  87. <cfargument name="handle" type="string" required="false" default="query" hint="Handle to use for the query in pagination. This is useful when you're paginating multiple queries and need to reference them in the @paginationLinks function, for example. This argument is only used when the `page` argument has been passed in.">
  88. <cfargument name="cache" type="any" required="false" default="" hint="If you want to cache the query, you can do so by specifying the number of minutes you want to cache the query for here. If you set it to `true`, the default cache time will be used (60 minutes).">
  89. <cfargument name="reload" type="boolean" required="false" hint="Set to `true` to force Wheels to query the database even though an identical query may have been run in the same request. (The default in Wheels is to get the second query from the request-level cache.)">
  90. <cfargument name="parameterize" type="any" required="false" hint="Set to `true` to use `cfqueryparam` on all columns, or pass in a list of property names to use `cfqueryparam` on those only.">
  91. <cfargument name="returnAs" type="string" required="false" hint="Set this to `objects` to return an array of objects. Set this to `query` to return a query result set.">
  92. <cfargument name="returnIncluded" type="boolean" required="false" hint="When `returnAs` is set to `objects`, you can set this argument to `false` to prevent returning objects fetched from associations specified in the `include` argument. This is useful when you only need to include associations for use in the `WHERE` clause only and want to avoid the performance hit that comes with object creation.">
  93. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="You can set this argument to `false` to prevent running the execution of callbacks for a method call.">
  94. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="You can set this argument to `true` to include soft-deleted records in the results.">
  95. <cfargument name="$limit" type="numeric" required="false" default=0>
  96. <cfargument name="$offset" type="numeric" required="false" default=0>
  97. <cfscript>
  98. var loc = {};
  99. $args(name="findAll", args=arguments);
  100. // we only allow direct associations to be loaded when returning objects
  101. if (application.wheels.showErrorInformation && Len(arguments.returnAs) && arguments.returnAs != "query" && Find("(", arguments.include) && arguments.returnIncluded)
  102. $throw(type="Wheels", message="Incorrect Arguments", extendedInfo="You may only include direct associations to this object when returning an array of objects.");
  103. // count records and get primary keys for pagination
  104. if (arguments.page)
  105. {
  106. if (application.wheels.showErrorInformation && arguments.perPage lte 0)
  107. $throw(type="Wheels", message="Incorrect Argument", extendedInfo="The perPage argument should be a positive numeric value.");
  108. if (Len(arguments.order))
  109. {
  110. // insert primary keys to order clause unless they are already there, this guarantees that the ordering is unique which is required to make pagination work properly
  111. loc.compareList = $listClean(ReplaceNoCase(ReplaceNoCase(arguments.order, " ASC", "", "all"), " DESC", "", "all"));
  112. loc.iEnd = ListLen(primaryKeys());
  113. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  114. {
  115. loc.iItem = primaryKeys(loc.i);
  116. if (!ListFindNoCase(loc.compareList, loc.iItem) && !ListFindNoCase(loc.compareList, tableName() & "." & loc.iItem))
  117. arguments.order = ListAppend(arguments.order, loc.iItem);
  118. }
  119. }
  120. else
  121. {
  122. // we can't paginate without any order so we default to ascending ordering by the primary key column(s)
  123. arguments.order = primaryKey();
  124. }
  125. if (Len(arguments.include))
  126. loc.distinct = true;
  127. else
  128. loc.distinct = false;
  129. if (arguments.count gt 0)
  130. loc.totalRecords = arguments.count;
  131. else
  132. loc.totalRecords = this.count(where=arguments.where, include=arguments.include, reload=arguments.reload, cache=arguments.cache, distinct=loc.distinct, parameterize=arguments.parameterize, includeSoftDeletes=arguments.includeSoftDeletes);
  133. loc.currentPage = arguments.page;
  134. if (loc.totalRecords == 0)
  135. {
  136. loc.totalPages = 0;
  137. loc.returnValue = "";
  138. }
  139. else
  140. {
  141. loc.totalPages = Ceiling(loc.totalRecords/arguments.perPage);
  142. loc.limit = arguments.perPage;
  143. loc.offset = (arguments.perPage * arguments.page) - arguments.perPage;
  144. // if the full range of records is not requested we correct the limit to get the exact amount instead
  145. // for example if totalRecords is 57, limit is 10 and offset 50 (i.e. requesting records 51-60) we change the limit to 7
  146. if ((loc.limit + loc.offset) gt loc.totalRecords)
  147. loc.limit = loc.totalRecords - loc.offset;
  148. if (loc.limit < 1)
  149. {
  150. // if limit is 0 or less it means that a page that has no records was asked for so we return an empty query
  151. loc.returnValue = "";
  152. }
  153. else
  154. {
  155. loc.values = findAll($limit=loc.limit, $offset=loc.offset, select=primaryKeys(), where=arguments.where, order=arguments.order, include=arguments.include, reload=arguments.reload, cache=arguments.cache, distinct=loc.distinct, parameterize=arguments.parameterize, includeSoftDeletes=arguments.includeSoftDeletes);
  156. if (loc.values.RecordCount) {
  157. loc.paginationWhere = "";
  158. loc.iEnd = ListLen(primaryKeys());
  159. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  160. {
  161. loc.property = primaryKeys(loc.i);
  162. if (ListFindNoCase("integer,float", variables.wheels.class.properties[loc.property].validationtype))
  163. {
  164. loc.list = Evaluate("ValueList(loc.values.#loc.property#)");
  165. }
  166. else
  167. {
  168. loc.list = Evaluate("QuotedValueList(loc.values.#loc.property#)");
  169. }
  170. loc.paginationWhere = ListAppend(loc.paginationWhere, "#tableName()#.#loc.property# IN (#loc.list#)", Chr(7));
  171. }
  172. loc.paginationWhere = Replace(loc.paginationWhere, Chr(7), " AND ", "all");
  173. if (Len(arguments.where) && Len(arguments.include)) // this can be improved to also check if the where clause checks on a joined table, if not we can use the simple where clause with just the ids
  174. arguments.where = "(" & arguments.where & ")" & " AND " & loc.paginationWhere;
  175. else
  176. arguments.where = loc.paginationWhere;
  177. }
  178. }
  179. }
  180. // store pagination info in the request scope so all pagination methods can access it
  181. setPagination(loc.totalRecords, loc.currentPage, arguments.perPage, arguments.handle);
  182. }
  183. if (StructKeyExists(loc, "returnValue") && !Len(loc.returnValue))
  184. {
  185. if (arguments.returnAs == "query")
  186. loc.returnValue = QueryNew("");
  187. else if (singularize(arguments.returnAs) == arguments.returnAs)
  188. loc.returnValue = false;
  189. else
  190. loc.returnValue = ArrayNew(1);
  191. }
  192. else if (!StructKeyExists(loc, "returnValue"))
  193. {
  194. // make the where clause generic for use in caching
  195. loc.originalWhere = arguments.where;
  196. arguments.where = REReplace(arguments.where, variables.wheels.class.RESQLWhere, "\1?\8" , "all");
  197. // get info from cache when available, otherwise create the generic select, from, where and order by clause
  198. loc.queryShellKey = $hashedKey(variables.wheels.class.modelName, arguments);
  199. loc.sql = $getFromCache(key=loc.queryShellKey, category="schemas");
  200. if (!IsArray(loc.sql))
  201. {
  202. loc.sql = [];
  203. ArrayAppend(loc.sql, $selectClause(select=arguments.select, include=arguments.include, returnAs=arguments.returnAs));
  204. ArrayAppend(loc.sql, $fromClause(include=arguments.include));
  205. loc.sql = $addWhereClause(sql=loc.sql, where=loc.originalWhere, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
  206. loc.groupBy = $groupByClause(select=arguments.select, group=arguments.group, include=arguments.include, distinct=arguments.distinct, returnAs=arguments.returnAs);
  207. if (Len(loc.groupBy))
  208. ArrayAppend(loc.sql, loc.groupBy);
  209. loc.orderBy = $orderByClause(order=arguments.order, include=arguments.include);
  210. if (Len(loc.orderBy))
  211. ArrayAppend(loc.sql, loc.orderBy);
  212. if (application.wheels.cacheModelInitialization)
  213. $addToCache(key=loc.queryShellKey, value=loc.sql, category="schemas");
  214. }
  215. // add where clause parameters to the generic sql info
  216. loc.sql = $addWhereClauseParameters(sql=loc.sql, where=loc.originalWhere);
  217. // return existing query result if it has been run already in current request, otherwise pass off the sql array to the query
  218. loc.queryKey = $hashedKey(variables.wheels.class.modelName, arguments, loc.originalWhere);
  219. if (application.wheels.cacheQueriesDuringRequest && !arguments.reload && StructKeyExists(request.wheels, loc.queryKey))
  220. {
  221. loc.findAll = request.wheels[loc.queryKey];
  222. }
  223. else
  224. {
  225. loc.finderArgs = {};
  226. loc.finderArgs.sql = loc.sql;
  227. loc.finderArgs.maxRows = arguments.maxRows;
  228. loc.finderArgs.parameterize = arguments.parameterize;
  229. loc.finderArgs.limit = arguments.$limit;
  230. loc.finderArgs.offset = arguments.$offset;
  231. loc.finderArgs.$primaryKey = primaryKeys();
  232. if (application.wheels.cacheQueries && (IsNumeric(arguments.cache) || (IsBoolean(arguments.cache) && arguments.cache)))
  233. loc.finderArgs.cachedWithin = $timeSpanForCache(arguments.cache);
  234. loc.findAll = variables.wheels.class.adapter.$query(argumentCollection=loc.finderArgs);
  235. request.wheels[loc.queryKey] = loc.findAll; // <- store in request cache so we never run the exact same query twice in the same request
  236. }
  237. request.wheels[$hashedKey(loc.findAll.query)] = variables.wheels.class.modelName; // place an identifer in request scope so we can reference this query when passed in to view functions
  238. switch (arguments.returnAs)
  239. {
  240. case "query":
  241. {
  242. loc.returnValue = loc.findAll.query;
  243. // execute callbacks unless we're currently running the count or primary key pagination queries (we only want the callback to run when we have the actual data)
  244. if (loc.returnValue.columnList != "wheelsqueryresult" && !arguments.$limit && !arguments.$offset)
  245. $callback("afterFind", arguments.callbacks, loc.returnValue);
  246. break;
  247. }
  248. case "struct": case "structs":
  249. {
  250. loc.returnValue = $serializeQueryToStructs(query=loc.findAll.query, argumentCollection=arguments);
  251. break;
  252. }
  253. case "object": case "objects":
  254. {
  255. loc.returnValue = $serializeQueryToObjects(query=loc.findAll.query, argumentCollection=arguments);
  256. break;
  257. }
  258. default:
  259. {
  260. if (application.wheels.showErrorInformation)
  261. $throw(type="Wheels.IncorrectArgumentValue", message="Incorrect Arguments", extendedInfo="The `returnAs` may be either `query`, `struct(s)` or `object(s)`");
  262. break;
  263. }
  264. }
  265. }
  266. </cfscript>
  267. <cfreturn loc.returnValue>
  268. </cffunction>
  269. <cffunction name="findByKey" returntype="any" access="public" output="false" hint="Fetches the requested record by primary key and returns it as an object. Returns `false` if no record is found. You can override this behavior to return a `cfquery` result set instead, similar to what's described in the documentation for @findOne."
  270. examples=
  271. '
  272. <!--- Getting the author with the primary key value `99` as an object --->
  273. <cfset auth = model("author").findByKey(99)>
  274. <!--- Getting an author based on a form/URL value and then checking if it was found --->
  275. <cfset auth = model("author").findByKey(params.key)>
  276. <cfif NOT IsObject(auth)>
  277. <cfset flashInsert(message="Author ##params.key## was not found")>
  278. <cfset redirectTo(back=true)>
  279. </cfif>
  280. <!--- If you have a `belongsTo` association setup from `comment` to `post`, you can do a scoped call. (The `post` method below will call `model("post").findByKey(comment.postId)` internally) --->
  281. <cfset comment = model("comment").findByKey(params.commentId)>
  282. <cfset post = comment.post()>
  283. '
  284. categories="model-class,read" chapters="reading-records,associations" functions="belongsTo,findAll,findOne">
  285. <cfargument name="key" type="any" required="true" hint="Primary key value(s) of the record to fetch. Separate with comma if passing in multiple primary key values. Accepts a string, list, or a numeric value.">
  286. <cfargument name="select" type="string" required="false" default="" hint="See documentation for @findAll.">
  287. <cfargument name="include" type="string" required="false" default="" hint="See documentation for @findAll.">
  288. <cfargument name="cache" type="any" required="false" default="" hint="See documentation for @findAll.">
  289. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  290. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  291. <cfargument name="returnAs" type="string" required="false" hint="See documentation for @findOne.">
  292. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  293. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  294. <cfscript>
  295. var returnValue = "";
  296. $args(name="findByKey", args=arguments);
  297. if (Len(arguments.key))
  298. {
  299. $keyLengthCheck(arguments.key);
  300. }
  301. // convert primary key column name(s) / value(s) to a WHERE clause that is then used in the findOne call
  302. arguments.where = $keyWhereString(values=arguments.key);
  303. StructDelete(arguments, "key");
  304. returnValue = findOne(argumentCollection=arguments);
  305. </cfscript>
  306. <cfreturn returnValue>
  307. </cffunction>
  308. <cffunction name="findOne" returntype="any" access="public" output="false" hint="Fetches the first record found based on the `WHERE` and `ORDER BY` clauses. With the default settings (i.e. the `returnAs` argument set to `object`), a model object will be returned if the record is found and the boolean value `false` if not. Instead of using the `where` argument, you can create cleaner code by making use of a concept called dynamic finders."
  309. examples=
  310. '
  311. <!--- Getting the most recent order as an object from the database --->
  312. <cfset order = model("order").findOne(order="datePurchased DESC")>
  313. <!--- Using a dynamic finder to get the first person with the last name `Smith`. Same as calling `model("user").findOne(where"lastName=''Smith''")` --->
  314. <cfset person = model("user").findOneByLastName("Smith")>
  315. <!--- Getting a specific user using a dynamic finder. Same as calling `model("user").findOne(where"email=''someone@somewhere.com'' AND password=''mypass''")` --->
  316. <cfset user = model("user").findOneByEmailAndPassword("someone@somewhere.com,mypass")>
  317. <!--- If you have a `hasOne` association setup from `user` to `profile`, you can do a scoped call. (The `profile` method below will call `model("profile").findOne(where="userId=##user.id##")` internally) --->
  318. <cfset user = model("user").findByKey(params.userId)>
  319. <cfset profile = user.profile()>
  320. <!--- If you have a `hasMany` association setup from `post` to `comment`, you can do a scoped call. (The `findOneComment` method below will call `model("comment").findOne(where="postId=##post.id##")` internally) --->
  321. <cfset post = model("post").findByKey(params.postId)>
  322. <cfset comment = post.findOneComment(where="text=''I Love Wheels!''")>
  323. '
  324. categories="model-class,read" chapters="reading-records,associations" functions="findAll,findByKey,hasMany,hasOne">
  325. <cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
  326. <cfargument name="order" type="string" required="false" default="" hint="See documentation for @findAll.">
  327. <cfargument name="select" type="string" required="false" default="" hint="See documentation for @findAll.">
  328. <cfargument name="include" type="string" required="false" default="" hint="See documentation for @findAll.">
  329. <cfargument name="cache" type="any" required="false" default="" hint="See documentation for @findAll.">
  330. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  331. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  332. <cfargument name="returnAs" type="string" required="false" hint="Set this to `query` to return as a single-row query result set. Set this to `object` to return as an object.">
  333. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  334. <cfscript>
  335. var returnValue = "";
  336. $args(name="findOne", args=arguments);
  337. if (!Len(arguments.include) || (StructKeyExists(variables.wheels.class.associations, arguments.include) && variables.wheels.class.associations[arguments.include].type != "hasMany"))
  338. {
  339. // no joins will be done or the join will be done to a single record so we can safely get just one record from the database
  340. // note that the check above can be improved to go through the entire include string and check if all associations are "single" (i.e. hasOne or belongsTo)
  341. arguments.maxRows = 1;
  342. }
  343. else
  344. {
  345. // since we're joining with associated tables (and not to just one record) we could potentially get duplicate records for one object and we work around this by using the pagination code which has this functionality built in
  346. arguments.page = 1;
  347. arguments.perPage = 1;
  348. arguments.count = 1;
  349. }
  350. returnValue = findAll(argumentCollection=arguments);
  351. if (IsArray(returnValue))
  352. {
  353. if (ArrayLen(returnValue))
  354. returnValue = returnValue[1];
  355. else
  356. returnValue = false;
  357. }
  358. </cfscript>
  359. <cfreturn returnValue>
  360. </cffunction>
  361. <!--- update --->
  362. <cffunction name="updateAll" returntype="numeric" access="public" output="false" hint="Updates all properties for the records that match the `where` argument. Property names and values can be passed in either using named arguments or as a struct to the `properties` argument. By default, objects will not be instantiated and therefore callbacks and validations are not invoked. You can change this behavior by passing in `instantiate=true`. This method returns the number of records that were updated."
  363. examples=
  364. '
  365. <!--- Update the `published` and `publishedAt` properties for all records that have `published=0` --->
  366. <cfset recordsUpdated = model("post").updateAll(published=1, publishedAt=Now(), where="published=0")>
  367. <!--- If you have a `hasMany` association setup from `post` to `comment`, you can do a scoped call. (The `removeAllComments` method below will call `model("comment").updateAll(postid="", where="postId=##post.id##")` internally.) --->
  368. <cfset aPost = model("post").findByKey(params.postId)>
  369. <cfset removedSuccessfully = aPost.removeAllComments()>
  370. '
  371. categories="model-class,update" chapters="updating-records,associations" functions="hasMany,update,updateByKey,updateOne">
  372. <cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
  373. <cfargument name="include" type="string" required="false" default="" hint="See documentation for @findAll.">
  374. <cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
  375. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  376. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  377. <cfargument name="instantiate" type="boolean" required="false" hint="Whether or not to instantiate the object(s) first. When objects are not instantiated, any callbacks and validations set on them will be skipped.">
  378. <cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
  379. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  380. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  381. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  382. <cfscript>
  383. var loc = {};
  384. $args(name="updateAll", args=arguments);
  385. arguments.properties = $setProperties(argumentCollection=arguments, filterList="where,include,properties,reload,parameterize,instantiate,validate,transaction,callbacks,includeSoftDeletes", setOnModel=false);
  386. if (arguments.instantiate) // find and instantiate each object and call its update function
  387. {
  388. loc.returnValue = 0;
  389. loc.objects = findAll(select=propertyNames(), where=arguments.where, include=arguments.include, reload=arguments.reload, parameterize=arguments.parameterize, callbacks=arguments.callbacks, includeSoftDeletes=arguments.includeSoftDeletes, returnIncluded=false, returnAs="objects");
  390. for (loc.i=1; loc.i lte ArrayLen(loc.objects); loc.i++)
  391. {
  392. if (loc.objects[loc.i].update(properties=arguments.properties, parameterize=arguments.parameterize, transaction=arguments.transaction, callbacks=arguments.callbacks))
  393. loc.returnValue = loc.returnValue + 1;
  394. }
  395. }
  396. else
  397. {
  398. arguments.sql = [];
  399. ArrayAppend(arguments.sql, "UPDATE #tableName()# SET");
  400. loc.pos = 0;
  401. for (loc.key in arguments.properties)
  402. {
  403. loc.pos = loc.pos + 1;
  404. ArrayAppend(arguments.sql, "#variables.wheels.class.properties[loc.key].column# = ");
  405. loc.param = {value=arguments.properties[loc.key], type=variables.wheels.class.properties[loc.key].type, dataType=variables.wheels.class.properties[loc.key].dataType, scale=variables.wheels.class.properties[loc.key].scale, null=!len(arguments.properties[loc.key])};
  406. ArrayAppend(arguments.sql, loc.param);
  407. if (StructCount(arguments.properties) gt loc.pos)
  408. ArrayAppend(arguments.sql, ",");
  409. }
  410. arguments.sql = $addWhereClause(sql=arguments.sql, where=arguments.where, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
  411. arguments.sql = $addWhereClauseParameters(sql=arguments.sql, where=arguments.where);
  412. loc.returnValue = invokeWithTransaction(method="$updateAll", argumentCollection=arguments);
  413. }
  414. </cfscript>
  415. <cfreturn loc.returnValue>
  416. </cffunction>
  417. <cffunction name="$updateAll" returntype="numeric" access="public" output="false">
  418. <cfset var update = variables.wheels.class.adapter.$query(sql=arguments.sql, parameterize=arguments.parameterize)>
  419. <cfreturn update.result.recordCount>
  420. </cffunction>
  421. <cffunction name="updateByKey" returntype="boolean" access="public" output="false" hint="Finds the object with the supplied key and saves it (if validation permits it) with the supplied properties and/or named arguments. Property names and values can be passed in either using named arguments or as a struct to the `properties` argument. Returns `true` if the object was found and updated successfully, `false` otherwise."
  422. examples=
  423. '
  424. <!--- Updates the object with `33` as the primary key value with values passed in through the URL/form --->
  425. <cfset result = model("post").updateByKey(33, params.post)>
  426. <!--- Updates the object with `33` as the primary key using named arguments --->
  427. <cfset result = model("post").updateByKey(key=33, title="New version of Wheels just released", published=1)>
  428. '
  429. categories="model-class,update" chapters="updating-records,associations" functions="hasOne,hasMany,update,updateAll,updateOne">
  430. <cfargument name="key" type="any" required="true" hint="See documentation for @findByKey.">
  431. <cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
  432. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  433. <cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
  434. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  435. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  436. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  437. <cfscript>
  438. var returnValue = "";
  439. $args(name="updateByKey", args=arguments);
  440. $keyLengthCheck(arguments.key);
  441. arguments.where = $keyWhereString(values=arguments.key);
  442. StructDelete(arguments, "key");
  443. returnValue = updateOne(argumentCollection=arguments);
  444. </cfscript>
  445. <cfreturn returnValue>
  446. </cffunction>
  447. <cffunction name="updateOne" returntype="boolean" access="public" output="false" hint="Gets an object based on the arguments used and updates it with the supplied properties. Returns `true` if an object was found and updated successfully, `false` otherwise."
  448. examples=
  449. '
  450. <!--- Sets the `new` property to `1` on the most recently released product --->
  451. <cfset result = model("product").updateOne(order="releaseDate DESC", new=1)>
  452. <!--- If you have a `hasOne` association setup from `user` to `profile`, you can do a scoped call. (The `removeProfile` method below will call `model("profile").updateOne(where="userId=##aUser.id##", userId="")` internally.) --->
  453. <cfset aUser = model("user").findByKey(params.userId)>
  454. <cfset aUser.removeProfile()>
  455. '
  456. categories="model-class,update" chapters="updating-records,associations" functions="hasOne,update,updateAll,updateByKey">
  457. <cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
  458. <cfargument name="order" type="string" required="false" default="" hint="See documentation for @findAll.">
  459. <cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
  460. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  461. <cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
  462. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  463. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  464. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  465. <cfscript>
  466. var loc = {};
  467. $args(name="updateOne", args=arguments);
  468. loc.object = findOne(where=arguments.where, order=arguments.order, reload=arguments.reload, includeSoftDeletes=arguments.includeSoftDeletes);
  469. StructDelete(arguments, "where");
  470. StructDelete(arguments, "order");
  471. if (IsObject(loc.object))
  472. loc.returnValue = loc.object.update(argumentCollection=arguments);
  473. else
  474. loc.returnValue = false;
  475. </cfscript>
  476. <cfreturn loc.returnValue>
  477. </cffunction>
  478. <cffunction name="updateProperty" returntype="boolean" access="public" output="false" hint="Updates a single property and saves the record without going through the normal validation procedure. This is especially useful for boolean flags on existing records."
  479. examples=
  480. '
  481. <!--- Sets the `new` property to `1` through updateProperty() --->
  482. <cfset product = model("product").findByKey(56)>
  483. <cfset product.updateProperty("new", 1)>
  484. '
  485. categories="model-class,update" chapters="updating-records,associations" functions="hasOne,update,updateAll,updateByKey,updateProperties">
  486. <cfargument name="property" type="string" required="true" hint="Name of the property to update the value for globally.">
  487. <cfargument name="value" type="any" required="true" hint="Value to set on the given property globally.">
  488. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  489. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  490. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  491. <cfscript>
  492. $args(name="updateProperty", args=arguments);
  493. arguments.validate = false;
  494. this[arguments.property] = arguments.value;
  495. </cfscript>
  496. <cfreturn save(parameterize=arguments.parameterize, reload=false, validate=arguments.validate, transaction=arguments.transaction, callbacks=arguments.callbacks) />
  497. </cffunction>
  498. <cffunction name="updateProperties" returntype="boolean" access="public" output="false" hint="Updates all the properties from the `properties` argument or other named arguments. If the object is invalid, the save will fail and `false` will be returned."
  499. examples=
  500. '
  501. <!--- Sets the `new` property to `1` through `updateProperties()` --->
  502. <cfset product = model("product").findByKey(56)>
  503. <cfset product.updateProperties(new=1)>
  504. '
  505. categories="model-class,update" chapters="updating-records,associations" functions="hasOne,update,updateAll,updateByKey,updateProperties">
  506. <cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="Struct containing key/value pairs with properties and associated values that need to be updated globally.">
  507. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  508. <cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
  509. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  510. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  511. <cfscript>
  512. $args(name="updateProperties", args=arguments);
  513. $setProperties(argumentCollection=arguments, filterList="properties,parameterize,validate,transaction,callbacks");
  514. </cfscript>
  515. <cfreturn save(parameterize=arguments.parameterize, reload=false, validate=arguments.validate, transaction=arguments.transaction, callbacks=arguments.callbacks) />
  516. </cffunction>
  517. <!--- delete --->
  518. <cffunction name="deleteAll" returntype="numeric" access="public" output="false" hint="Deletes all records that match the `where` argument. By default, objects will not be instantiated and therefore callbacks and validations are not invoked. You can change this behavior by passing in `instantiate=true`. Returns the number of records that were deleted."
  519. examples=
  520. '
  521. <!--- Delete all inactive users without instantiating them (will skip validation and callbacks) --->
  522. <cfset recordsDeleted = model("user").deleteAll(where="inactive=1", instantiate=false)>
  523. <!--- If you have a `hasMany` association setup from `post` to `comment`, you can do a scoped call. (The `deleteAllComments` method below will call `model("comment").deleteAll(where="postId=##post.id##")` internally.) --->
  524. <cfset post = model("post").findByKey(params.postId)>
  525. <cfset howManyDeleted = post.deleteAllComments()>
  526. '
  527. categories="model-class,delete" chapters="deleting-records,associations" functions="delete,deleteByKey,deleteOne,hasMany">
  528. <cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
  529. <cfargument name="include" type="string" required="false" default="" hint="See documentation for @findAll.">
  530. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  531. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  532. <cfargument name="instantiate" type="boolean" required="false" hint="See documentation for @updateAll.">
  533. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  534. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  535. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  536. <cfargument name="softDelete" type="boolean" required="false" default="true" hint="See documentation for @delete.">
  537. <cfscript>
  538. var loc = {};
  539. $args(name="deleteAll", args=arguments);
  540. if (arguments.instantiate)
  541. {
  542. loc.returnValue = 0;
  543. loc.objects = findAll(select=propertyNames(), where=arguments.where, include=arguments.include, reload=arguments.reload, parameterize=arguments.parameterize, includeSoftDeletes=arguments.includeSoftDeletes, returnIncluded=false, returnAs="objects");
  544. for (loc.i=1; loc.i lte ArrayLen(loc.objects); loc.i++)
  545. {
  546. if (loc.objects[loc.i].delete(parameterize=arguments.parameterize, transaction=arguments.transaction, callbacks=arguments.callbacks, softDelete=arguments.softDelete))
  547. loc.returnValue++;
  548. }
  549. }
  550. else
  551. {
  552. arguments.sql = [];
  553. arguments.sql = $addDeleteClause(sql=arguments.sql, softDelete=arguments.softDelete);
  554. arguments.sql = $addWhereClause(sql=arguments.sql, where=arguments.where, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
  555. arguments.sql = $addWhereClauseParameters(sql=arguments.sql, where=arguments.where);
  556. loc.returnValue = invokeWithTransaction(method="$deleteAll", argumentCollection=arguments);
  557. }
  558. </cfscript>
  559. <cfreturn loc.returnValue>
  560. </cffunction>
  561. <cffunction name="$deleteAll" returntype="numeric" access="public" output="false">
  562. <cfset var delete = variables.wheels.class.adapter.$query(sql=arguments.sql, parameterize=arguments.parameterize)>
  563. <cfreturn delete.result.recordCount>
  564. </cffunction>
  565. <cffunction name="deleteByKey" returntype="boolean" access="public" output="false" hint="Finds the record with the supplied key and deletes it. Returns `true` on successful deletion of the row, `false` otherwise."
  566. examples=
  567. '
  568. <!--- Delete the user with the primary key value of `1` --->
  569. <cfset result = model("user").deleteByKey(1)>
  570. '
  571. categories="model-class,delete" chapters="deleting-records" functions="delete,deleteAll,deleteOne">
  572. <cfargument name="key" type="any" required="true" hint="See documentation for @findByKey.">
  573. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  574. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  575. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  576. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  577. <cfargument name="softDelete" type="boolean" required="false" default="true" hint="See documentation for @delete.">
  578. <cfscript>
  579. var loc = {};
  580. $args(name="deleteByKey", args=arguments);
  581. $keyLengthCheck(arguments.key);
  582. loc.where = $keyWhereString(values=arguments.key);
  583. loc.returnValue = deleteOne(where=loc.where, reload=arguments.reload, transaction=arguments.transaction, callbacks=arguments.callbacks, includeSoftDeletes=arguments.includeSoftDeletes, softDelete=arguments.softDelete);
  584. </cfscript>
  585. <cfreturn loc.returnValue>
  586. </cffunction>
  587. <cffunction name="deleteOne" returntype="boolean" access="public" output="false" hint="Gets an object based on conditions and deletes it."
  588. examples=
  589. '
  590. <!--- Delete the user that signed up last --->
  591. <cfset result = model("user").deleteOne(order="signupDate DESC")>
  592. <!--- If you have a `hasOne` association setup from `user` to `profile` you can do a scoped call (the `deleteProfile` method below will call `model("profile").deleteOne(where="userId=##aUser.id##")` internally) --->
  593. <cfset aUser = model("user").findByKey(params.userId)>
  594. <cfset aUser.deleteProfile()>
  595. '
  596. categories="model-class,delete" chapters="deleting-records,associations" functions="delete,deleteAll,deleteOne,hasOne">
  597. <cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
  598. <cfargument name="order" type="string" required="false" default="" hint="See documentation for @findAll.">
  599. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  600. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  601. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  602. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  603. <cfargument name="softDelete" type="boolean" required="false" default="true" hint="See documentation for @delete.">
  604. <cfscript>
  605. var loc = {};
  606. $args(name="deleteOne", args=arguments);
  607. loc.object = findOne(where=arguments.where, order=arguments.order, reload=arguments.reload, includeSoftDeletes=arguments.includeSoftDeletes);
  608. if (IsObject(loc.object))
  609. loc.returnValue = loc.object.delete(transaction=arguments.transaction, callbacks=arguments.callbacks, softDelete=arguments.softDelete);
  610. else
  611. loc.returnValue = false;
  612. </cfscript>
  613. <cfreturn loc.returnValue>
  614. </cffunction>
  615. <!--- other --->
  616. <cffunction name="exists" returntype="boolean" access="public" output="false" hint="Checks if a record exists in the table. You can pass in either a primary key value to the `key` argument or a string to the `where` argument."
  617. examples=
  618. '
  619. <!--- Checking if Joe exists in the database --->
  620. <cfset result = model("user").exists(where="firstName=''Joe''")>
  621. <!--- Checking if a specific user exists based on a primary key valued passed in through the URL/form in an if statement --->
  622. <cfif model("user").exists(keyparams.key)>
  623. <!--- Do something... --->
  624. </cfif>
  625. <!--- If you have a `belongsTo` association setup from `comment` to `post`, you can do a scoped call. (The `hasPost` method below will call `model("post").exists(comment.postId)` internally.) --->
  626. <cfset comment = model("comment").findByKey(params.commentId)>
  627. <cfset commentHasAPost = comment.hasPost()>
  628. <!--- If you have a `hasOne` association setup from `user` to `profile`, you can do a scoped call. (The `hasProfile` method below will call `model("profile").exists(where="userId=##user.id##")` internally.) --->
  629. <cfset user = model("user").findByKey(params.userId)>
  630. <cfset userHasProfile = user.hasProfile()>
  631. <!--- If you have a `hasMany` association setup from `post` to `comment`, you can do a scoped call. (The `hasComments` method below will call `model("comment").exists(where="postid=##post.id##")` internally.) --->
  632. <cfset post = model("post").findByKey(params.postId)>
  633. <cfset postHasComments = post.hasComments()>
  634. '
  635. categories="model-class,miscellaneous" chapters="reading-records,associations" functions="belongsTo,hasMany,hasOne">
  636. <cfargument name="key" type="any" required="false" default="" hint="See documentation for @findByKey.">
  637. <cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
  638. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  639. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  640. <cfscript>
  641. var loc = {};
  642. $args(name="exists", args=arguments);
  643. if (application.wheels.showErrorInformation)
  644. if (Len(arguments.key) && Len(arguments.where))
  645. $throw(type="Wheels.IncorrectArguments", message="You cannot pass in both `key` and `where`.");
  646. if (Len(arguments.where))
  647. loc.returnValue = findOne(select=primaryKey(), where=arguments.where, reload=arguments.reload, returnAs="query").RecordCount gte 1;
  648. else if (Len(arguments.key))
  649. loc.returnValue = findByKey(key=arguments.key, select=primaryKey(), reload=arguments.reload, returnAs="query").RecordCount == 1;
  650. else
  651. loc.returnValue = false;
  652. </cfscript>
  653. <cfreturn loc.returnValue>
  654. </cffunction>
  655. <!--- PUBLIC MODEL OBJECT METHODS --->
  656. <!--- crud --->
  657. <cffunction name="delete" returntype="boolean" access="public" output="false" hint="Deletes the object, which means the row is deleted from the database (unless prevented by a `beforeDelete` callback). Returns `true` on successful deletion of the row, `false` otherwise."
  658. examples=
  659. '
  660. <!--- Get a post object and then delete it from the database --->
  661. <cfset post = model("post").findByKey(33)>
  662. <cfset post.delete()>
  663. <!--- If you have a `hasMany` association setup from `post` to `comment`, you can do a scoped call. (The `deleteComment` method below will call `comment.delete()` internally.) --->
  664. <cfset post = model("post").findByKey(params.postId)>
  665. <cfset comment = model("comment").findByKey(params.commentId)>
  666. <cfset post.deleteComment(comment)>
  667. '
  668. categories="model-object,crud" chapters="deleting-records,associations" functions="deleteAll,deleteByKey,deleteOne,hasMany">
  669. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  670. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  671. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  672. <cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
  673. <cfargument name="softDelete" type="boolean" required="false" default="true" hint="Set to `false` to permanently delete a record, even if it has a soft delete column.">
  674. <cfscript>
  675. $args(name="delete", args=arguments);
  676. arguments.sql = [];
  677. arguments.sql = $addDeleteClause(sql=arguments.sql, softDelete=arguments.softDelete);
  678. arguments.sql = $addKeyWhereClause(sql=arguments.sql, includeSoftDeletes=arguments.includeSoftDeletes);
  679. </cfscript>
  680. <cfreturn invokeWithTransaction(method="$delete", argumentCollection=arguments)>
  681. </cffunction>
  682. <cffunction name="$delete" returntype="boolean" access="public" output="false">
  683. <cfscript>
  684. var loc = {};
  685. if ($callback("beforeDelete", arguments.callbacks))
  686. {
  687. $deleteDependents(); // delete dependents before the main record in case of foreign key constraints
  688. loc.del = variables.wheels.class.adapter.$query(sql=arguments.sql, parameterize=arguments.parameterize);
  689. if (loc.del.result.recordCount eq 1 and $callback("afterDelete", arguments.callbacks))
  690. return true;
  691. }
  692. return false;
  693. </cfscript>
  694. </cffunction>
  695. <cffunction name="reload" returntype="void" access="public" output="false" hint="Reloads the property values of this object from the database."
  696. examples=
  697. '
  698. <!--- Get an object, call a method on it that could potentially change values, and then reload the values from the database --->
  699. <cfset employee = model("employee").findByKey(params.key)>
  700. <cfset employee.someCallThatChangesValuesInTheDatabase()>
  701. <cfset employee.reload()>
  702. '
  703. categories="model-object,miscellaneous" chapters="reading-records" functions="">
  704. <cfscript>
  705. var loc = {};
  706. loc.query = findByKey(key=key(), reload=true, returnAs="query");
  707. loc.properties = propertyNames();
  708. loc.iEnd = ListLen(loc.properties);
  709. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  710. {
  711. // coldfusion has a problem with blank boolean values in the query
  712. try
  713. {
  714. loc.property = ListGetAt(loc.properties, loc.i);
  715. this[loc.property] = loc.query[loc.property][1];
  716. }
  717. catch (Any e)
  718. {
  719. this[loc.property] = "";
  720. }
  721. }
  722. </cfscript>
  723. </cffunction>
  724. <cffunction name="save" returntype="boolean" access="public" output="false" hint="Saves the object if it passes validation and callbacks. Returns `true` if the object was saved successfully to the database, `false` if not."
  725. examples=
  726. '
  727. <!--- Save the user object to the database (will automatically do an `INSERT` or `UPDATE` statement depending on if the record is new or already exists --->
  728. <cfset user.save()>
  729. <!--- Save the user object directly in an if statement without using `cfqueryparam` and take appropriate action based on the result --->
  730. <cfif user.save(parameterize=false)>
  731. <cfset flashInsert(notice="The user was saved!")>
  732. <cfset redirectTo(action="edit")>
  733. <cfelse>
  734. <cfset flashInsert(alert="Error, please correct!")>
  735. <cfset renderPage(action="edit")>
  736. </cfif>
  737. '
  738. categories="model-object,crud" chapters="creating-records" functions="">
  739. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  740. <cfargument name="reload" type="boolean" required="false" hint="Set to `true` to reload the object from the database once an insert/update has completed.">
  741. <cfargument name="validate" type="boolean" required="false" default="true" hint="Set to `false` to skip validations for this operation.">
  742. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="Set this to `commit` to update the database when the save has completed, `rollback` to run all the database queries but not commit them, or `none` to skip transaction handling altogether.">
  743. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="Set to `false` to disable callbacks for this operation.">
  744. <cfset $args(name="save", args=arguments)>
  745. <cfset clearErrors()>
  746. <cfreturn invokeWithTransaction(method="$save", argumentCollection=arguments)>
  747. </cffunction>
  748. <cffunction name="$save" returntype="boolean" access="public" output="false">
  749. <cfargument name="parameterize" type="any" required="true" />
  750. <cfargument name="reload" type="boolean" required="true" />
  751. <cfargument name="validate" type="boolean" required="true" />
  752. <cfargument name="callbacks" type="boolean" required="true" />
  753. <cfscript>
  754. // make sure all of our associations are set properly before saving
  755. $setAssociations();
  756. if ($callback("beforeValidation", arguments.callbacks))
  757. {
  758. if (isNew())
  759. {
  760. if ($validateAssociations() && $callback("beforeValidationOnCreate", arguments.callbacks) && $validate("onSave,onCreate", arguments.validate) && $callback("afterValidation", arguments.callbacks) && $callback("afterValidationOnCreate", arguments.callbacks) && $callback("beforeSave", arguments.callbacks) && $callback("beforeCreate", arguments.callbacks))
  761. {
  762. $create(parameterize=arguments.parameterize, reload=arguments.reload);
  763. if ($saveAssociations(argumentCollection=arguments) && $callback("afterCreate", arguments.callbacks) && $callback("afterSave", arguments.callbacks))
  764. {
  765. $updatePersistedProperties();
  766. return true;
  767. }
  768. }
  769. }
  770. else
  771. {
  772. if ($callback("beforeValidationOnUpdate", arguments.callbacks) && $validate("onSave,onUpdate", arguments.validate) && $callback("afterValidation", arguments.callbacks) && $callback("afterValidationOnUpdate", arguments.callbacks) && $saveAssociations(argumentCollection=arguments) && $callback("beforeSave", arguments.callbacks) && $callback("beforeUpdate", arguments.callbacks))
  773. {
  774. $update(parameterize=arguments.parameterize, reload=arguments.reload);
  775. if ($callback("afterUpdate", arguments.callbacks) && $callback("afterSave", arguments.callbacks))
  776. {
  777. $updatePersistedProperties();
  778. return true;
  779. }
  780. }
  781. }
  782. }
  783. </cfscript>
  784. <cfreturn false />
  785. </cffunction>
  786. <cffunction name="update" returntype="boolean" access="public" output="false" hint="Updates the object with the supplied properties and saves it to the database. Returns `true` if the object was saved successfully to the database and `false` otherwise."
  787. examples=
  788. '
  789. <!--- Get a post object and then update its title in the database --->
  790. <cfset post = model("post").findByKey(33)>
  791. <cfset post.update(title="New version of Wheels just released")>
  792. <!--- Get a post object and then update its title and other properties based on what is pased in from the URL/form --->
  793. <cfset post = model("post").findByKey(params.key)>
  794. <cfset post.update(title="New version of Wheels just released", properties=params.post)>
  795. <!--- If you have a `hasOne` association setup from `author` to `bio`, you can do a scoped call. (The `setBio` method below will call `bio.update(authorId=anAuthor.id)` internally.) --->
  796. <cfset author = model("author").findByKey(params.authorId)>
  797. <cfset bio = model("bio").findByKey(params.bioId)>
  798. <cfset author.setBio(bio)>
  799. <!--- If you have a `hasMany` association setup from `owner` to `car`, you can do a scoped call. (The `addCar` method below will call `car.update(ownerId=anOwner.id)` internally.) --->
  800. <cfset anOwner = model("owner").findByKey(params.ownerId)>
  801. <cfset aCar = model("car").findByKey(params.carId)>
  802. <cfset anOwner.addCar(aCar)>
  803. <!--- If you have a `hasMany` association setup from `post` to `comment`, you can do a scoped call. (The `removeComment` method below will call `comment.update(postId="")` internally.) --->
  804. <cfset aPost = model("post").findByKey(params.postId)>
  805. <cfset aComment = model("comment").findByKey(params.commentId)>
  806. <cfset aPost.removeComment(aComment)>
  807. '
  808. categories="model-object,crud" chapters="updating-records,associations" functions="hasMany,hasOne,updateAll,updateByKey,updateOne">
  809. <cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
  810. <cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
  811. <cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
  812. <cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
  813. <cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
  814. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  815. <cfset $args(name="update", args=arguments)>
  816. <cfset $setProperties(argumentCollection=arguments, filterList="properties,parameterize,reload,validate,transaction,callbacks")>
  817. <cfreturn save(parameterize=arguments.parameterize, reload=arguments.reload, validate=arguments.validate, transaction=arguments.transaction, callbacks=arguments.callbacks)>
  818. </cffunction>
  819. <!--- other --->
  820. <cffunction name="isNew" returntype="boolean" access="public" output="false" hint="Returns `true` if this object hasn't been saved yet. (In other words, no matching record exists in the database yet.) Returns `false` if a record exists."
  821. examples=
  822. '
  823. <!--- Create a new object and then check if it is new (yes, this example is ridiculous. It makes more sense in the context of callbacks for example) --->
  824. <cfset employee = model("employee").new()>
  825. <cfif employee.isNew()>
  826. <!--- Do something... --->
  827. </cfif>
  828. '
  829. categories="model-object,miscellaneous" chapters="" functions="">
  830. <cfscript>
  831. // if no values have ever been saved to the database this object is new
  832. if (!StructKeyExists(variables, "$persistedProperties"))
  833. {
  834. return true;
  835. }
  836. return false;
  837. </cfscript>
  838. </cffunction>
  839. <!--- PRIVATE MODEL CLASS METHODS --->
  840. <!--- other --->
  841. <cffunction name="$createInstance" returntype="any" access="public" output="false">
  842. <cfargument name="properties" type="struct" required="true">
  843. <cfargument name="persisted" type="boolean" required="true">
  844. <cfargument name="row" type="numeric" required="false" default="1">
  845. <cfargument name="base" type="boolean" required="false" default="true">
  846. <cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
  847. <cfscript>
  848. var loc = {};
  849. loc.fileName = $objectFileName(name=variables.wheels.class.modelName, objectPath=variables.wheels.class.path, type="model");
  850. loc.returnValue = $createObjectFromRoot(path=variables.wheels.class.path, fileName=loc.fileName, method="$initModelObject", name=variables.wheels.class.modelName, properties=arguments.properties, persisted=arguments.persisted, row=arguments.row, base=arguments.base, useFilterLists=(!arguments.persisted));
  851. // if the object should be persisted, call afterFind else call afterNew
  852. if ((arguments.persisted && loc.returnValue.$callback("afterFind", arguments.callbacks)) || (!arguments.persisted && loc.returnValue.$callback("afterNew", arguments.callbacks)))
  853. loc.returnValue.$callback("afterInitialization", arguments.callbacks);
  854. </cfscript>
  855. <cfreturn loc.returnValue>
  856. </cffunction>
  857. <!--- PRIVATE MODEL OBJECT METHODS --->
  858. <!--- crud --->
  859. <cffunction name="$create" returntype="boolean" access="public" output="false">
  860. <cfargument name="parameterize" type="any" required="true">
  861. <cfargument name="reload" type="boolean" required="true">
  862. <cfscript>
  863. var loc = {};
  864. if (variables.wheels.class.timeStampingOnCreate)
  865. $timestampProperty(property=variables.wheels.class.timeStampOnCreateProperty);
  866. if (application.wheels.setUpdatedAtOnCreate && variables.wheels.class.timeStampingOnUpdate)
  867. $timestampProperty(property=variables.wheels.class.timeStampOnUpdateProperty);
  868. loc.sql = [];
  869. loc.sql2 = [];
  870. ArrayAppend(loc.sql, "INSERT INTO #tableName()# (");
  871. ArrayAppend(loc.sql2, " VALUES (");
  872. for (loc.key in variables.wheels.class.properties)
  873. {
  874. if (StructKeyExists(this, loc.key))
  875. {
  876. ArrayAppend(loc.sql, variables.wheels.class.properties[loc.key].column);
  877. ArrayAppend(loc.sql, ",");
  878. loc.param = {value=this[loc.key], type=variables.wheels.class.properties[loc.key].type, dataType=variables.wheels.class.properties[loc.key].dataType, scale=variables.wheels.class.properties[loc.key].scale, null=!len(this[loc.key])};
  879. ArrayAppend(loc.sql2, loc.param);
  880. ArrayAppend(loc.sql2, ",");
  881. }
  882. }
  883. ArrayDeleteAt(loc.sql, ArrayLen(loc.sql));
  884. ArrayDeleteAt(loc.sql2, ArrayLen(loc.sql2));
  885. ArrayAppend(loc.sql, ")");
  886. ArrayAppend(loc.sql2, ")");
  887. loc.iEnd = ArrayLen(loc.sql);
  888. for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
  889. ArrayAppend(loc.sql, loc.sql2[loc.i]);
  890. loc.ins = variables.wheels.class.adapter.$query(sql=loc.sql, parameterize=arguments.parameterize, $primaryKey=primaryKeys());
  891. loc.generatedKey = variables.wheels.class.adapter.$generatedKey();
  892. if (StructKeyExists(loc.ins.result, loc.generatedKey))
  893. this[primaryKeys(1)] = loc.ins.result[loc.generatedKey];
  894. if (arguments.reload)
  895. this.reload();
  896. </cfscript>
  897. <cfreturn true>
  898. </cffunction>
  899. <cffunction name="$update" returntype="boolean" access="public" output="false">
  900. <cfargument name="parameterize" type="any" required="true">
  901. <cfargument name="reload" type="boolean" required="true">
  902. <cfscript>
  903. var loc = {};
  904. if (variables.wheels.class.timeStampingOnUpdate)
  905. $timestampProperty(property=variables.wheels.class.timeStampOnUpdateProperty);
  906. loc.sql = [];
  907. ArrayAppend(loc.sql, "UPDATE #tableName()# SET ");
  908. for (loc.key in variables.wheels.class.properties)
  909. {
  910. // include all changed non-key values in the update
  911. if (StructKeyExists(this, loc.key) && !ListFindNoCase(primaryKeys(), loc.key) && hasChanged(loc.key))
  912. {
  913. ArrayAppend(loc.sql, "#variables.wheels.class.properties[loc.key].column# = ");
  914. loc.param = {value = this[loc.key], type = variables.wheels.class.properties[loc.key].type, dataType = variables.wheels.class.properties[loc.key].dataType, scale = variables.wheels.class.properties[loc.key].scale, null = !len(this[loc.key])};
  915. ArrayAppend(loc.sql, loc.param);
  916. ArrayAppend(loc.sql, ",");
  917. }
  918. }
  919. if (ArrayLen(loc.sql) gt 1) // only submit the update if we generated an sql set statement
  920. {
  921. ArrayDeleteAt(loc.sql, ArrayLen(loc.sql));
  922. loc.sql = $addKeyWhereClause(sql=loc.sql);
  923. loc.upd = variables.wheels.class.adapter.$query(sql=loc.sql, parameterize=arguments.parameterize);
  924. if (arguments.reload)
  925. this.reload();
  926. }
  927. </cfscript>
  928. <cfreturn true>
  929. </cffunction>
  930. <!--- other --->
  931. <cffunction name="$keyLengthCheck" returntype="void" access="public" output="false"
  932. hint="Makes sure that the number of keys passed in is the same as the number of keys defined for the model. If not, an error is raised.">
  933. <cfargument name="key" type="any" required="true">
  934. <cfscript>
  935. if (ListLen(primaryKeys()) != ListLen(arguments.key))
  936. {
  937. $throw(type="Wheels.InvalidArgumentValue", message="The `key` argument contains an invalid value.", extendedInfo="The `key` argument contains a list, however this table doesn't have a composite key. A list of values is allowed for the `key` argument, but this only applies in the case when the table contains a composite key.");
  938. }
  939. </cfscript>
  940. </cffunction>
  941. <!---
  942. developers can now override this method for localizing dates if they prefer.
  943. --->
  944. <cffunction name="$timestampProperty" returntype="void" access="public" output="false">
  945. <cfargument name="property" type="string" required="true" />
  946. <cfscript>
  947. this[arguments.property] = Now();
  948. </cfscript>
  949. </cffunction>