Twitter: raymondcamden


Address: Lafayette, LA, USA

MockData CFC Released

08-04-2014 2,624 views ColdFusion 7 Comments

A while ago I wrote a Node.js service called MockData. The idea behind it was to create a quick way to generate mock data for client-side applications that were purely driven by URL parameters. So for example, I could get 10 random people by doing an XHR request to http://myserver:myport?num=10&author=name. The service I built supported a few different types of mock data (names, emails, addresses, telephone numbers, etc), and was, I think, pretty flexible. I thought it would be interesting to rewrite the core logic in ColdFusion, specifically ColdFusion 11, to see how much of the JS code had to be re-engineered.

To be fair, this wasn't meant to be a complete rewrite. My Node.js version is its own little server. It runs on a port and could easily be used with anything - ColdFusion, PHP, another Node.js app, or even a static web site. The ColdFusion version doesn't have to worry about firing up a server or taking over a port. It simply has to look at the query string and generate the proper data.

So how did it go? All in all it was mostly painless. The biggest annoyance I ran into was... and yes... you can taunt me with these words later... was ColdFusion starting arrays at 0. I will go to my grave convinced that 0-based indexes came about because the original designer was drunk and needed to cover his ass later (cue the outraged comments in 3...2...1...) but at the end of day... I'm just more used to 0 now and I wish ColdFusion had a toggle to use it too.

There. I said it.

The other big thing I ran into was also array based. I kept typing someArr.length when I needed to do someArr.len(). I'm happy ColdFusion 11 allows me to run methods on variables, but I do wish they had added a simple length property too.

Speaking of methods, I did find myself forgetting to make use of them. For example, I had structKeyExists(foo, "goo") where foo.keyExists("goo") works now. If you look over the code you'll probably find other places where I forgot this as well. (In fact, I just checked and found another one I missed: arr.each). The code isn't terribly long so I'll share the entire thing below, but you can download, and fork it, here: https://github.com/cfjedimaster/MockDataCFC

component {

	cfheader(name="Access-Control-Allow-Origin", value="*");
	
	//So you can skip passing it...
	url.method="mock";

	//Defaults used for data, may move to a file later
	fNames = ["Andy","Alice","Amy","Barry","Bob","Charlie","Clarence","Clara","Danny","Delores","Erin","Frank","Gary","Gene","George","Heather","Jacob","Leah","Lisa","Lynn","Nick","Noah","Ray","Roger","Scott","Todd"];
	lNames = ["Anderson","Bearenstein","Boudreaux","Camden","Clapton","Degeneres","Hill","Moneymaker","Padgett","Rogers","Smith","Sharp","Stroz","Zelda"];
	emailDomains = ["gmail.com","aol.com","microsoft.com","apple.com","adobe.com"];
	lorem = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";

	defaults = ["name","fname","lname","age","all_age","email","ssn","tel","gps","num"];
	function isDefault(s) {
		return defaults.findNoCase(s) >= 0; 
	}

	function generateFirstName() {
		return fNames[randRange(1, fNames.len())];
	}

	function generateLastName() {
		return lNames[randRange(1, lNames.len())];
	}

	function generateFakeData(type) {
		if(type == "string") return "string";
		if(type == "name") return generateFirstName() & " " & generateLastName();
		if(type == "fname") return generateFirstName();
		if(type == "lname") return generateLastName();
		if(type == "age") return randRange(18,75);
		if(type == "all_age") return randRange(1,100);
		if(type == "email") {
			var fname = generateFirstName().toLowerCase();
			var lname = generateLastName().toLowerCase();
			var emailPrefix = fname.charAt(1) & lname;
			return emailPrefix & "@" & emailDomains[randRange(1, emailDomains.len())];
		}
		if(type == "ssn") {
			return randRange(1,9) & randRange(1,9) & randRange(1,9) & "-" &
				    randRange(1,9) & randRange(1,9) & "-" & 
				    randRange(1,9) & randRange(1,9) & randRange(1,9) & randRange(1,9);
		}
		if(type == "tel") {
			return "(" & randRange(1,9) & randRange(1,9) & randRange(1,9) & ") " &
				    randRange(1,9) & randRange(1,9) & randRange(1,9) & "-" & 
				    randRange(1,9) & randRange(1,9) & randRange(1,9) & randRange(1,9);
	
		}
		if(type.find("num") == 1) {
			//Support num, num:10, num:1:10
			if(type == "num") return randRange(1,10);
			if(type.find(":") > 1) {
				var parts = type.listToArray(":");
				if(parts.len() == 2) return randRange(1,parts[2]);
				else return randRange(parts[2],parts[3]);
			}
		}
		if(type.find("oneof") == 1) {
			//Support oneof:male:female, ie, pick a random one
			var items = type.listToArray(":");
			items.deleteAt(1);
			return items[randRange(1,items.len())];
		}
		if(type.find("lorem") == 1) {
			if(type == "lorem") return lorem;
			if(type.find(":") > 1) {
				var parts = type.listToArray(":");
				var result = "";
				var count = "";
				if(parts.len() == 2) count = parts[2];
				else count = randRange(parts[2],parts[3]);
				for(var i=0; i<count; i++) result &= lorem & "\n\n";
				return result;
			}
		}
		return "";
	}

	function generateNewItem(model) {
		var result = {};

		model.each(function(field) {
			if(!field.keyExists("name")) {
				field.name = "field"&i;
			}
	
			if(!field.keyExists("type")) {
				//if we are a default, that is our type, otherwise string
				if(isDefault(field.name)) field.type = field.name;
			}
			result[field.name] = generateFakeData(field.type);
			
		});
		
		return result;
	}
	
	remote function mock() returnformat="json" {
		
		//Did they specify how many they want?
		if(!arguments.keyExists("num")) arguments.num = 10;

		if(!isNumeric(arguments.num) && arguments.num.find(":") > 0) {
			var parts = arguments.num.listToArray(":");
			if(parts[1] != "rnd") {
				throw("Invalid num prefix sent. Must be 'rnd'");
			}
			// format is rnd:10 which means, from 1 to 10
			if(parts.len() == 2) {
				arguments.num = randRange(1,parts[2]);
			} else {
				arguments.num = randRange(parts[2],parts[3]);
			}
		}

		var fieldModel = [];
		for(var key in arguments) {
			if(key != "num") {
				fieldModel.append({name:key,type:arguments[key]});
			}
		}
	
		var result = [];
		for(var i=1; i<=arguments.num; i++) {
			result.append(generateNewItem(fieldModel));
		}
			
		cfheader(name="Content-Type", value="application/json");

		return result;
	}

}

7 Comments

  • Commented on 08-04-2014 at 5:04 PM
    Pretty interesting. I see the CF code is about 30 lines longer due to a mock() method. I'm curious how Node covered that functionality. (Disclaimer: I haven't actually tried to read and understand all the code yet :)
  • Commented on 08-04-2014 at 5:06 PM
    Not a tag in sight. Brilliant.

    --
    Adam
  • Commented on 08-04-2014 at 5:21 PM
    @Brad: Keep in mind, my Node.js skills are still a bit fresh. What you see in mock() in the CFc version is present in my Node apps app.js file and dataUtills. The app.js handles parsing the query string and figuring what you want, then dataUtils.js creates the real fake data. (Um, you get the idea.) Thinking about this now, I would have also abstracted the 'parse the query string' stuff out so app.js was simpler. From what I know in my limited Node.js experience, your App.js (core interface for the app) should be as simple as possible - setting up services and routing for example, with almost all of the logic separated out.

    Again - take that with a grain of salt.

    @Adam: Thank you. :)
  • Commented on 08-04-2014 at 6:26 PM
    Didn't you mean...

    outraged comments in 2...1...0...??
  • Commented on 08-04-2014 at 7:20 PM
    @Jim: You win. :)
  • Commented on 08-05-2014 at 3:22 AM
    If you remove:

    cfheader(name="Content-Type", value="application/json");

    Does it still give you the right content type?
  • Commented on 08-05-2014 at 5:28 AM
    @AL: No, which is odd. I thought we fixed that back in 10.

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty