PageRenderTime 723ms CodeModel.GetById 521ms app.highlight 9ms RepoModel.GetById 122ms app.codeStats 1ms

/wheels/model/crud.cfm

http://cfwheels.googlecode.com/
ColdFusion | 1041 lines | 965 code | 39 blank | 37 comment | 107 complexity | a3896e23b531ae97d5cf4caff6380a41 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1<!--- PUBLIC MODEL CLASS METHODS --->
  2
  3<!--- create --->
  4
  5<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."
  6	examples=
  7	'
  8		<!--- Create a new author and save it to the database --->
  9		<cfset newAuthor = model("author").create(params.author)>
 10
 11		<!--- Same as above using named arguments --->
 12		<cfset newAuthor = model("author").create(firstName="John", lastName="Doe")>
 13
 14		<!--- Same as above using both named arguments and a struct --->
 15		<cfset newAuthor = model("author").create(active=1, properties=params.author)>
 16
 17		<!--- 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.) --->
 18		<cfset aCustomer = model("customer").findByKey(params.customerId)>
 19		<cfset anOrder = aCustomer.createOrder(shipping=params.shipping)>
 20	'
 21	categories="model-class,create" chapters="creating-records,associations" functions="hasOne,hasMany,new">
 22	<cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
 23	<cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
 24	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @save.">
 25	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
 26	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
 27	<cfscript>
 28		var loc = {};
 29		$args(name="create", args=arguments);
 30		loc.parameterize = arguments.parameterize;
 31		StructDelete(arguments, "parameterize");
 32		loc.returnValue = new(argumentCollection=arguments);
 33		loc.returnValue.save(parameterize=loc.parameterize, reload=arguments.reload, transaction=arguments.transaction, callbacks=arguments.callbacks);
 34	</cfscript>
 35	<cfreturn loc.returnValue>
 36</cffunction>
 37
 38<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."
 39	examples=
 40	'
 41		<!--- Create a new author in memory (not saved to the database) --->
 42		<cfset newAuthor = model("author").new()>
 43
 44		<!--- Create a new author based on properties in a struct --->
 45		<cfset newAuthor = model("author").new(params.authorStruct)>
 46
 47		<!--- Create a new author by passing in named arguments --->
 48		<cfset newAuthor = model("author").new(firstName="John", lastName="Doe")>
 49
 50		<!--- 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.) --->
 51		<cfset aCustomer = model("customer").findByKey(params.customerId)>
 52		<cfset anOrder = aCustomer.newOrder(shipping=params.shipping)>
 53	'
 54	categories="model-class,create" chapters="creating-records,associations" functions="create,hasMany,hasOne">
 55	<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).">
 56	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
 57	<cfscript>
 58		var loc = {};
 59		arguments.properties = $setProperties(argumentCollection=arguments, filterList="properties,reload,transaction,callbacks", setOnModel=false);
 60		loc.returnValue = $createInstance(properties=arguments.properties, persisted=false, callbacks=arguments.callbacks);
 61		loc.returnValue.$setDefaultValues();
 62	</cfscript>
 63	<cfreturn loc.returnValue>
 64</cffunction>
 65
 66<!--- read --->
 67
 68<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."
 69	examples=
 70	'
 71		<!--- Getting only 5 users and ordering them randomly --->
 72		<cfset fiveRandomUsers = model("user").findAll(maxRows=5, order="random")>
 73
 74		<!--- Including an association (which in this case needs to be setup as a `belongsTo` association to `author` on the `article` model first)  --->
 75		<cfset articles = model("article").findAll(where="published=1", order="createdAt DESC", include="author")>
 76
 77		<!--- 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) --->
 78		<cfset bobsArticles = model("author").findAll(where="firstName=''Bob''", include="articles")>
 79
 80		<!--- 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) --->
 81		<cfset songs = model("song").findAll(include="album(artist)", page=2, perPage=25)>
 82
 83		<!--- Using a dynamic finder to get all books released a certain year. Same as calling model("book").findOne(where="releaseYear=##params.year##") --->
 84		<cfset books = model("book").findAllByReleaseYear(params.year)>
 85
 86		<!--- 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##''") --->
 87		<cfset books = model("book").findAllByReleaseYearAndType("##params.year##,##params.type##")>
 88
 89		<!--- 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) --->
 90		<cfset post = model("post").findByKey(params.postId)>
 91		<cfset comments = post.comments()>
 92	'
 93	categories="model-class,read" chapters="reading-records,associations" functions="findByKey,findOne,hasMany">
 94	<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.">
 95	<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.">
 96	<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.">
 97	<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.">
 98	<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).">
 99	<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.">
100	<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.">
101	<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.">
102	<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.">
103	<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.">
104	<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.">
105	<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).">
106	<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.)">
107	<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.">
108	<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.">
109	<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.">
110	<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.">
111	<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.">
112	<cfargument name="$limit" type="numeric" required="false" default=0>
113	<cfargument name="$offset" type="numeric" required="false" default=0>
114	<cfscript>
115		var loc = {};
116		$args(name="findAll", args=arguments);
117
118		// we only allow direct associations to be loaded when returning objects
119		if (application.wheels.showErrorInformation && Len(arguments.returnAs) && arguments.returnAs != "query" && Find("(", arguments.include) && arguments.returnIncluded)
120			$throw(type="Wheels", message="Incorrect Arguments", extendedInfo="You may only include direct associations to this object when returning an array of objects.");
121
122		// count records and get primary keys for pagination
123		if (arguments.page)
124		{
125			if (application.wheels.showErrorInformation && arguments.perPage lte 0)
126				$throw(type="Wheels", message="Incorrect Argument", extendedInfo="The perPage argument should be a positive numeric value.");
127
128			if (Len(arguments.order))
129			{
130				// 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
131				loc.compareList = $listClean(ReplaceNoCase(ReplaceNoCase(arguments.order, " ASC", "", "all"), " DESC", "", "all"));
132				loc.iEnd = ListLen(primaryKeys());
133				for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
134				{
135					loc.iItem = primaryKeys(loc.i);
136					if (!ListFindNoCase(loc.compareList, loc.iItem) && !ListFindNoCase(loc.compareList, tableName() & "." & loc.iItem))
137						arguments.order = ListAppend(arguments.order, loc.iItem);
138				}
139			}
140			else
141			{
142				// we can't paginate without any order so we default to ascending ordering by the primary key column(s)
143				arguments.order = primaryKey();
144			}
145			if (Len(arguments.include))
146				loc.distinct = true;
147			else
148				loc.distinct = false;
149			if (arguments.count gt 0)
150				loc.totalRecords = arguments.count;
151			else
152				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);
153			loc.currentPage = arguments.page;
154			if (loc.totalRecords == 0)
155			{
156				loc.totalPages = 0;
157				loc.returnValue = "";
158			}
159			else
160			{
161				loc.totalPages = Ceiling(loc.totalRecords/arguments.perPage);
162				loc.limit = arguments.perPage;
163				loc.offset = (arguments.perPage * arguments.page) - arguments.perPage;
164
165				// if the full range of records is not requested we correct the limit to get the exact amount instead
166				// for example if totalRecords is 57, limit is 10 and offset 50 (i.e. requesting records 51-60) we change the limit to 7
167				if ((loc.limit + loc.offset) gt loc.totalRecords)
168					loc.limit = loc.totalRecords - loc.offset;
169
170				if (loc.limit < 1)
171				{
172					// if limit is 0 or less it means that a page that has no records was asked for so we return an empty query
173					loc.returnValue = "";
174				}
175				else
176				{
177					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);
178					if (loc.values.RecordCount) {
179						loc.paginationWhere = "";
180						loc.iEnd = ListLen(primaryKeys());
181						for (loc.i=1; loc.i <= loc.iEnd; loc.i++)
182						{
183							loc.property = primaryKeys(loc.i);
184							if (ListFindNoCase("integer,float", variables.wheels.class.properties[loc.property].validationtype))
185							{
186								loc.list = Evaluate("ValueList(loc.values.#loc.property#)");
187							}
188							else
189							{
190								loc.list = Evaluate("QuotedValueList(loc.values.#loc.property#)");
191							}
192							loc.paginationWhere = ListAppend(loc.paginationWhere, "#tableName()#.#loc.property# IN (#loc.list#)", Chr(7));
193						}
194						loc.paginationWhere = Replace(loc.paginationWhere, Chr(7), " AND ", "all");
195						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
196							arguments.where = "(" & arguments.where & ")" & " AND " & loc.paginationWhere;
197						else
198							arguments.where = loc.paginationWhere;
199					}
200				}
201			}
202			// store pagination info in the request scope so all pagination methods can access it
203			setPagination(loc.totalRecords, loc.currentPage, arguments.perPage, arguments.handle);
204		}
205
206		if (StructKeyExists(loc, "returnValue") && !Len(loc.returnValue))
207		{
208			if (arguments.returnAs == "query")
209				loc.returnValue = QueryNew("");
210			else if (singularize(arguments.returnAs) == arguments.returnAs)
211				loc.returnValue = false;
212			else
213				loc.returnValue = ArrayNew(1);
214		}
215		else if (!StructKeyExists(loc, "returnValue"))
216		{
217			// make the where clause generic for use in caching
218			loc.originalWhere = arguments.where;
219			arguments.where = REReplace(arguments.where, variables.wheels.class.RESQLWhere, "\1?\8" , "all");
220
221			// get info from cache when available, otherwise create the generic select, from, where and order by clause
222			loc.queryShellKey = $hashedKey(variables.wheels.class.modelName, arguments);
223			loc.sql = $getFromCache(key=loc.queryShellKey, category="schemas");
224			if (!IsArray(loc.sql))
225			{
226				loc.sql = [];
227				ArrayAppend(loc.sql, $selectClause(select=arguments.select, include=arguments.include, returnAs=arguments.returnAs));
228				ArrayAppend(loc.sql, $fromClause(include=arguments.include));
229				loc.sql = $addWhereClause(sql=loc.sql, where=loc.originalWhere, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
230				loc.groupBy = $groupByClause(select=arguments.select, group=arguments.group, include=arguments.include, distinct=arguments.distinct, returnAs=arguments.returnAs);
231				if (Len(loc.groupBy))
232					ArrayAppend(loc.sql, loc.groupBy);
233				loc.orderBy = $orderByClause(order=arguments.order, include=arguments.include);
234				if (Len(loc.orderBy))
235					ArrayAppend(loc.sql, loc.orderBy);
236				if (application.wheels.cacheModelInitialization)
237					$addToCache(key=loc.queryShellKey, value=loc.sql, category="schemas");
238			}
239
240			// add where clause parameters to the generic sql info
241			loc.sql = $addWhereClauseParameters(sql=loc.sql, where=loc.originalWhere);
242
243			// return existing query result if it has been run already in current request, otherwise pass off the sql array to the query
244			loc.queryKey = $hashedKey(variables.wheels.class.modelName, arguments, loc.originalWhere);
245			if (application.wheels.cacheQueriesDuringRequest && !arguments.reload && StructKeyExists(request.wheels, loc.queryKey))
246			{
247				loc.findAll = request.wheels[loc.queryKey];
248			}
249			else
250			{
251				loc.finderArgs = {};
252				loc.finderArgs.sql = loc.sql;
253				loc.finderArgs.maxRows = arguments.maxRows;
254				loc.finderArgs.parameterize = arguments.parameterize;
255				loc.finderArgs.limit = arguments.$limit;
256				loc.finderArgs.offset = arguments.$offset;
257				loc.finderArgs.$primaryKey = primaryKeys();
258				if (application.wheels.cacheQueries && (IsNumeric(arguments.cache) || (IsBoolean(arguments.cache) && arguments.cache)))
259					loc.finderArgs.cachedWithin = $timeSpanForCache(arguments.cache);
260				loc.findAll = variables.wheels.class.adapter.$query(argumentCollection=loc.finderArgs);
261				request.wheels[loc.queryKey] = loc.findAll; // <- store in request cache so we never run the exact same query twice in the same request
262			}
263			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
264
265			switch (arguments.returnAs)
266			{
267				case "query":
268				{
269					loc.returnValue = loc.findAll.query;
270					// 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)
271					if (loc.returnValue.columnList != "wheelsqueryresult" && !arguments.$limit && !arguments.$offset)
272						$callback("afterFind", arguments.callbacks, loc.returnValue);
273					break;
274				}
275				case "struct": case "structs":
276				{
277					loc.returnValue = $serializeQueryToStructs(query=loc.findAll.query, argumentCollection=arguments);
278					break;
279				}
280				case "object": case "objects":
281				{
282					loc.returnValue = $serializeQueryToObjects(query=loc.findAll.query, argumentCollection=arguments);
283					break;
284				}
285				default:
286				{
287					if (application.wheels.showErrorInformation)
288						$throw(type="Wheels.IncorrectArgumentValue", message="Incorrect Arguments", extendedInfo="The `returnAs` may be either `query`, `struct(s)` or `object(s)`");
289					break;
290				}
291			}
292		}
293	</cfscript>
294	<cfreturn loc.returnValue>
295</cffunction>
296
297<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."
298	examples=
299	'
300		<!--- Getting the author with the primary key value `99` as an object --->
301		<cfset auth = model("author").findByKey(99)>
302
303		<!--- Getting an author based on a form/URL value and then checking if it was found --->
304		<cfset auth = model("author").findByKey(params.key)>
305		<cfif NOT IsObject(auth)>
306			<cfset flashInsert(message="Author ##params.key## was not found")>
307			<cfset redirectTo(back=true)>
308		</cfif>
309
310		<!--- 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) --->
311		<cfset comment = model("comment").findByKey(params.commentId)>
312		<cfset post = comment.post()>
313	'
314	categories="model-class,read" chapters="reading-records,associations" functions="belongsTo,findAll,findOne">
315	<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.">
316	<cfargument name="select" type="string" required="false" default="" hint="See documentation for @findAll.">
317	<cfargument name="include" type="string" required="false" default="" hint="See documentation for @findAll.">
318	<cfargument name="cache" type="any" required="false" default="" hint="See documentation for @findAll.">
319	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
320	<cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
321	<cfargument name="returnAs" type="string" required="false" hint="See documentation for @findOne.">
322	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
323	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
324	<cfscript>
325		var returnValue = "";
326		$args(name="findByKey", args=arguments);
327		if (Len(arguments.key))
328		{
329			$keyLengthCheck(arguments.key);
330		}
331		// convert primary key column name(s) / value(s) to a WHERE clause that is then used in the findOne call
332		arguments.where = $keyWhereString(values=arguments.key);
333		StructDelete(arguments, "key");
334		returnValue = findOne(argumentCollection=arguments);
335	</cfscript>
336	<cfreturn returnValue>
337</cffunction>
338
339<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."
340	examples=
341	'
342		<!--- Getting the most recent order as an object from the database --->
343		<cfset order = model("order").findOne(order="datePurchased DESC")>
344
345		<!--- Using a dynamic finder to get the first person with the last name `Smith`. Same as calling `model("user").findOne(where"lastName=''Smith''")` --->
346		<cfset person = model("user").findOneByLastName("Smith")>
347
348		<!--- Getting a specific user using a dynamic finder. Same as calling `model("user").findOne(where"email=''someone@somewhere.com'' AND password=''mypass''")` --->
349		<cfset user = model("user").findOneByEmailAndPassword("someone@somewhere.com,mypass")>
350
351		<!--- 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) --->
352		<cfset user = model("user").findByKey(params.userId)>
353		<cfset profile = user.profile()>
354
355		<!--- 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) --->
356		<cfset post = model("post").findByKey(params.postId)>
357		<cfset comment = post.findOneComment(where="text=''I Love Wheels!''")>
358	'
359	categories="model-class,read" chapters="reading-records,associations" functions="findAll,findByKey,hasMany,hasOne">
360	<cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
361	<cfargument name="order" type="string" required="false" default="" hint="See documentation for @findAll.">
362	<cfargument name="select" type="string" required="false" default="" hint="See documentation for @findAll.">
363	<cfargument name="include" type="string" required="false" default="" hint="See documentation for @findAll.">
364	<cfargument name="cache" type="any" required="false" default="" hint="See documentation for @findAll.">
365	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
366	<cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
367	<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.">
368	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
369	<cfscript>
370		var returnValue = "";
371		$args(name="findOne", args=arguments);
372		if (!Len(arguments.include) || (StructKeyExists(variables.wheels.class.associations, arguments.include) && variables.wheels.class.associations[arguments.include].type != "hasMany"))
373		{
374			// 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
375			// 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)
376			arguments.maxRows = 1;
377		}
378		else
379		{
380			// 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
381			arguments.page = 1;
382			arguments.perPage = 1;
383			arguments.count = 1;
384		}
385		returnValue = findAll(argumentCollection=arguments);
386		if (IsArray(returnValue))
387		{
388			if (ArrayLen(returnValue))
389				returnValue = returnValue[1];
390			else
391				returnValue = false;
392		}
393	</cfscript>
394	<cfreturn returnValue>
395</cffunction>
396
397<!--- update --->
398
399<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."
400	examples=
401	'
402		<!--- Update the `published` and `publishedAt` properties for all records that have `published=0` --->
403		<cfset recordsUpdated = model("post").updateAll(published=1, publishedAt=Now(), where="published=0")>
404
405		<!--- 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.) --->
406		<cfset aPost = model("post").findByKey(params.postId)>
407		<cfset removedSuccessfully = aPost.removeAllComments()>
408	'
409	categories="model-class,update" chapters="updating-records,associations" functions="hasMany,update,updateByKey,updateOne">
410	<cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
411	<cfargument name="include" type="string" required="false" default="" hint="See documentation for @findAll.">
412	<cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
413	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
414	<cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
415	<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.">
416	<cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
417	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
418	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
419	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
420	<cfscript>
421		var loc = {};
422		$args(name="updateAll", args=arguments);
423		arguments.properties = $setProperties(argumentCollection=arguments, filterList="where,include,properties,reload,parameterize,instantiate,validate,transaction,callbacks,includeSoftDeletes", setOnModel=false);
424
425		if (arguments.instantiate) // find and instantiate each object and call its update function
426		{
427			loc.returnValue = 0;
428			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");
429			for (loc.i=1; loc.i lte ArrayLen(loc.objects); loc.i++)
430			{
431				if (loc.objects[loc.i].update(properties=arguments.properties, parameterize=arguments.parameterize, transaction=arguments.transaction, callbacks=arguments.callbacks))
432					loc.returnValue = loc.returnValue + 1;
433			}
434		}
435		else
436		{
437			arguments.sql = [];
438			ArrayAppend(arguments.sql, "UPDATE #tableName()# SET");
439			loc.pos = 0;
440			for (loc.key in arguments.properties)
441			{
442				loc.pos = loc.pos + 1;
443				ArrayAppend(arguments.sql, "#variables.wheels.class.properties[loc.key].column# = ");
444
445				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])};
446				ArrayAppend(arguments.sql, loc.param);
447				if (StructCount(arguments.properties) gt loc.pos)
448					ArrayAppend(arguments.sql, ",");
449			}
450			arguments.sql = $addWhereClause(sql=arguments.sql, where=arguments.where, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
451			arguments.sql = $addWhereClauseParameters(sql=arguments.sql, where=arguments.where);
452			loc.returnValue = invokeWithTransaction(method="$updateAll", argumentCollection=arguments);
453		}
454	</cfscript>
455	<cfreturn loc.returnValue>
456</cffunction>
457
458<cffunction name="$updateAll" returntype="numeric" access="public" output="false">
459	<cfset var update = variables.wheels.class.adapter.$query(sql=arguments.sql, parameterize=arguments.parameterize)>
460	<cfreturn update.result.recordCount>
461</cffunction>
462
463<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."
464	examples=
465	'
466		<!--- Updates the object with `33` as the primary key value with values passed in through the URL/form --->
467		<cfset result = model("post").updateByKey(33, params.post)>
468
469		<!--- Updates the object with `33` as the primary key using named arguments --->
470		<cfset result = model("post").updateByKey(key=33, title="New version of Wheels just released", published=1)>
471	'
472	categories="model-class,update" chapters="updating-records,associations" functions="hasOne,hasMany,update,updateAll,updateOne">
473	<cfargument name="key" type="any" required="true" hint="See documentation for @findByKey.">
474	<cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
475	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
476	<cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
477	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
478	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
479	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
480	<cfscript>
481		var returnValue = "";
482		$args(name="updateByKey", args=arguments);
483		$keyLengthCheck(arguments.key);
484		arguments.where = $keyWhereString(values=arguments.key);
485		StructDelete(arguments, "key");
486		returnValue = updateOne(argumentCollection=arguments);
487	</cfscript>
488	<cfreturn returnValue>
489</cffunction>
490
491<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."
492	examples=
493	'
494		<!--- Sets the `new` property to `1` on the most recently released product --->
495		<cfset result = model("product").updateOne(order="releaseDate DESC", new=1)>
496
497		<!--- 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.) --->
498		<cfset aUser = model("user").findByKey(params.userId)>
499		<cfset aUser.removeProfile()>
500	'
501	categories="model-class,update" chapters="updating-records,associations" functions="hasOne,update,updateAll,updateByKey">
502	<cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
503	<cfargument name="order" type="string" required="false" default="" hint="See documentation for @findAll.">
504	<cfargument name="properties" type="struct" required="false" default="#StructNew()#" hint="See documentation for @new.">
505	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
506	<cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
507	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
508	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
509	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
510	<cfscript>
511		var loc = {};
512		$args(name="updateOne", args=arguments);
513		loc.object = findOne(where=arguments.where, order=arguments.order, reload=arguments.reload, includeSoftDeletes=arguments.includeSoftDeletes);
514		StructDelete(arguments, "where");
515		StructDelete(arguments, "order");
516		if (IsObject(loc.object))
517			loc.returnValue = loc.object.update(argumentCollection=arguments);
518		else
519			loc.returnValue = false;
520	</cfscript>
521	<cfreturn loc.returnValue>
522</cffunction>
523
524<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."
525	examples=
526	'
527		<!--- Sets the `new` property to `1` through updateProperty() --->
528		<cfset product = model("product").findByKey(56)>
529		<cfset product.updateProperty("new", 1)>
530	'
531	categories="model-class,update" chapters="updating-records,associations" functions="hasOne,update,updateAll,updateByKey,updateProperties">
532	<cfargument name="property" type="string" required="true" hint="Name of the property to update the value for globally.">
533	<cfargument name="value" type="any" required="true" hint="Value to set on the given property globally.">
534	<cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
535	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
536	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
537	<cfscript>
538		$args(name="updateProperty", args=arguments);
539		arguments.validate = false;
540		this[arguments.property] = arguments.value;
541	</cfscript>
542	<cfreturn save(parameterize=arguments.parameterize, reload=false, validate=arguments.validate, transaction=arguments.transaction, callbacks=arguments.callbacks) />
543</cffunction>
544
545<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."
546	examples=
547	'
548		<!--- Sets the `new` property to `1` through `updateProperties()` --->
549		<cfset product = model("product").findByKey(56)>
550		<cfset product.updateProperties(new=1)>
551	'
552	categories="model-class,update" chapters="updating-records,associations" functions="hasOne,update,updateAll,updateByKey,updateProperties">
553	<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.">
554	<cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
555	<cfargument name="validate" type="boolean" required="false" default="true" hint="See documentation for @save.">
556	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
557	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
558	<cfscript>
559		$args(name="updateProperties", args=arguments);
560		$setProperties(argumentCollection=arguments, filterList="properties,parameterize,validate,transaction,callbacks");
561	</cfscript>
562	<cfreturn save(parameterize=arguments.parameterize, reload=false, validate=arguments.validate, transaction=arguments.transaction, callbacks=arguments.callbacks) />
563</cffunction>
564
565
566<!--- delete --->
567
568<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."
569	examples=
570	'
571		<!--- Delete all inactive users without instantiating them (will skip validation and callbacks) --->
572		<cfset recordsDeleted = model("user").deleteAll(where="inactive=1", instantiate=false)>
573
574		<!--- 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.) --->
575		<cfset post = model("post").findByKey(params.postId)>
576		<cfset howManyDeleted = post.deleteAllComments()>
577	'
578	categories="model-class,delete" chapters="deleting-records,associations" functions="delete,deleteByKey,deleteOne,hasMany">
579	<cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
580	<cfargument name="include" type="string" required="false" default="" hint="See documentation for @findAll.">
581	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
582	<cfargument name="parameterize" type="any" required="false" hint="See documentation for @findAll.">
583	<cfargument name="instantiate" type="boolean" required="false" hint="See documentation for @updateAll.">
584	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
585	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
586	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
587	<cfargument name="softDelete" type="boolean" required="false" default="true" hint="See documentation for @delete.">
588	<cfscript>
589		var loc = {};
590		$args(name="deleteAll", args=arguments);
591
592		if (arguments.instantiate)
593		{
594			loc.returnValue = 0;
595			loc.objects = findAll(select=propertyNames(), where=arguments.where, include=arguments.include, reload=arguments.reload, parameterize=arguments.parameterize, includeSoftDeletes=arguments.includeSoftDeletes, returnIncluded=false, returnAs="objects");
596			for (loc.i=1; loc.i lte ArrayLen(loc.objects); loc.i++)
597			{
598				if (loc.objects[loc.i].delete(parameterize=arguments.parameterize, transaction=arguments.transaction, callbacks=arguments.callbacks, softDelete=arguments.softDelete))
599					loc.returnValue++;
600			}
601		}
602		else
603		{
604			arguments.sql = [];
605			arguments.sql = $addDeleteClause(sql=arguments.sql, softDelete=arguments.softDelete);
606			arguments.sql = $addWhereClause(sql=arguments.sql, where=arguments.where, include=arguments.include, includeSoftDeletes=arguments.includeSoftDeletes);
607			arguments.sql = $addWhereClauseParameters(sql=arguments.sql, where=arguments.where);
608			loc.returnValue = invokeWithTransaction(method="$deleteAll", argumentCollection=arguments);
609		}
610	</cfscript>
611	<cfreturn loc.returnValue>
612</cffunction>
613
614<cffunction name="$deleteAll" returntype="numeric" access="public" output="false">
615	<cfset var delete = variables.wheels.class.adapter.$query(sql=arguments.sql, parameterize=arguments.parameterize)>
616	<cfreturn delete.result.recordCount>
617</cffunction>
618
619<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."
620	examples=
621	'
622		<!--- Delete the user with the primary key value of `1` --->
623		<cfset result = model("user").deleteByKey(1)>
624	'
625	categories="model-class,delete" chapters="deleting-records" functions="delete,deleteAll,deleteOne">
626	<cfargument name="key" type="any" required="true" hint="See documentation for @findByKey.">
627	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
628	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
629	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
630	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
631	<cfargument name="softDelete" type="boolean" required="false" default="true" hint="See documentation for @delete.">
632	<cfscript>
633		var loc = {};
634		$args(name="deleteByKey", args=arguments);
635		$keyLengthCheck(arguments.key);
636		loc.where = $keyWhereString(values=arguments.key);
637		loc.returnValue = deleteOne(where=loc.where, reload=arguments.reload, transaction=arguments.transaction, callbacks=arguments.callbacks, includeSoftDeletes=arguments.includeSoftDeletes, softDelete=arguments.softDelete);
638	</cfscript>
639	<cfreturn loc.returnValue>
640</cffunction>
641
642<cffunction name="deleteOne" returntype="boolean" access="public" output="false" hint="Gets an object based on conditions and deletes it."
643	examples=
644	'
645		<!--- Delete the user that signed up last --->
646		<cfset result = model("user").deleteOne(order="signupDate DESC")>
647
648		<!--- 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) --->
649		<cfset aUser = model("user").findByKey(params.userId)>
650		<cfset aUser.deleteProfile()>
651	'
652	categories="model-class,delete" chapters="deleting-records,associations" functions="delete,deleteAll,deleteOne,hasOne">
653	<cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
654	<cfargument name="order" type="string" required="false" default="" hint="See documentation for @findAll.">
655	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
656	<cfargument name="transaction" type="string" required="false" default="#application.wheels.transactionMode#" hint="See documentation for @save.">
657	<cfargument name="callbacks" type="boolean" required="false" default="true" hint="See documentation for @save.">
658	<cfargument name="includeSoftDeletes" type="boolean" required="false" default="false" hint="See documentation for @findAll.">
659	<cfargument name="softDelete" type="boolean" required="false" default="true" hint="See documentation for @delete.">
660	<cfscript>
661		var loc = {};
662		$args(name="deleteOne", args=arguments);
663		loc.object = findOne(where=arguments.where, order=arguments.order, reload=arguments.reload, includeSoftDeletes=arguments.includeSoftDeletes);
664		if (IsObject(loc.object))
665			loc.returnValue = loc.object.delete(transaction=arguments.transaction, callbacks=arguments.callbacks, softDelete=arguments.softDelete);
666		else
667			loc.returnValue = false;
668	</cfscript>
669	<cfreturn loc.returnValue>
670</cffunction>
671
672<!--- other --->
673
674<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."
675	examples=
676	'
677		<!--- Checking if Joe exists in the database --->
678		<cfset result = model("user").exists(where="firstName=''Joe''")>
679
680		<!--- Checking if a specific user exists based on a primary key valued passed in through the URL/form in an if statement --->
681		<cfif model("user").exists(keyparams.key)>
682			<!--- Do something... --->
683		</cfif>
684
685		<!--- 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.) --->
686		<cfset comment = model("comment").findByKey(params.commentId)>
687		<cfset commentHasAPost = comment.hasPost()>
688
689		<!--- 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.) --->
690		<cfset user = model("user").findByKey(params.userId)>
691		<cfset userHasProfile = user.hasProfile()>
692
693		<!--- 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.) --->
694		<cfset post = model("post").findByKey(params.postId)>
695		<cfset postHasComments = post.hasComments()>
696	'
697	categories="model-class,miscellaneous" chapters="reading-records,associations" functions="belongsTo,hasMany,hasOne">
698	<cfargument name="key" type="any" required="false" default="" hint="See documentation for @findByKey.">
699	<cfargument name="where" type="string" required="false" default="" hint="See documentation for @findAll.">
700	<cfargument name="reload" type="boolean" required="false" hint="See documentation for @findAll.">
701	<cfargument name="parameterize…

Large files files are truncated, but you can click here to view the full file