Version 0.2 of Transfer ORM library is now ready for download and there is a whole lot of new things to look at.
It's well past midnight here, so I'll go into full detail about this in coming days, but some details of what I've been working on with this version:
- A new area of the site dedicated specifically to Transfer
- A lot more documentation
- Installation is now easier, with the 'transfer_sequence' table no longer needing you to define it's data - it is now handled by Transfer.
- The ability to define your own CFML functions on a Business Object in the Transfer XML configuration file.
- A new mailing list for users of Transfer for the discussion and support of Transfer.
- Updated the example application to incorporate these new features, and include the latest features and new functionality.
I've also sent off a application to cfopen , and hopefully when that gets approved, I will be moving the codebase into CVS on there, not to mention it will be nice to have a centralised bug tracker.
However, I am asking of all the people that downloaded Transfer, or plan to soon (yes, I read my web stats, there were a few of you), is to please, join the mailing list, and then have a good go at testing Transfer for me.
While I had a lot of fun testing Transfer out myself, there are always things the developers never think that someone will do with their software, so I figure there has definitley got to be holes out there for someone to poke in this.
If you have some time, please do test Transfer on supported environments, unsupported environments, and anything else that you feel like, and let me know what happens.
Helping out on #coldfusion on dalnet, the topic of resizing JPEG images without having to install a external Java lib came up.
There are 2 things that make this slightly tricky, but not all together impossible:
- Documentation for com.sun.image.codec.jpeg.JPEGCodec can be very difficult to find if you don't know where you are looking
- Scaling a BufferedImage produces a Image, which can't be encoded into JPEG, so you need to create a new BufferedImage with the given Image.
So, here is the code I used as a test case to take an image of a Nissan 350z and scale it down by a quarter:
<cfscript>
//need this to do all the JPEG work
JpegCodec = createObject("java", "com.sun.image.codec.jpeg.JPEGCodec");
//reading in file
inputStream = createObject("java", "java.io.FileInputStream").init(expandPath("nissan.jpg"));
//pushing out the file
outputStream = createObject("java", "java.io.FileOutputStream").init(expandPath("nissan1.jpg"));
//decodes the JPEG
decoder = jpegCodec.createJPEGDecoder(inputStream);
//give us an image to scale
image = decoder.decodeAsBufferedImage();
//make it 1/4 the size
height = round(image.getHeight() /4);
width = round(image.getWidth() / 4);
//this is java.awt.image.Image, but we need a java.awt.image.BufferedImage!
scaledImage = image.getScaledInstance(JavaCast("int", width), JavaCast("int", height), image.SCALE_DEFAULT);
//create a new BufferedImage
newBufferedImage = createObject("java", "java.awt.image.BufferedImage").init(JavaCast("int", width), JavaCast("int", height), image.TYPE_INT_RGB);
//draw the image on the buffered image
graphics = newBufferedImage.createGraphics();
graphics.drawImage(scaledImage, 0, 0, Javacast("null", ""));
//encode it
encoder = jpegCodec.createJPEGEncoder(outputStream);
encoder.encode(newBufferedImage);
//close off the resources
outputStream.close();
</cfscript>
So there you have it - JPEG scaling without using an external Java library. Enjoy.
Transfer is a library of CFCs for an idea I had a long time ago, and now I am going to pass it out to you for your feedback, and see if what I have developed is sometime useful that should be further developed. This is the first time I've ever released anything substantial into the wide space of the Internet, so please bear with me.
The thought for Transfer was that to ultimately cut back on development time, it would be very handy to simply be able to create your database, and a corresponding xml file, and from there your Business Objects could be created, updated and deleted all without the writing of another piece of code as well as being managed in persistant scopes.
This is done through 2 processes -
- The business object is manufactured within the library, but unlike a code generator, in which you create your code only once, and the CFCs are defined until you run your code generator again, Transfer works by generating only the udfs that are required by each business object, and decorating a generic TransferObject with the required functions at run time when the particular object is requested. This allows for easier development as the code generation is completely implicit, you don't need to manually fire it off.
- The Transfer Library automatically generates all your SQL for CRUD operations, based upon the details specified in the xml config file. This includes SQL for composite objects.
To show you how it works, I'll take you through some basic Transfer Object definitions, and then we'll get a little bit more complicated with some composite object creation.
This is taken from the provided tBlog example application that is available for download here.
If we want to have a User in the system, first we will set up a table that has the following details:
| Name |
Data type
|
IDUser (PK)
|
numeric
|
user_Name
|
varchar(500)
|
user_Email
|
varchar(500)
|
Within our transfer configuration file, we want an object of type 'user.User', and we tell it what table it is from.
<objectDefintions>
<package name="user">
<object name="User" table="tbl_User">
</object>
</package>
</objectDefintions>
We then define the primary key, which will give us a getIDUser() and setIDUser() on the eventual TransferObject
<objectDefintions>
<package name="user">
<object name="User" table="tbl_User">
<id name="IDUser" column="IDUser" type="numeric"/>
</object>
</package>
<objectDefintions>
We then set each of the properties on the 'user.User', in this case, name and email, giving us both get & setName() and get & setEmail() on the user.User TransferObject. Each property has a type, and requires that you specify which column that the property refers to.
<objectDefintions>
<package name="user">
<object name="User" table="tbl_User">
<id name="IDUser" column="IDUser" type="numeric"/>
<property name="Name" type="string" column="user_Name"/>
<property name="Email" type="string" column="user_Email"/>
</object>
</package>
<objectDefintions>
I won't take you into installing the Transfer library, I'll leave that to the (relatively sparse) documentation and the provided example, but we'll assume that the main class transfer.com.Transfer is available to you.
To retrieve a new empty user.User transfer object, all is required is:
<cfscript>
//get a new user
user = transfer.new("user.User");
</cfscript>
To then create the user as a record in the database, we can now:
<cfscript>
//create the user
user.setName("Mark");
user.setEmail("notanemail@email.com");
transfer.create(user);
</cfscript>
To retrieve a record from the database:
<cfscript>
//get a user
user = transfer.get("user.User", url.IDUser);
</cfscript>
Update and delete
<cfscript>
//update a user
user.setName("Fred");
transfer.update(user);
//delete
transfer.delete(user);
</cfscript>
A lot of this is relatively straight forward, however where Transfer really comes into it's own is where it starts handling composite objects.
There are 3 different types of ways in which Transfer can handle different composition of objects, but I will show you one example, so you can get a feel for how it works.
Again, from the tBlog example, we'll look at a Post with Comments (in tBlog, a post has Comments, a User, and multiple Categories, but in this case, we will only worry about Comments).
So first we create a 'post.Post' (I won't show the database table, I figure you have a grasp on how it works), and a 'post.Comment' objects:
<package name="post">
<object name="Post" table="tbl_Post">
<id name="IDPost" type="string" column="IDPost"/>
<property name="Title" type="string" column="post_Title"/>
<property name="Body" type="string" column="post_Body"/>
<property name="DateTime" type="date" column="post_DateTime"/>
</object>
<object name="Comment" table="tbl_Comment">
<id name="IDComment" type="numeric" column="IDComment"/>
<property name="Name" type="string" column="comment_Name"/>
<property name="Value" type="string" column="comment_Value"/>
<property name="DateTime" type="date" column="comment_DateTime"/>
</object>
</package>
This provides us with our basic CRUD operations for the tbl_Comment and tbl_Post, however in the database schema, tbl_Comment has a foreign key, lnkIDPost, that provides a one to many relationship between Post and Comment that we still need to define.
Strangely enough, we do it with an element in <object> called 'onetomany', like so:
<package name="post">
<object name="Post" table="tbl_Post">
<id name="IDPost" type="string" column="IDPost"/>
<property name="Title" type="string" column="post_Title"/>
<property name="Body" type="string" column="post_Body"/>
<property name="DateTime" type="date" column="post_DateTime"/>
<onetomany name="Comment">
<link to="post.Comment" column="lnkIDPost"/>
<collection type="array"/>
</onetomany>
</object>
<object name="Comment" table="tbl_Comment">
<id name="IDComment" type="numeric" column="IDComment"/>
<property name="Name" type="string" column="comment_Name"/>
<property name="Value" type="string" column="comment_Value"/>
<property name="DateTime" type="date" column="comment_DateTime"/>
</object>
</package>
In this, the name of the onetomany relationship is defined, thus giving us methods like 'addComment()', 'getCommentArray()', 'removeComment()' and a few more on the post.Post TransferObject. The onetomany element also sets a 'setParentParent()' and 'getParentPost()' method on the Comment itself, for controlling what post the comment is for.
If we want to create a new Comment, and add it to a post, all we need to do now is:
<cfscript>
post = transfer.get("post.Post", url.id);
comment = transfer.new("post.Comment");
comment.setName(form.name);
comment.setValue(form.comment);
comment.setParentPost(post);
//no need to sort, or add to the parent, it is done implicitly.
transfer.create(comment);
</cfscript>
And presto, the comment is created, and has implicitly been added to the Post.
If we were to go through the post.getCommentArray() we would find the new Comment in there.
You will also note that there is a 'type' attribute of 'array' value on the 'collection' element. This states what sort of collection is going to be created on a post.Post. The other option is 'struct', in which case you must also set a key to be used.
The advantage of having an array however, means we can control the sorting of the Comments, like so:
<object name="Post" table="tbl_Post">
<id name="IDPost" type="string" column="IDPost"/>
<property name="Title" type="string" column="post_Title"/>
<property name="Body" type="string" column="post_Body"/>
<property name="DateTime" type="date" column="post_DateTime"/>
<onetomany name="Comment">
<link to="post.Comment" column="lnkIDPost"/>
<collection type="array">
<order property="DateTime" order="asc"/>
</collection>
</onetomany>
</object>
This does several things - when a post is first retrieved from the database, all its comments will be sorted by their DateTime of entry, however when a new Comment is added, it is automatically software sorted into the correct order.
Therefore, if during development you have decided that sorting shouldn't be on the DateTime property of a post.Comment, but instead on the Name property, all you would need to do is:
<order property="Name" order="asc"/>
and it the change ordering would be handled the next time the xml file is read by the library.
There is a lot more to Transfer that I haven't looked at here, but if this has piqued your interest, the following is available for download:
I'll also make available the latest documentation as I write it at:
Which will get updated whenever I have a spare moment.
If you are going to have a look at what I've written, please do drop me a note, either via the comments below or via my contact form, I would greatly appreciate it. I've learnt a lot writing this library, so hopefully you can get something out of it as well.
Please note, this is pretty much Alpha code. While it does run behind the scenes here, it hasn't had nearly as much testing as is reasonable to run on a mission critical system, so in any way you use it, it is at your own risk.
Woah.
I spent months and months writing the stuff behind this site in my spare time, and finally I can see the results. v2.0 is up and running :o).
I had had a bunch of ideas for different pieces of Coldfusion software to write, some interesting ideas for CSS styling, and a whole internal argument on how to design my code... I decided to rewrite my blog with all those ideas in mind.
After refactor after refactor after refactor (you have no idea how much code I threw away and rewrote), I'm actually happy with the way this thing turned out. In all honesty it's been a great learning experience.
So in the near future you should be seeing me releasing some of the things I've written that are utlised behind the scenes on this blog, which is very exciting for me, as I've never really 'open sourced' anything before, and I hope everyone who looks at what I've written can (a) find it useful and (b) learn as much as I did writing them.
So keep your eyes peeled for a little 'Projects' box that will eventually sit on the right hand side, and various other things I've got in my head to go on this site, and hopefully you will enjoy your stay.
One thing that ColdFusion has always done in a relatively odd way was, it tended to pass Structs by reference, and arrays by value.
This seems to be a decision that was made back in the hey-day of the product, and has been continued since then to maintain backwards compatibility.
But I started noting something that I was doing inside my CFCs, that lead me to believe (and realise) that arrays are only passed by value sometimes.
This came to pass when I would often have a getter and a setter method for an array inside a CFC, and would often perform operations on it inside the CFC instance like so:
<cfscript>
ArrayAppend(getThingArray(), newThing);
</cfscript>
and had never really thought anything about it.
However, if you look at it, if getThingArray() (which returns an array), it shouldn't actually change the state of the object if I call ArrayAppend() on it, as it should have returned the array by value.
That being said, it does change the value, so we can see here in this case, it does return the array by reference, not by value.
Some more investigation reveals that an array is passed by reference only when the array is returned inside a ColdFusion native function, such as ArrayAppend() or ArrayDeleteAt().
This has some interesting consequences for encapsulation that you may need to be aware of, for example, doing:
<cfscript>
obj = createObject("component", "obj").init();
array = obj.getArray();
ArrayAppend(array, "george");
</cfscript>
Will only ever result in the external array being changed, the state of 'obj' will never be altered, however if we:
<cfscript>
obj = createObject("component", "obj").init();
arrayAppend(obj.getArray(), "fred");
</cfscript>
This will mean that the state of 'obj' will be changed, and 'fred' will be appended to the array stored inside of 'obj'.
Depending on how strong you want your encapsulation to be, this could be an issue for you. The same would apply for structs as they are always passed on by reference, however it seems to be a little less obvious when applied to arrays.
This was just something I picked up on the other day and thought I would share it, in case it tripped someone up along the way.