Adam Tuttle

Taffy: A Restful Framework for ColdFusion

Update: Since this was posted in August of 2010, a lot has changed with Taffy. We're now approaching a 1.2 release, and new development should not be done against 1.0. See this page for more information.

Today I'm excited to announce something that I've been working on quietly for a few months. I've created a framework for the purpose of building Restful APIs with semantically correct URIs -- no easy task in vanilla CFML -- simply and elegantly. I call it Taffy.

I've taken some inspiration from FW/1, Powernap, and Swiz. Much like FW/1, Taffy is a set of base classes that your Application.cfc and resource CFCs extend; and similar to Powernap, Taffy uses the notion of "representation" classes. Don't worry about these yet - it's easy and I'll get into them in more detail later. Lastly, using Swiz taught me the power of metadata, so I made it a big part of Taffy.

As much as is possible, Taffy uses convention instead of configuration. There's no messy mapping of this http verb used with this URI calls this function in this cfc. You set a few bits of metadata and use a few pre-determined function names, and everything falls into place.

Enough blabber, let's look at a REST API written with Taffy!

Setting up the framework

Application.cfc

component extends="taffy.core.api" {
    this.name = hash(getCurrentTemplatePath());
}

Simple enough for you? In case you missed it, this is a generic application with a unique name... and it uses the component extends attribute to extend Application.cfc from Taffy's core api class.

The only requirement is that you either put Taffy in your web root, or create a CF mapping to it.

Ok, so there are a few configuration options, all of which you set in Application.cfc, as in the next code sample. I'm going to show all of the configuration options, but use their default values.

component extends="taffy.core.api" {

    this.name = hash(getCurrentTemplatePath());

    //use this instead of onApplicationStart()
    void function applicationStartEvent(){}

    void function configureTaffy(){
        //these are all default values, but if you want to change them, this is the place:
        setDebugKey("debug");
        setReloadKey("reload");
        setReloadPassword("true");
        setDashboardKey("dashboard");
        setDefaultRepresentationClass("taffy.core.genericRepresentation");
        registerMimeType("json", "application/json");
        setDefaultMime("json");     
    }
}

Still pretty simple, right? And now you have an application capable of servicing REST requests. You just don't have any resources.

Resources

In general with REST, there are two types of resources: Collections and Members. Think of Collections as a query object that shows the entire contents of the table, and Members as a structure representing a single row of the same query. With Taffy, you implement Collections and Members as separate CFCs.

Let's start with a Collection, using the trusty old Art Gallery datasource:

artistCollection.cfc

<cfcomponent extends="taffy.core.resource" taffy_uri="/artists">

    <cffunction name="get" access="public" output="false">
        <cfset var q = "" />
        <cfquery name="q" datasource="cfartgallery" cachedwithin="#createTimeSpan(0,0,0,1)#">
            select * from artists
        </cfquery>
        <cfreturn representationOf(q).withStatus(200) />
    </cffunction>

    <cffunction name="post" access="public" output="false">
        <cfargument name="firstname" type="string" required="false" default="" />
        <cfargument name="lastname" type="string" required="false" default="" />
        <cfargument name="address" type="string" required="false" default="" />
        <cfargument name="city" type="string" required="false" default="" />
        <cfargument name="state" type="string" required="false" default="" />
        <cfargument name="postalcode" type="string" required="false" default="" />
        <cfargument name="email" type="string" required="false" default="" />
        <cfargument name="phone" type="string" required="false" default="" />
        <cfargument name="fax" type="string" required="false" default="" />
        <cfargument name="thepassword" type="string" required="false" default="" />
        <cfset var q = "" />
        <cfquery name="q" datasource="cfartgallery">
            insert into artists (firstname,lastname,address,city,state,postalcode,email,phone,fax,thepassword)
            values (
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.firstname#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.lastname#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.address#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.city#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.state#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.postalcode#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.email#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.phone#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.fax#" />,
                <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.thepassword#" />
            )
        </cfquery>
        <cfquery name="q" datasource="cfartgallery">
            select * from artists
            where
                firstname = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.firstname#" />
                and lastname = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.lastname#" />
                and address = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.address#" />
                and city = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.city#" />
                and state = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.state#" />
                and postalcode = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.postalcode#" />
                and email = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.email#" />
                and phone = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.phone#" />
                and fax = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.fax#" />
                and thepassword = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.thepassword#" />
        </cfquery>
        <cfreturn representationOf(q).withStatus(200) />
    </cffunction>

</cfcomponent>

There are quite a few lines of code there, but most of them are querying the database. If we ignore those lines, we go down from about 55 lines to about 21. Let's break down the important stuff:

  1. The component extends taffy.core.resource. This is where it inherits functionality like the representationOf and withStatus functions.

  2. The component also has a metadata attribute named taffy_uri that tells Taffy the URI of the collection that we are defining. We'll talk more about URIs and how you can use them in the next section.

  3. Functions defined here are named GET and POST. For the sake of discussion, I refer to these as Responders. Notice that these correspond to HTTP verb names (get, put, post, delete). I bet you can guess the convention. That's right, when the GET verb is used, the GET function will be called.

  4. All responders must return using the function representationOf. This returns a special class containing your data that can be serialized into multiple formats. A JSON serializing representation class comes with the framework, but you can also create and use a custom representation class capable of serializing to any format you want (for example: XML, YAML, WDDX, HTML, etc). I will cover using a custom representation class in a later post, and may decide to create examples that serialize to various formats.

  5. Notice that responders are not present for all 4 HTTP verbs. If a request is made and no responder exists corresponding to the request verb, Taffy will respond with HTTP status 405 Not Allowed.

URIs and making them work for you

You know what a URI is, right? It's a neat (as in tidy) little chunk of URL that tells the web server exactly what bit of data you're interested in. For example, here's a URI for a recent tweet of mine on twitter:

/TuttleTree/status/21767382574

When sent to Twitter.com, they reply with just that one tweet. In actuality, your browser is making a GET request to Twitter's API, which is responding with some HTML. Likewise, using the URI /TuttleTree you'll get an HTML response of the entire collection of my tweets (paged).

Taffy makes doing this with ColdFusion simple and powerful. This is where that component metadata comes into play. By setting the taffy_uri of /artists, Taffy knows to connect requests for the /artists collection up to our artistCollection CFC.

That's great for collections, but if you're thinking ahead then you've no doubt wondered how we're going to handle unique bits in the URI. Given the same Twitter-based example, we can't handle every permutation of unique tweet ID's in the metadata individually. For one, it's not possible, and even if it were, it's just not a reasonable approach. That's where tokens come in.

This is a perfectly valid taffy_uri: /artists/{artistId} -- and in fact, that is the taffy_uri of the artistMember CFC.

If we were writing the Twitter API and the tweet member CFC, it might start out like this:

component
    extends="taffy.core.resource"
    taffy_uri="/{username}/status/{tweetId}" {

When Taffy starts up, it inspects all of your Resource CFCs, grabbing that metadata, and converting the taffy_uri to a regular expression used to match incoming requests. All that you need to know is that if your URI defines a token {foo} then it will be passed by name to your responders -- so you have to have an argument named the same as your token, in this case, "foo".

There is one caveat with tokenized URIs. Stripped of all tokens, they must all be unique. If you define two Resources with the URIs /foo/{bar} and /foo/{fubar} then there is no way for Taffy to tell which is the appropriate CFC to use for the request. To Taffy, they both look like this: /foo/{some_token}, so having two of them is not unique, and Taffy will throw an exception during startup.

Providing Resources to Taffy

There are three methods for getting Resources into Taffy.

  • Taffy will automatically load your Resource CFCs from a child folder of the API root named "resources". If you are using ColdFusion 6, 7, or 8, you will need another CF mapping named "/resources" which points to your resources folder, and in CF8, of course, you can use an application-specific mapping. In CF9 (and presumably later) no "/resources" mapping is necessary. This is due to a difference in the way that CFC paths are evaluated when used from a parent CFC, which I plan to write about in a later post.

Taffy will also work with an external bean factory, like ColdSpring, in two ways.

  • First, you can use it to manage your resources instead of putting them into a child folder named "resources". If you choose to do this, there is no need to notify Taffy about each resource in any way; you simply set the bean factory and Taffy will request any beans that extend the taffy.core.resource base class. If you choose to go this route, you can simply manage any dependencies of your resources -- e.g. the service objects they might use to access the database -- in your bean factory configuration.

  • Alternately, you can use the convention folder ("resources") and an external bean factory, which will be used to resolve dependencies. This works similarly to ColdSpring -- if your Resource CFC contains a method named "setFoo" and your bean factory has a bean named "foo", then the "foo" bean will be set into the Resource when it is instantiated and cached.

In both cases, you can set your Taffy API up to be a child-application of a larger application that already uses a bean factory. By doing so, you don't have to duplicate your bean factory configuration, and Taffy will use the same existing factory instance.

Download

I suppose that's enough information to get you started. The download comes with several examples: an API implemented using no external bean factory, one using only ColdSpring, and one using both the internal resource factory and ColdSpring. It also has a consumer application that you can point at any of the three example APIs.

There are a few other things that I decided to leave out here for the sake of brevity (ha!), but everything should be pretty well documented in the wiki: http://github.com/atuttle/Taffy/wiki. If you have any questions, just ask!

The project is listed on RIAForge: http://taffy.riaforge.org, and the source is hosted on Github: http://github.com/atuttle/Taffy.

You can download as:

Download as ZIP Download as TAR

40 responses:

Sami Hoda

Sami Hoda

So how is this different from PowerNap?
Adam

Adam

Hi Sami,

PowerNap uses a lot of configuration. For example, to add a resource that the framework is aware of, you have to add configuration along this line:

<cfset newResource("myResource").isA("target.cfc.location") />

And then, to map a URI to that resource, you add:

<cfset map().get().uri("/myresource/{id}").to("myResource").calls("someMethod") />

To accomplish the same thing with Taffy, you create a cfc (the file name doesn't matter) that is either in the Resources folder, or in your Bean Factory config, that has the following:

component extends="taffy.core.resource" taffy_uri="/myresource/{id}" {...}

The PowerNap configuration also requires you to explicitly map the GET verb to a function, where Taffy requires that the function be named "get".

There are a lot of little differences, but those are the big ones.
Henry Ho

Henry Ho

is verb HEAD supported?
Adam

Adam

Hi Henry,

No, HEAD is not currently supported. It should be fairly easy to add, though. Please log a bug/ER for it. :)
John Farrar

John Farrar

OK... why do you have a whole application framework around REST? Seems like this should be refactored to run without the application framework and then refactored back into an applicaiton framework so it runs in and out. (or am I missing something again?)
Brian Carr

Brian Carr

Adam, I'm glad to see that Powernap played a part in your innovation :-) Needless to say I love ReST and am always happy to see other projects promoting it within the CF community. As an aside, Powernap is now also a CFCommons context plugin (http://www.cfcommons.org/index.cfm/module/context/powernap/plugin/).

In regards to your URL variable "uniqueness" limitation - this is something that I toiled with early on in the developement days of PN, the SimpleURLMatcher component (a part of the cfcommons HTTP library) adds a lot of flexibility to how URL matching templates are created - check it out when you get a chance, you might be able to leverage it to enhance your framework; http://www.cfcommons.org/index.cfm/module/http/simple-url-matching.
Adam

Adam

John, I'm not sure I follow your question exactly, so I'll break it into parts.

Why have a "whole" application framework for REST?

Well, "whole" might be a bit harsh. Have you looked at the source? It's like 5 files, and a combined total of 542 lines -- including all kinds of comments and blank lines to make things easier to read. There's not a "whole" lot to it. That's why I say it's lightweight.

But to answer your question, there is obviously a need for frameworks to manage REST requests -- ColdBox, Mach-ii, and PowerNap all immediately spring to mind. I'm sure there are others. So someone needs it.

As for why it should be separate from a traditional MVC framework -- well, that's just personal preference. Being separate means I can attach a Taffy (and probably a PowerNap, though I haven't tried/checked) API to an existing application that uses Model-Glue, or Fusebox, or even COOP. Taffy will also use an existing instance of your bean factory, too, so there is no need to duplicate the bean configuration or double the memory usage by creating a second instance of the same bean factory.

The last bit, about refactoring out and back in, I don't follow, so I can't really answer. If you're trying to suggest that I should refactor this code out of some existing application and then back into it (huh?), I can at least say that it is not part of any existing application. It is well and truly a framework unto itself.
Adam

Adam

Hi Brian, thanks for the comment. I wasn't aware of the addition to CFCommons, so congrats on that.

I took a _quick_ look at the simple url matcher you mentioned, and the only thing I see that it does that my implementation doesn't do is allow you to specify a regular expression of values that it should accept, right inside the token -- although this would be possible through Taffy's onTaffyRequest method. If there turns out to be interest in something like that, I'll look into it further.
Andrew Schwabe

Andrew Schwabe

I just posted a blog entry about how to configure Apache to remove "index.cfm" from your URLs, and it works great with taffy.
Mike

Mike

Hi,

I am having a bit of difficulty retrieving data via a CFHTTP call. Everything comes back fine when I hit the URL directly from a browser. When I make a chttp call from a test page I receive a “400 Bad Request”.

The cfhttp call is as follows:
<cfhttp throwonerror="yes" url="http://xxx/remote/comment/index.cfm/story/24439/comments"; method="get" timeout="10" />

If I simply browse to http://xxx/remote/comment/index.cfm/story/24439/comments I get back the correct JSON formatted data.

I’m pretty new at this so I’m probably making some newbie mistake. Any help would be appreciated.

Regards,
Mike
Mike

Mike

...i figured it out. I just changed the default requested mime type from "" to JSON.
Adam

Adam

Hi Mike,

I'm glad you figured it out. Had you specified a default of "" or was that a setting that you didn't change? Taffy should use JSON by default, unless you change it or if there is a bug! ;)
David G Ortega

David G Ortega

Hello Adam,

thanks a lot for the code!! I'm trying to use it implementing and API but I'm getting crazy just trying to figure out how several thigns works.

Taking consideration artCollection.cfc the taffy:uri="/artist/{artistId}/art"

and the get argument is

<cfargument name="artistId" type="number" required="false" default="-1" hint="If included, filters the results to match the requested id. -1 (default) includes all." />

reading it I assume that artistId is not needed as default has been declared so I undestand that I could call

http://myserver/examplefolder/index.cfm/artist/art

and expect to have the complete list... but I have an error!!

Calling
http://myserver/examplefolder/index.cfm/artist/3/art works

So how can I create uri with non required arguments??

Thanks

D
Adam

Adam

Hi David,

I'm glad you like Taffy. The problem you're experiencing is that the URI-matching doesn't include the notion of optional arguments -- every part of it is required.

Since you apparently want a collection of all art to be returned, consider using taffy:uri="/art", and keep your optional argument in the function. In this case, the optional argument could be supplied as a query param, as: /art?artistId=3.

Hope that helps.
Jim

Jim

This looks great!

I'm getting an error in the application.cfc. I'm really new to this.

Element _TAFFY.FACTORY is undefined in a Java object of type class [Ljava.lang.String; referenced as ''

The error occurred in [DIR]\taffy\core\api.cfc: line 488
Called from [DIR]t\taffy\core\api.cfc: line 213
Called from [DIR]\taffy\core\api.cfc: line 25
486 :    <cffunction name="inspectMimeTypes" access="private" output="false" returntype="void">
487 :       <cfargument name="customClassDotPath" type="string" required="true" hint="dot-notation path of representation class" />
488 :       <cfif application._taffy.factory.containsBean(arguments.customClassDotPath)>
489 :          <cfset _recurse_inspectMimeTypes(getMetadata(application._taffy.factory.getBean(arguments.customClassDotPath))) />
490 :       <cfelse>
Adam

Adam

Hi Jim,

I'm glad you like Taffy. If I had to guess, I'd say your Taffy API has the same application name as another application that's being started first, which is causing problems when Taffy starts. (I'm planning on fixing this in 1.1, to be released soon...)

Try changing the application name to something unique. If that doesn't fix it, let me know and I can help troubleshoot your code.
David G Ortega

David G Ortega

Hi Adam,

thanks a lot for the reply! I have understood... I think that would be better if you set the required option to true and remove that hint ;)
I was thinking that if the hint says taht if parameter is not given it will take -1 that's why I tried to reach the uri without the parameter at all.

Another question is that I had to implement a line of code to force the app to be restarted since any change on the code will not have effect until restart... Have you implemented this yet?

And finally... do you think it's possible to implemement a multiple uri mechanism or as we said before implement uris with optional parameters? If it's possible I think I can do it ;)

Thanks
Adam

Adam

Hi David,

Yes, the ID would be required -- if you're referring to an example where it is listed as optional, please let me know which one so I can update it. The arguments that provide a default value of -1 should be things that I expect to receive as query string parameters, not an ID value from the URL.

As for multiple URLs for the same resource, sorry, that's just anti-REST, so that's not going to happen. Every resource should have 1 ID. If you want a collection of all art, make it "/art", if you want to filter the art collection by artist, "/art?artistid=5" -or- "/artist/5/art". But "/artist/art" with no filtering doesn't make much sense.
David G Ortega

David G Ortega

Hi Adam,

I have found it in examples/api/artcollection.cfc ;)

Regarding to the multi uri proposal I don't want to be antiRest at all!! I'm innocent!! ;)

Seriously what I would like to do is just use the same cfc to generate different uris. Imagine that I have a list of singers and I have implemented a cfc that deals with the db generating different results depending on the parameters called singers.cfc

Our resources' taffy component named singersCollection is instanting it assigning the singers instance...

So ie we want to create two uris:
/singers //returns the complete list of singers
/singers/top //returns the top 10 based on user reviews

we could have two uris deffinition like:
/singers
/singers/{filter}

Its pretty simple to imagine that if I have the code below in the function get I dont have to repeat the code several times

var singers = CreateObject("component", "Singers");
if(len(filter))
{
singers.filters.add(filter);
}

result = singers.query();

return representationOf(result).withStatus(200);

If not I have to create one file per uri...

Hope this explanation makes sense to you... ;)
Adam

Adam

Hi David,

Thanks for pointing out which example you got that from. I can see how that would be confusing -- I'll be sure to change that shortly.

I can kind of understand where you're coming from with your specific requirements, but the fact remains that if I added that functionality there is far more chance it will be used for evil than for good. There's a reason Lex Luthor didn't have Superman's powers. ;)

If you want to write maintainable code (eg, not copy/paste your logic between two CFC's) and use Taffy to serve up both /singers and /singers/top, then my suggestion would be to implement the logic in another CFC, then access it from a Taffy resource.

Taffy has a bean factory built in, and will resolve dependencies by name. If you create Singers.cfc in the resources folder, and then have a setSingers() method in your resource CFC's, then Taffy will set the Singers object into each resource for you to use -- very much like ColdSpring's AutoWire by Name feature.

Hopefully that makes sense.
David G Ortega

David G Ortega

Hi Adam,

I have been researching about having the same entry point to several uris calling different methods of a class... I can't see which is the bad point of this architecture in fact I found one example really interesting... but in PHP... Epiphany
https://github.com/jmathai/epiphany/blob/master/docs/Api.markdown#readme

Why would you think this is a bad architecture?

Thanks Adam ;)
Adam

Adam

David, what about that readme is what you wanted me to see? I looked at it and don't see anything about multiple routes going to the same class.

My view is this: the U in URI stands for "unique." If you make multiple URIs for the same resource, you're going against the definition of the word. Taffy resource cfc's can be SO small and simple that writing 2 of them to support your /singers and /singers/top example seems perfectly reasonable to me.
David G Ortega

David G Ortega

Hi Adam,

probably this is getting confusing and I should discuss this with you by mail if you consider it properly. Also you can delete all the non relevant posts to Taffy that I send to you. I recommend you that ;)

The original idea that I was thinking about was just declare all the uris in the same entry point and associate each to an action... the good thing for me about ephiphany is that he is mapping each uri with a method of a class

getApi()->get('/version.json', array('Api', 'version'), EpiApi::external);
getApi()->get('/users.json', array('Api', 'users'), EpiApi::external);

/version is going to call Api::version() and /users Api::Users and so on...All declared in the same entry point... Actually with Taffy I create a resource cfc just only to instantiate the same cfc and call a different method. I'm duplicating code... not too much but duplicating... even worse just because of adding the Memcached (http://en.wikipedia.org/wiki/Memcached) part...

I don't want you to understand that Taffy is not well designed or could be done better... Taffy rocks!! In fact for all those coldfusion programmers that actually are reading this and are thinking about using this stuff... don't think it to much!! just try it!! highly recommended!!
Adam

Adam

David, that sounds a lot like PowerNap (http://powernap.riaforge.org/). Here's some example PowerNap config:

<cfset map().get().uri("/myresource/{id}").to("myResource").calls("someGetMethod") />

If that appeals to you, then you should check out PowerNap. :)
Gary Menzel

Gary Menzel

Hello Adam....

One thing I recently discovered about the regex that is composed for matching is that it does not match from the front of the string.

So - if I have the following two URIs....

/doco/component/tab
&
/tab

It will match whichever one it finds first because "/tab" is in both strings.

In my case, the longer URI actually has the third piece as a variable - so the behaviour was quite random until I worked out what was going on....

To fix it, I added a "^" to the front of the returned uriRegex in the convertURIToRegex method and the issue was resolved.

Regards,
Gary

[NOTE: I am not using the whole Taffy component set - just the URL parsing and matching routines]
Andrew Penniman

Andrew Penniman

Greetings, Adam. I am experiencing the same issue as Jim (see comment data 11 Mar 2011):

Element _TAFFY.FACTORY is undefined in a Java object of type class [Ljava.lang.String; referenced as ''

I changed the application name to something that I know is unique but I'm still receiving this error.

I've placed the taffy code in C:\CFusion8\CustomTags\taffy\. I placed my resources directory in C:\CFusion8\CustomTags\ as well.

My application directory is sitting in the root of http://some.domain.com andy my application.cfc is located in C:\InetPub\some.domain.com\.

I imagine this is my error and that it is glaringly obvious but I don't see it. My application consists of a barebones skeleton that doesn't do anything yet.

Any pointers?

Cheers!
Andy
Andrew Penniman

Andrew Penniman

I moved C:\CFusion8\CustomTags\taffy\ and C:\CFusion8\CustomTags\resources\ into my api root directory and now it's working like a charm.

I'm curious as to why it fails when in the global CustomTags folder and I'd like to find a way to remedy that. So much to do, so much to learn...

Thank you,
Andy
Geoff

Geoff

Just curious about your POST example above... I was under the impression the result of a POST operation should return the URI of what you've just submitted - the browser can then be redirected to this.

Looks like in your example you're querying the inserted record directly and immediately returning the result.

Have I got the process wrong?
Adam

Adam

Gary, thanks for the bug report and fix. I'll definitely get that included in the 1.1 release!

Andy, Taffy is not a set of custom tags. Perhaps you meant to do it with mappings instead? Glad you got it working though...

Geoff, returning the URI of an inserted record is definitely a best practice (but not a requirement), and whether or not you should redirect there would be entirely up to you... That said, there's nothing stopping you from implementing any of that using Taffy. :)
Dan B

Dan B

This is great - I was able to implement in very short order and start building an API.

How would you recommend handling the datasource names in your resources (cfcs) that have database calls? Probably not something you would want to pass as part of the http call correct? Conventional logic would not have it hard-coded or use an application wide variable so I'm curious on how you approach it. thanks
Adam

Adam

Hi Dan, I'm glad you like Taffy! To answer your question, there's no reason you couldn't use application variables. Rules are made to be broken, right?

If you wanted to, you could create a configuration bean object and add a setter to your resources, which would have the bean factory set it into them. If you're already using the ColdSpring integration, this would be especially easy.

Also, just FYI, Taffy 1.1 will be out soon. I've got a first release candidate available. I'll be sure to add your question to the documentation. :)
denny

denny

I remembered what it was that I changed when I started using this on Railo (1.1rc is working fine, BTW).

It was the need to have a separate target for collections.

There are a lot of REST consumers that expect a set up like this:

http://en.wikipedia.org/wiki/Representational_state_transfer#RESTful_web_services

Where there is one endpoint for the representation, including a collection of the representation. I personally prefer this myself.

To get taffy to do this, it was a 2 line change:

https://github.com/denuno/Taffy/commit/de8986d8b57db2aa11c800031bb3157cfe2b9fdb

Good stuff man, thanks!
Carmen

Carmen

As for the error "Element _TAFFY.FACTORY is undefined in a Java object of type class [Ljava.lang.String; referenced as ''" - I encountered this because I did not place my resources in the /resources directory - DOH! I'm on CF9 FYI...

I like Taffy...Thanks!
Adam

Adam

Hi Carmen,

Thanks for the insight. I'll log a bug and see if I can't make Taffy smarter about that!
Stefan

Stefan

Dear all,

I am developing a site running on Coldfusion 9 and would like to use RESTful web service as single-sign-on. I wonder if someone can give me suggestions on where/how to begin?

Thanks a lot in advance!
Scott Conklin

Scott Conklin

Can Taffy be integrated to work with Fw/1? I have an application where most pages are just views being served up by FW/1 through a default layout. Now, i am developing a page within this app that is slowly morphing into a full on JS application with alot of ajax calls, etc.. I am now to the point where i am re-factoring a lot of the JS code into an MVC architecture using BackBone.js I think i want to use something like Taffy to replace my DB calls with a REST API. I am currently sidestepping the Framework and routing pure data calls through a proxy cfc in my webroot.

Can I make these two frameworks play nicely together? I was thinking of having some sort of a mechanism or convention in the application.cfc (which is already extending 'org.corfield.framework' ) that allows to route the request appropriately.
Adam

Adam

Scott,

The nature of the two frameworks, FW/1 and Taffy, is that they cannot co-habit the same Application.cfc. They both work by short-circuiting the application event lifecycle (onRequest/etc), and both require setting an "extends" attribute on Application.cfc to bootstrap the framework.

However, you can easily create a sub-folder of your application (might I suggest /api), and put the Taffy API's Application.cfc in that folder. I haven't experimented with a Taffy API as a sub folder of a FW/1 application, so I don't know if you'll need to add the folder to FW/1's unhandled paths, but keep it in mind in case things seem to go awry.

As for re-using your model (which I assume is the goal), you can do a couple of things.

The simplest of which is to simply create fresh instances of your model objects from your API resources using their paths... e.g. New myapp.model.Foo(); or createObject("component", "myapp.model.Foo");

If you're using ColdSpring in your parent application then you can use the same application name (this.name in Application.cfc) in both the parent and the API to share application variables between the API and the parent application, and thus use the ColdSpring instance from your main app. This has its own implications that are too lengthy to discuss here, so if you go down this road I'd suggest you look at the documentation (https://github.com/atuttle/Taffy/wiki/_pages) and consider subscribing to the mailing list (https://groups.google.com/group/taffy-users), too. :)
Murray

Murray

Hi Adam,

I have just started setting up a test app using your great framework. One extra bit of info you might add to the Getting Started is that you also need to create a \resource folder in your app folder, and then put the resource cfcs in that folder. Seems bleedin' obvious in retrospect!

Thanks, I look forward to playing!

Cheers,
Murray
Steve

Steve

Hi Adam,

I don't understand why this is giving me so much trouble but it is.
Using: CF8
This is my directory structure:
/taffy/bonus
/taffy/core
/taffy/examples

/steve/REST/application.cfc (sets a unique application name only)
/steve/REST/index.cfm (blank)
/steve/REST/resources/memberCollection.cfc (simple get and post functions)

I am able to get the dashboard up in the browser. It does not show any resources, and I feel that is my problem. I however am left at a loss as to how to rectify this. Can you make any suggestions?

I have tried setting a '/resources' mapping in the application.cfc to no avail.
I did it like this:
<cfset This.Mappings["/resources"] = getDirectoryFromPath(getCurrentTemplatePath()) & "resources/">
Adam

Adam

Steve,

That's probably a discussion best had on the mailing list, instead of cluttering up blog comments. :)

Your comment:

Leave this field empty: