Posted by Sam on Jun 18, 2007 at 02:50 PM UTC - 6 hrs
This one refers to the 40+ minute presentation by Obie Fernandez on Agile DSL Development in Ruby. (Is InfoQ not one of the greatest resources ever?)
You should really view the video for better details, but I'll give a little rundown of the talk here.
Obie starts out talking about how you should design the language first with the domain expert, constantly refining it until it is good - and only then should you worry about implementing it (this is about the same procedure you'd follow if you were building an expert system as well). That's where most of the Agility comes into play.
More...
Later, he moves on to describe four different types of design for your DSL. This was something I hadn't really thought about before, therefore it was the most interesting for me. Here are the four types:
- Instantiation: The DSL consists simply of methods on an object. This is not much different from normal programming. He says it is "DSLish," perhaps the way Blaine Buxton looks at it.
- Class Macros: DSL as methods on some ancestor class, and subclasses can then use those methods to tweak the behavior of themselves and their subclasses. This follows a declarative style of programming, and is the type of DSL followed by Ruby on Rails (and cfrails) if I understand him correctly.
- Top-level methods: Your application defines the DSL as a set of top-level methods, and then invokes
load with the path to your DSL script. When those methods are called in the configuration file, they modify some central (typically global) data, which your application uses to determine how it should execute. This is like scripting, and (again) if I understood correctly, this is the style my (almost working correctly) Partial Order Planner uses.
- Sandboxing (aka Contexts): Similar to the Instantiation style, but with more magic. Your DSL is defined as methods of some object, but that object is really just a "sandbox." Interaction with the object's methods modifies some state in the sandbox, which is then queried by the application. This is useful for processing user-maintained scripts kept in a database, and you can vary the behavior by changing the execution context.
Note: These are "almost" quotes from the slides/talk (with a couple of comments by myself), but I didn't want to keep rewinding and such, so they aren't exact. Therefore, I'm giving him credit for the good stuff, but I stake a claim on any mistakes made.
The talk uses Ruby as the implementation language for the example internal DSLs, but I think the four types mentioned above can be generalized. At the least, they are possible in ColdFusion - but with Java, I'm not so sure. In particular, I think you'd need mixin ability for the Top-level methods style of DSL.
Next, Obie catalogs some of the features of Ruby you see used quite often in crafting DSLs: Symbols (because they have less noise than strings), Blocks (for delayed evaluation of code), Modules (for cleaner separation of code), Splats (for parameter arrays), the different types of eval (for dynamic evaluation), and define_method and alias_method .
Finally, he winds up the presentation with a bit about BNL. I liked this part because I found Jay Fields' blog and his series of articles about BNL.
As always, comments, questions, and thoughts are appreciated. Flames - not so much.
Hey! Why don't you make your life easier and subscribe to the full post
or short blurb RSS feed? I'm so confident you'll love my smelly pasta plate
wisdom that I'm offering a no-strings-attached, lifetime money back guarantee!
Posted by Sam on Jun 12, 2007 at 04:43 PM UTC - 6 hrs
It's been a couple of months, but cfrails has been "officially" updated (as in, I released a new zip, not just put new code into the repository).
We're a lot closer to 1.0 than one quarter of the way, so soon you should be seeing higher version number jumps. Anyway, here's what's been updated with this release:
More...
-
Added the automatic include of views/layouts/application_header.cfm and views/layouts/application_footer.cfm even when using view CFC (formerly, this only happened if you instructed it to and using .cfm templates for views). Also added auto-include of controller-specific templates simply by placing controllerName_header.cfm and controllerName_footer.cfm in views/layouts.
-
Brought the existing unit tests up to date (amazingly, with all the neglect there was only a couple of very minor problems, mostly due to changing paths).
-
Migrated request processing out of individual applications and into Dispatcher.cfc. This is a major improvement in the DRY arena, and allows me to make changes to the ways requests are processed without having to make them to every application that uses cfrails (Thanks to Dan Lancelot for prodding me to do this a couple of months back).
-
Added some minor Spry integration for form validation - this only works with required text fields at the moment.
-
Added the ability to use primary keys that aren't named "id" (although, still no composite PK support)
-
Changed behavior of a particular file to not catch errors (it was getting hard to debug)
-
Added an onchange attribute for form elements (can edit onchange per column without writing out the entire form)
-
cfrails now automatically creates blank controller, model, and view cfcs if you request one where it doesn't exist. This just makes it easier to develop, and may end up being a setting you turn on and off.
-
Added validations where you can specify a function to call and a message to display upon failure for validating forms (as opposed to simply using the built-in autovalidation based on DBMS metadata).
-
Added support for bigint, tinyint, and smalldatetime data types.
-
Added the ability to call a function when outputting a column in a list (though complete closures are not yet fully integrated)
So, there was a lot done as you can tell. And, MySQL support is coming soon, I promise!
Update: Oh yeah, I forgot what might be the most important part: New, more complete docs on cfrails are available (thanks to cfcdoc).
Last modified on Jun 12, 2007 at 04:47 PM UTC - 6 hrs
Posted by Sam on May 23, 2007 at 11:23 AM UTC - 6 hrs
Something I haven't thought much about, but am beginning to (want to) get into is the auto-generation of reports. Autogenerating forms, validation, and operations for CRUD is quite simple - the approach I've used simply gets what metadata it can from the database, and dynamically builds around it. So, NOT NULL fields become required, datetime s build different form fields from nvarchar s, and validate as such (as do other types), and field maxlength s are honored on the HTML side based on the DB metadata (among other things). That is incredibly useful by itself, but not useful enough for a real, production quality application (should the user really be required to remember the categoryID ?)
More...
Of course not -- some fields should map to a foreign key which should show up as a select form element, which lists the human readable descriptions of those fields (as opposed to their database identifiers). Further, some fields should be hidden, and some fields should be compositions of others. Values for some fields should be in a range of legitimate values, and so the list of possible customizations goes on. In those special cases (which occur frequently enough), you just need to provide a means to override the default behaviors (through extra, user-defined metadata, a DSL, or some other tactic or a combination of them).
Then, in the really rare edge cases, you can always drop out of the framework and code it yourself (or have it generate a template for you to modify). Even for search forms (which I think are more often in need of customization than simple CRUD stuff), you can provide the basics and allow the programmer to customize as needed (and drop out of the framework all together if need be).
But is there a similar strategy for generating reports? My initial thought is that there isn't. Reports just seem to need too much customization to have any useful automagic generation. But, I'm trying to look past that initial impression, because I don't have as many years of experience in hand-writing the same crap code over and over again in reporting as I do in writing CRUD operations (now, I have the same crap code for CRUD in one place and it just figures out what to do, but at least I don't ever need to write it again =)).
In any case, I just don't see it. But, I can see using metadata/DSL, where you can define the tables and columns you want to report on, and generally it will still be much faster for developing reports (can you tell I'm trying to work on this at the moment?). Taking the idea further, what about bringing convention over configuration into this realm? Once you get out of the world of 8.3 filenames and realize you can name variables (or columns) pretty much anything you want, it is easy to see the benefit of having names that describe what you want the user to see. For instance, instead of orderGTot , just go ahead and name the column order_grand_total or orderGrandTotal . Who cares about the extra characters? It's not like you have to type them that many times, since the framework (presumably) takes care of most of it for you, and you don't have to define what the user should see in more than one place.
Of course, this is also just metadata. But, instead of having what amounts to the same metadata in different places, you've just got it in one - your column name. So if we take this idea to reporting, what if we just tag certain columns with something like _reportable ? I haven't figured out how we might generate reports on that amount of information alone, but I see it as a useful start.
When your classes average between zero and twenty lines of code (which amounts to just customizing interfaces and performing validations, along with perhaps a couple of calculations), it's time to stop worrying about DAOs, DTOs, PPOs, and Cheerios. Skip to the IPO and let the frameworks worry about the rest. I want one to do my reporting for me, because that seems to be one of the last major hurdles that I see (at least, as far as getting rid of repetition goes).
What do you think?
Posted by Sam on May 07, 2007 at 09:20 AM UTC - 6 hrs
As I'm getting into little details about the generation cfrails is doing, I had a couple of questions I thought the community could provide some insight on better than my own experiences regarding lists.
One of the great things about generating this stuff is that you can have for free all the bells and whistles that used to take a long time to do. In particular, you can have sorting on columns automatically generated, as well as pagination.
So question 1 is: given that you can have sorting for free, would you rather automatically sort on every column, and specify any columns you did not want to sort on, or would you prefer to not have sorting placed automatically, but just specify which columns to sort on?
And question 2: Given that more and more people are on broadband, is it time to up the 10-record limit on results? I find it annoying to have to reload all the time, and if given the option, I normally up the results/page to 50 or 100. What do you think, would you make the default number of results/page higher (and how high would you take it?), or would you cater to the lowest common denominator?
Posted by Sam on May 01, 2007 at 09:36 AM UTC - 6 hrs
There are a couple of drawbacks (or some incompleteness) to scaffolding being truly useful. The one that seems to be most often cited is that normally (at least in Ruby on Rails, which seems to have popularized it) it looks like crap (it is only scaffolding though). Of course, most who make that complaint also recognize that it is only a starting point from which you should build.
Django has been generating (what I feel is) production-quality "admin" scaffolding since I first heard about it. It looks clean and professional. Compare that to the bare-bones you get by default in Rails, and you're left wondering, "why didn't they do that?" Well, as it happens, it has been done. In particular, there are at least 4 such products for use with Rails: Rails AutoAdmin (which professes to be "heavily inspired by the Django administration system"), ActiveScaffold (which is just entering RC2 status), Hobo, and Streamlined (whose site is down for me at the moment).
More...
My interest lies in the second complaint though - that writing it to a live file (rather than dynamically figuring it out - which I've been calling "synthesis," as opposed to generation) means you can't get the benefits of upgrades to the scaffolding engine (and, it is not following DRY!). But, if you just use the default scaffolding, what happens if it doesn't come out just right (assuming you've even passed the notUgly test in the first place)? Well, thats a big part of what I'm trying to solve with cfrails, by using a DSL that provides tweakability to the scaffolding, without requiring you to write to a file (though, if the tweaks won't work, you are always welcome to go to a file with HTML and the like). The interesting part for me about the RoR plugins above, therefore, is that it appears (I haven't checked them out yet) that at least Hobo contains a DSL, called DRYML, to help along those lines.
I'll be having a closer look at those when free time becomes a bit less scarce. What do you think? Is it the holy grail, or can there be very useful
"scaffolding"?
Last modified on May 01, 2007 at 09:37 AM UTC - 6 hrs
Posted by Sam on Apr 26, 2007 at 10:54 AM UTC - 6 hrs
Back in December, I had a post about why closures are useful. In particular, I mentioned what I called the "generalized Template Method" pattern as a benefit: basically, you have a function where, when the user calls it, you want them to be able to change its behavior in some way.
I was short of practical examples, but today I came across one. I've got a list() function that behaves identically for each object it is called on. Basically, it figures out what properties the object has that should be listed, and creates a table that lists them for some query result set. Normally, this just outputs the variable's value on a cell with a white background. The slight change in this case was that depending on the value, the list should set a background color to one particular cell.
More...
Being that I've been burned a lot in the past by not following the DRY and OAOO principles, I'm quite fanatacal about following them nowadays. So when I needed the list() function to behave a bit differently for this one type of object, I didn't want to replicate the entire list function -- I just wanted to add a bit of logic in the middle. That's where closures enter the picture.
I decided I'd create a function, onOutputOf(column="column that special stuff needs to be done to", run="function, or code") . So, instead of outputting the value of the cell, the list now knows to run the code provided in the run parameter.
And now, some really complex code doesn't need to be replicated elsewhere. Isn't that sweet?
Last modified on Apr 26, 2007 at 10:54 AM UTC - 6 hrs
Posted by Sam on Apr 24, 2007 at 09:05 AM UTC - 6 hrs
This morning has been strange. On the drive to work, I started out thinking about encapsulation, and how much I hate the thought of generating a bunch of getAttribute() methods for components that extend cfrails.Model (in cfrails, of course). To top it off, I don't know how I'd generate these methods other than to write them to a file and then cfinclude it. But as I said, I really hate the idea of (say in the views) having to write code like #person.getSocialSecurityNumber()# . That's just ugly.
But then again, I don't like the alternative of leaving them public in the this scope either, because then there is no method to override if you wanted to get or set based on some calculations (of course, you could provide your own, but you'd have to remember to use them, and the attributes themselves would remain public. Currently, this is the way its done, because I feel like providing default getters and setters is not really encapsulating anything on its own. The encapsulation part only enters the game when you are hiding some implementation of how they are calculated or set.
More...
Then, of course there are the generic getters and setters that Peter Bell is often talking about. You know, where you have some implementation like (excuse the pseudocode -- I'm just lazy right now =), but it shows the idea):
function get(attr)
{
if (methodExists("get"+attr)) return callMethod("get"+attr);
else return variables[attr];
}
This is easy enough to implement without resorting to file I/O, and it has the side benefit of allowing you to check if a getAttribute() method already exists, and call it if so. And where this morning starts getting wierd is that I randomly came across this post from David Harris where he and Peter are discussing this strategy in the comments. What a coincidence.
But what I really wanted is something you see in Ruby (and other languages too): the attr_accessor (or reader and writer) in Ruby. You can do something like this:
class Cow
attr_accessor :gender, :cowtype
# or equivalently, we can have writers and readers separate:
# attr_reader :gender, :cowtype
# attr_writer :gender, :cowtype
def initialize(g, t)
@gender= g
@cowtype = t
end
end
elsie = Cow.new("female", "milk")
#if we did not have the attr_reader defined, the next line would choke
puts "Elsie is a " + elsie.cowtype + " cow."
#change the cowtype + this line would break without the writer defined
elsie.cowtype = "meat"
puts "Elsie is now a " + elsie.cowtype + " cow."
#here we'll change the class (this is happening at runtime)
class Cow
# add a setter for cowtype, which overrides the default one
def cowtype=(t)
@cowtype="moo " + t
end
end
#change elsie to a model cow
elsie.cowtype = "model"
#what type of cow is elsie now?
puts "What a hot mamma! She's turned into a " + elsie.cowtype + "!"
#she's a moo model!
See how easy that is?! That's what I really want to be able to do in ColdFusion. And the best part is, it really would only (as far as I can tell) require a couple of changes to the language. The first one being something like if (cowtype= is defined as a method on the object I'm working with) call it when doing an assignment; and the second one is being able to define a method like so:
<cffunction name="cowtype=">
</cffunction>
In any case, the morning got more coincidental when I ran into this post on InfoQ about Adding Properties to Ruby Metaprogramatically, which I'd recommend reading if you're wanting to metaprogram with Ruby (or if you are just interested in that sort of thing).
It's now time for me to crawl back into my hole and write about the wonderful world of The History of Partial-Order Planners. (The good news on that is I'm getting close to the fun part - actually programming one).
So method= is going on my wishlist for CF9, and in the mean time I'll probably end up going the generic getter/setter route. What are your thoughts? What would you go with?
Posted by Sam on Mar 30, 2007 at 03:12 PM UTC - 6 hrs
One of the great benefits of using a framework (in the general sense) is the freedom in portability it often gives you. Rather than writing your own Ajax routines which would need to handle browser incompatibilities, you can rely on Prototype/ Scriptaculous, AjaxCFC, Spry, or a seemingly infinite number of Ajax frameworks available. We see the same phenomenon in our use of ColdFusion, which uses Java to be cross-platform. And, you can get the benefit for databases by using a framework like Transfer or Reactor or ActiveRecord in Ruby (all also have a slew of other benefits). In general, we're simply seeing an abstracting away of the differences between low-level things so we can think at a much higher level than "oh crap, what if they're using Lynx!" (Ok, I'm doubting any of those Ajax frameworks fully support Lynx =), but you get the picture)
More...
I can honestly say I've never had to build an application that needed to be portable between databases, aside from between different versions by the same vendor (although there was once where it might have been nice, since I used the apparently impossible to find in hosting combination of Microsoft SQL Server and Java). My guess is that unless you're building software which is meant to be deployed on different databases (such as a framework, library, or software you plan to sell over and over again which you won't be hosting), you probably haven't needed to do so either... Or at least, not often enough to let it impact your coding.
So today, I started my quest to turn cfrails into a DB-independent framework (as of today, March 30, 2007 it works only with Microsoft SQL Server). Since one of my goals is to limit the amount of configuration the programmer has to provide to get up and running, simply telling cfrails your database metadata (instead of having it look in the database itself) was out of the question (XML or otherwise). I've looked at using CF's getMetaData(query_result) function in the past, but that doesn't have near the amount of data that would be useful for me.
I figured my best bet would be to drop down into Java and use its DatabaseMetaData interface. This way, I could let Java take care of the differences between DB implementations for me. Sure enough, looking over the docs, it had everything I needed. I went ahead and whipped up some code to use it and test it before trying to put it into Coldfusion. If you're interested, here's most of what I needed (only using the jdbc/odbc bridge). If not, feel free to skip this code:
// at the top of the file, you'll need to import java.sql.*
// and of course, define your class as you normally would.
Connection connection=null;
String driverPrefixURL = "jdbc:odbc:";
String dataSource = "ODBC Datasource Name";
String username = "user";
String password = "password";
String catalog = "which database";
String schema = "dbo";
String tableYouWantColumnsFor = "sometable";
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
connection = DriverManager.getConnection(driverPrefixURL+dataSource, username, password);
DatabaseMetaData dbmeta = connection.getMetaData();
ResultSet cols = dbmeta.getColumns(catalog,schema,tableYouWantColumnsFor,"%");
while (cols.next())
{
// this is all going to be unformatted as-is
System.out.print(cols.getString("COLUMN_NAME") + " ");
System.out.print(cols.getString("DATA_TYPE") + " ");
System.out.print(cols.getString("TYPE_NAME") + " ");
System.out.print(cols.getString("COLUMN_SIZE") + " ");
System.out.print(cols.getString("IS_NULLABLE") + " ");
// no clue why these two lines are throwing errors
//System.out.print(cols.getString("COLUMN_DEF") + " ");
//System.out.print(cols.getString("ORDINAL_POSITION") + " ");
System.out.println(); // skip to the next line
}
connection.close();
}
catch (Exception e)
{
e.printStackTrace();
// probably should be in finally block, but didn't worry about it as this was just a test
try
{
connection.close();
}
catch (Exception ex)
{
System.out.println("Could not close connection - probably wasn't open.");
}
}
(if you need more info, see the docs I linked above -- this is just a small sampling of the metadata available for columns in a table)
Sweet! I didn't really need ORDINAL_POSITION since it already ordered the results by it, and COLUMN_DEF (the column's default value) wasn't all that important (well, I could figure out a way to get that later, anyway). But, when I went to put it into ColdFusion, I realized - I have no clue how I might get the information I need to connect- forget about the fact that I'm not sure the average developer (CF or otherwise) will know what a catalog or schema is, or which one they need to access if they do. Who in the world isn't going to have to go through some work to figure out what driver they need and what the connection URL is?
So, I needed a way to let Java know these things through CF. I thought I could get at least some of that information from the CFAdminAPI (or whatever the proper case and spelling is), but I didn't really like that option. I couldn't think of any other option that would allow me to use Java to get the metadata, so I decided I'd have a look at the codebases for Transfer ORM, Reactor, and DataMgr and see how those smart guys did it. I only browsed, but I didn't find anything useful for me trying to use Java to do the trick. So, it was back to writing a DB-specific implementation for each database I wanted to support.
Luckily for me, even though my design doesn't approach the modularity and complexity of Reactor or Transfer (yet... I'm a big fan of YAGNI and haven't had any reason to break out of the simple design I currently have and Transfer is downright confusing for me to quickly browse it and figure out what's going on), it is quite flexible enough for me to do the different implementations and integrate them with ease. To do so, I only need to refactor the current routine that gets the MSSQL Server metadata, and provide a way to figure out which database type it is looking in (each of these only has to happen once). After that, all that remains is to write a query to get the metadata and convert its output (for each implementation) to the interface the rest of my code expects. Not hard by any means, but it would have been cool for maintenance purposes and greater overall simplicity if I could have let Java handle it.
So now that you've gotten this far in this boring post with no conclusion, have you used Java to get the DB metadata in CF (or even outside of CF)? Have you found a way to do it without making the programmer specify the connection URL and driver?
Posted by Sam on Mar 30, 2007 at 11:48 AM UTC - 6 hrs
A thought occurred to me today- just because something doesn't offer all you need, doesn't mean you can't put it to good reuse. This goes back to Sean Corfield's post about why you should avoid the not-invented-here syndrome, my own post about why I thought I would release cfrails (and some of the decisions therein), and a multitude of others who realize that reinventing the wheel is not generally productive.
I had the realization today (though, I must admit it is quite obvious!) that there would be nothing wrong with adding an extra layer of abstraction, which would in turn add the features you needed to some other product/library/code. So when I thought "well, I can't use Transfer or Reactor" (what ever happened to Arf!?) because they didn't provide all the metadata I needed, I could have still built on top of them. I guess the thought never occurred to me back then.
In any case, it has now. Although I'm far enough along that I don't think doing this will save me any more time, it's something I plan on investigating- at the very least, it could provide a familiar API to use (and of course, the framework would be generating any XML files rather than having the programmer modify them).
So basically, if you think you don't want to use a pre-made product - at least you should look into the possibility of building on top of it.
Posted by Sam on Mar 25, 2007 at 07:56 AM UTC - 6 hrs
Just a quick note - thanks to help and some pointers from Dan Lancelot (I'm so tempted to write "Sir" - but will refrain for the time being =)), I've updated the cfrails distribution and repository over at RIAForge with a couple of bug fixes - one in particular a big one that made a liar out of me (I had forgotten to update the app_skeleton with the latest router).
Thanks Dan!
Posted by Sam on Mar 01, 2007 at 05:13 PM UTC - 6 hrs
Just a quick note: the online class/method documentation for cfrails has been updated to include most of the available methods. All that is missing are the function aliases between functionName and function_name are missing, since that isn't generated automatically. If you happen to be using it and notice that one function isn't available either way, be sure to let me know.
It's been a while, but I'm glad to have that off my to-do list. You can find the cfrails docs here.
Posted by Sam on Feb 25, 2007 at 12:57 PM UTC - 6 hrs
Sean Corfield's post about the perils of NIH and RTW and Peter Bell's subsequent one entitled Should you Release Your Framework? got me thinking about my own decision to release cfrails.
Sean mentioned that the not-invented here syndrome, along with reinventing the wheel, "plagues any coherent attempt at Open Source within the ColdFusion community - look at the number of half-baked calendars available (and don't get me started on the unit testing frameworks!)." I wanted to add, "and look at all the generation going on!" (as I can think of at least 5 such projects off the top of my head, my own being one of them.
In general, NIH and RTW are bad things. And Sean is right, to a degree. But I also agree with the sentiment of Peter's post: more is better but if it is substantially similar to another project, it may be better to contribute to that one instead.
More...
I'm a big fan of choice, and of learning, so I tend to think that having more choice (to learn from as well) is better, but if choices are substantially similar, one should certainly strongly consider contributing to the similar project. Of course, a question reveals itself here: "how can I be sure what I'm doing is substantially different?" One cannot take the time to review every framework out there, and probably asking for just a couple is too much. In my case, I didn't review all the frameworks I knew about, but I did read up on them, at least. (As a side note, I don't know when Model Glue started generating code, can anyone confirm when for sure - has it always been there?)
I feel like cfrails is "substantially" different than anything I've seen (even CF on Wheels, which I also plan to contribute to), but I always worry if what I'm doing may at some time be considered to be stepping on toes. I don't want to be perceived that way, for sure.
Further, I don't think I'm solving any problem that hasn't been solved before, but I do think I'm doing it in a different way - and I think there is merit in that, even if, in doing so, I am duplicating some of the effort of others (for instance, cfrails has its own ORM, but I felt that changing what I knew about the existing ones might make them more specific than they were intended to be). With all that being said, I think generating code, while certainly being nothing new in computer science or even ColdFusion, is relatively new to being done publicly or in Open Source in ColdFusion. So, even though there are a lot to choose from, right now, it is a good thing, in my opinion, as we are all able to learn from each other, and may discover better ways of doing things.
In the end, as far as the generators go, it seems most of them are quite different in how they tackle the problem, and some of them even in the problems they solve. I think it would be great if those behind the various frameworks would write about how they came about (or, if you don't mind, leave a link here if you've already written it) and what problems they are intended to solve, and how they solve those problems. That might be a great learning exercise for all of us in the community (I plan to write that up for cfrails after the next couple of weeks -- midterms, you know? =) ).
Posted by Sam on Feb 20, 2007 at 06:34 PM UTC - 6 hrs
I can't remember the last time I used anything resembling a stack or queue (other than what I'm about to blog about). Of course, I've used lists and arrays, but not with the same intent you often see in the use of a typical LIFO or FIFO mechanism. I did have occasion to use the concept in the last couple of updates to cfrails, however, so I thought I'd bring it up in case anyone else came across the same use as I have. Let me explain how I arrived at
the need (which also doubles as a little documentation):
Using cfrails, to make a model you simply go into your project and create a CFC that extends cfrails.model in the model folder. Assuming that file has the same name as a table in your database, you've now got an object (after it is instantiated, of course) with all the basic CRUD methods. The case is similar for the view or controller, where you'd be in the appropriate directory, but name it modelname_view.cfc or modelname_controller.cfc. There is a way to change the name of the table if you don't want the model name to match the table for some reason, but I'll not go over it here, as it's an "undocumented feature" at the moment.
More...
So, now we have a table, let's call it drink . It has columns
id (int, pk, identity), brandname (nvarchar 50), label (nvarchar 50), bottle_date (datetime),
and
description (ntext) .
The code currently looks like:
<cfcomponent extends="cfrails.model">
</cfcomponent>
In this case, assuming you have the corresponding controller and view set up, cfrails will show label as "Label" and
bottle_date as "Bottle Date" to the user. description will be capitalized as well. Label will be shown as
an <input type="text"/> and description as a <textarea> .
But while brandname would show up as an input text field too, it will be displayed as "Brandname."
Now, if you had called the column brand_name or brandName , the space would be put in automatically.
That's easy enough to do with new tables, but if you're working on an old one where you had used the convention
numBottles , for instance, it would show up as "Num Bottles," which is not something you want displayed to the user.
Another issue shows up with bottle_date . Since it is a datetime column,
cfrails will display it with a select for each of year, month, day, hour, minute (when I get around to
implementing that). But in this case, you might only care about the month and the year.
How do you solve those two problems? That's where some of the configurations / extended metadata comes in.
One of the goals I have in cfrails is to keep related data together.
I'm not fond of the thought that I might need to have to open an extra file to configure my class. So, if
I want the view to show the name of brandname as "Brand Name," I should be able to do that within
drink_view.cfc (actually, you can hand-code your views in templates if the customization options
don't fit the bill, but I'm not getting into that here either).
What I wanted to be able to do was have my files look like this:
<cfcomponent extends="cfrails.view">
<!--- configuration --->
<cfset setHumanName("brandname", "Brand Name")>
<cfset setFieldType("bottle_date","month/YearDate")>
<!--- some extra functions I need --->
...
</cfcomponent>
(As a side note, you could use the underscore version of those functions as well. Also,
the month/YearDate has not yet been implemented as of Feb. 20, 2007, and is subject to change,
but you get the idea.)
That certainly seems easy enough. But, since the controller has to pass the model into
the view through the view's init() method, that configuration code at the top will be
executed before the view has any knowledge of a field called " brandname ."
It was a long time coming, but we've finally reached the point where I needed a stack
(well, the order didn't really matter, so what I implemented wasn't really a stack, but it served
the same purpose).
Instead of running the actual function when it is called, I checked if the component was fully instantiated.
If not, I "stack" each function that needs to be run, along with the supplied parameters.
Then, in the init() method, the stacks are "popped" (it's actually done in a queue fashion, but
it feels odd saying that).
Thoughts?
Last modified on Feb 20, 2007 at 06:36 PM UTC - 6 hrs
Posted by Sam on Feb 14, 2007 at 01:54 PM UTC - 6 hrs
If you're not familiar with what cfrails is, it is a somewhat new framework (I started it in November 2006) for building applications in ColdFusion. It does things like saving records, getting records, populating forms, and data validation automatically for you. It also provides nice abstractions for those things which it can't automatically do based on the database metadata (for instance, you may want a month/year date field, rather than a full date). Since I don't want to take too much of your time describing it, more info on it can be found on this blog post about cfrails 0.2.0 or at the cfrails RIAForge project page.
This update is a super-minor version change, but a significant one.
First, a couple of minor items - I added some more aliases to have functions read better.
For instance, instead of doing set_title_new() to change the default
title on a view's new item form, you can now write newform.set_title() . Similar
additions were made to the record listing table and edit forms. There will probably be other small
aliases as well, as I discover more awkward function names. Some of those
old ones will go away, but I'll keep the ones that aren't awkward. Just as well, I also made a few more aliases of the type camelCaseFunction = camel_case_function , to allow everyone to keep their respective programming styles. However, if you are naming arguments, you're currently at the mercy of whatever I have called them. I plan to standardize soon - its just weird thinking in Ruby and programming in CF - I'm mixing styles a lot.
More...
I also noticed that I needed to mix views on occasion (for instance, a Person_view might need
to display a list of checks written, which would be in another component). For this reason, I
added getModel(modelName) and getView(viewName) to the mix. This is a great
abstraction to remove the long and repetitive createObject calls. The downside is that apparently,
CF6.1 and CF7.0 look components up differently, so these don't work (depending on your directory structure, I think) in the older version of CF.
The most significant addition, however, is that there is now some ability for relationships among tables to be added into your models without much effort: via a call to the autoload_relationship() method in your models. This was a bigger pain than I thought it would be, but well worth the effort.
Overall, I'm starting to get excited about the progress being made on this. I've been using it
to extend an application we had written some time ago, and the results are good. For comparison,
I took one of the "old" modules that was written without cfrails, of similar size to
one I am working on now. The one written using cfrails is a bit smaller in scope and less
complex, so I added in the other 2 "modules" I've been working on using it as well. Whereas the
"old" one was over 500 lines, the 3 parts (two are quite small) using cfrails total to about 65, including white space.
I spend more time marveling at the thing than I do programming (just kidding, of course =) ). Of course,
this isn't any scientific comparison, especially since I'm sure I've grown as a programmer since writing
the other one. But, it is still pretty remarkable. The hardest part is going to be memorizing the function
calls. Well, not that it's hard, but it will take time looking at the (almost non-existent) docs,
which may slow you down.
So, what's ahead? First I need to reconcile differences between CF7 and 6.1. After that, I need to add in abstractions to make "select" form inputs, as well as groups of radio buttons and checkboxes. There are some other abstractions I'm thinking of putting in, but I'm not sure if they add any value or just clutter the interface, so I'm leaving them out for now. Other than that, there are some more options I'd like to put in, like concatenating columns of a (HTML) table, to make the list more customizable. Finally, a few more major things are integration with an Ajax framework, integration with more DBMSs (right now, only SQL Server 2000 and 2005 are supported), and I'm toying with the idea of automatically loading referenced tables when there are columns of the type tablename_id.
The biggest thing is re-writing the documentation. The only docs I have right now are based on version 0.1.0.
Clearly, a lot has changed since then.
Those are the things off the top of my head. Even though the version number is only 0.2.1,
I'm starting to feel awfully close to version 1. The main thing is that I want the interfaces (of the code variety) to be intuitive and easy to use, so I'm getting practice in using it. I may try to write a different application using it before I go to version 1, to make sure that it remains as easy to use in that one as it is in the application I'm using it for now.
Posted by Sam on Jan 24, 2007 at 10:39 PM UTC - 6 hrs
When I wrote that PseudoObject.cfc would be useless, I was thinking that all objects created would need 3 file operations, and thus, it would be quite unlikely to perform any better (or even on par with) normal object creation.
Based on the way I implemented it in that post, it would need three file operations.
But sitting in my data management class today, I was reminded of ORM because the professor noted that we wouldn't normally think of methods acting on an entity in the database, while each entity would have every other thing we would find in a typical object.
In any case, I realized you could move the file operations out a bit, and they would only need to be run once if you were building a ton of objects that shared the same type. This, I would think would be a substantial improvement over creating tons of real objects. And where would you do that? In an ORM framework - if you wanted to return an array of structs as objects. Then, each could be manipulated as if they were a real object!
Now, it would be freakin' sweet if you can attach a function to each row of the query like this. I expect it would be straight forward if possible, but if not, we can still see a lot of possibilities for ORMs out there (I'll probably test that tomorrow). I plan to implement something along these lines in cfrails, if it does hold up for thousands of objects.
Last modified on Jan 24, 2007 at 10:41 PM UTC - 6 hrs
Posted by Sam on Jan 15, 2007 at 09:35 AM UTC - 6 hrs
(and functions with arguments can take more than the defined amount)
This may be well known, but I haven't seen a lot (or anything) on it. Of course, as always,
I may just be missing something.
In any case, the other day as I was looking over some old code,
I (re)discovered this marvelous fact. Now, you may be wondering why on Earth I'd want
to use arguments in a function where none were defined. But, I have at least one case
where I think it's valuable: suppose you are following the
Active Record Pattern, or really
writing any ORM. Basically, you want to
abstract the query process. Now, that's certainly a noble goal. But, what happens when
you want to provide a filter? For instance, you might have a function find_by_id() ,
a which finds a record based on the id you pass in. That's easy enough.
You might even provide methods to find_by_other_columns_even_in_combinations() .
More...
But surely you can't provide every combination! That's where you'd want to
provide a filter argument. Here it comes again: another but! You don't want
to simply provide filter="where column1=#form.column1#" - you want to parameterize
the query for the developer. Now, one way to do that would be to parse the
filter argument and reconstruct it using your cfqueryparam s in all the right
places. But, an easier way would be to something like this:
find_records("where age=? and (name=? or name=?)", 9, "blockhead", "charlie brown")
Note: I would probably move the "where" into the abstraction, and just let the programmer provide the clause,
but for illustration purposes, I left it in there.
For illustration purposes, my function only does this:
<cfcomponent>
<cffunction name="find_records" output="true" >
<cfargument name="filter" >
<cfloop collection="#arguments#" item="key" >
#key# = #arguments[key]#<br/>
</cfloop>
</cffunction>
</cfcomponent>
But it would be easy enough to modify it to do something useful. Right now, if you run it, you see
all the arguments are available in the arguments structure. The first one comes under the key
"filter", and the rest come under numeric keys, in the order they were passed (of course, looping over it won't
show you that order, but the number of the key represents the position in which it was passed. Now, you can
parameterize the arguments, first checking more restrictive and moving to less restrictive parameters
(for instance, you'd want to check isDate() , isNumeric() , and fall back on string).
You could name the arguments too, but I haven't found that useful for this example, since they will have their
names as keys in the arguments structure, and I know of no good way to figure out the order
in which they were passed.
Finally, I haven't yet implemented this in cfrails, so I don't know how feasible basing parameterization on
the CF type is, but I'll try to remember to post it when
I do implement it. In any case, I'd be willing to bet there are other uses for this - it's just
that this is the most important one on my mind recently.
Update: It did occur to me as I was writing this post that you could accomplish the same thing by using an array as a
defined argument, with the added benefit of keeping your interface well defined. In all honesty, I would prefer to do it
that way if you could define an array inline like: [9, "blockhead", "charlie brown"] . But the way you define arrays in
Coldfusion really makes for too much code in what I'd like to accomplish with this, so in this case I'd provide a hint in the
function to let a developer know it expects you to pass those arguments. This way, I get cleaner syntax and a decent solution to the
interface problem.
Last modified on Jan 15, 2007 at 12:09 PM UTC - 6 hrs
Posted by Sam on Jan 09, 2007 at 04:44 PM UTC - 6 hrs
For those that don't know, cfrails is supposed to be a very light framework for obtaining MVC architecture with little to no effort (aside from putting custom methods where they belong). It works such that any changes to your database tables are reflected immediately throughout the application.
For instance, if you change the order of the columns, the order of those fields in the form is changed.
If you change the name of a column or it's data type, the labels for those form fields are changed, and
the validations for that column are also changed, along with the format in which it
is displayed (for example, a money field displays with the local currency,
datetime in the local format, and so forth).
I've also been developing a sort-of DSL for it,
so configuration can be performed quite easily programmatically (not just through the database), and you can follow DRY to the extreme. Further, some of this includes (and will include) custom data types (right now, there are only a couple of custom data types based on default data types).
More...
In a nutshell, the goal is to have such highly customizable "scaffolding" that there really is no scaffolding - all the code is synthesized - or generated "on the fly." Of course, this only gets you so far. For the stuff that really makes your application unique, you'll still have to code that. But you can compose views from others and such, so it's not like related tables have to stay unrelated, but I do want to stress that right now there is no relationship stuff implemented in the ORM.
I've skipped a few "mini-versions" from 0.1.3 to 0.2.0 because there were so many changes that I haven't documented one-by-one. That's just sloppiness on my part. Basically, I started by following Ruby on Rails' example, and taking my own experience about what I find myself doing over and over again. That part is done, except that the ORM needs to be able to auto-load and lazy-load relationships at the programmer's whim. In any case, once I got enough functionality to start using it on my project, I've been developing them in parallel. The problem is, I've fallen back on poor practices, so the code isn't as nice as it could be.
In particular, there aren't any new automated tests after the first couple of releases, which isn't as bad as it might otherwise be, since a lot of the core code was tested in them. But on that note, I haven't run the existing tests in a while, so they may be broken.
Further, since I've been thinking in Ruby and coding in Coldfusion, you'll see a mix of camelCase and under_score notations. My original goal was to provide both for all the public methods, and I still plan to do that (because, since I can't rely on case from all databases for the column names -- or so I think -- I use the under_score notation to tell where to put spaces when displaying the column names). But right now, there is a mix. Finally, the DSL needs a lot more thought put behind it - Right now it is a mix-and-match of variables.setting variables and set_something() methods. Right now it is really ugly, but when I take the time to get some updated documentation up and actually package it as a zip, I should have it cleaned up. In fact, I shouldn't have released this yet, but I was just starting to feel I needed to do something, since so much had be done on it and I hadn't put anything out in quite some time. Besides that, I'm quite excited to be using it - it's been a pain to build, but it's already saved me more time than had I not done anything like it.
In the end, I guess what I'm trying to say is: 1) Don't look at it to learn from. There may be
some good points, but there are bad points too, and 2) Don't rely too heavily on the interfaces.
While I don't anticipate changing them (only adding to them, and not forcing you to set
variables.property ), this is still less than version 1, so I reserve the right
to change the interfaces until then. =)
Other than that, I would love to hear any feedback if you happen to be using it, or need help because the documentation is out of date, or if you tried to use it but couldn't get it to work. You can contact me here. You can find
cfrails at http://cfrails.riaforge.org/ .
Last modified on Jan 09, 2007 at 04:45 PM UTC - 6 hrs
Posted by Sam on Jan 08, 2007 at 02:10 PM UTC - 6 hrs
InfoQ has an interview with Ryan "zenspider" Davis, a "hardcore Ruby hacker." The interview covers several topics, but those of interest to me and the stuff I've been working on lately with cfrails include DSLs and metaprogramming. It doesn't give too much of an in-depth treatment of these topics, as it's only an interview, but I found it interesting so I thought I'd link it.
Posted by Sam on Dec 08, 2006 at 08:47 AM UTC - 6 hrs
Two of the 3 projects I'm working on right now will be built using cfrails for at least some of the development. The third has such a strange architecture that I've never worked with before as to make it hard to see how I might integrate cfrails with it (it's already in version 1, and we've picked it up for version 2 and 3). With the other two, I've already integrated it, and it's quite simple to do.
Therefore, I thought I'd provide some insight in case someone else needed to do it. Both applications are already well established, but we're basically building new subapplications for them, and I've already integrated it for use with single-table manager mini-apps.
Basically, instead of using the provided index.cfm, which routes all the requests, we've already got index.cfms which are used. Now, two options are available here: either use the cfrails index.cfm, and "include" the old one where it says to include the default page, or just change the name of the cfrails index.cfm entirely.
To avoid any potential trouble, I just changed the name. So, now I have cfrails.cfm, where any links that point to actions that cfrails takes care of should go to. So, instead of having index.cfm/controller/action, you'll have cfrails.cfm/controller/action.
More...
Since these applications are already well established, I didn't bother creating any layouts - so the page titles aren't exactly representative of each page, but it helped in that I could just wrap cfrails with the current template, and I was done.
Except, for one last issue: clicking on links back to the "old" part of the application meant that cfrails was looking for actions called "thispage.cfm," and didn't know how to handle it. So, in the newly (re)named cfrails.cfm, I just needed a simple check (after the action was extracted from the URL):
<cfif findnocase(".cfm" , action)>
<cflocation url="../../#action#" >
</cfif>
It's not something anyone couldn't have figured out, for sure. But, I was just amazed at how easy it was, because this was never a design consideration. I was building it for use on a completely fresh application.
Last modified on Dec 08, 2006 at 08:52 AM UTC - 6 hrs
Posted by Sam on Dec 08, 2006 at 08:20 AM UTC - 6 hrs
After my first couple of trial runs, I was a bit disappointed with the performance of cfrails. It was taking several seconds for simple pages to load, which is completely unacceptable. Of course with everything dynamically synthesized for you, you expect a performance hit, because CF isn't all that fast itself, but several seconds is way too long to wait.
After the disappointment, I tried running it on our production box, and while you could still notice it was a bit slow, it wasn't unacceptable. But, if possible, I want to improve the performance (and I have quite a few ideas on how to acheive that).
So, that brings me to the good news: After about a 3-4 month hiatus on doing anything in Coldfusion (except for the occassional maintenance, for not more than an hour at a time), I'm finally working in a few CF projects again (3, to be exact). Working in CF again cascades the good news to cfrails - our development machine sucks.
More...
Now, I've known this for quite some time - it's still the same box we were running CF 4.5 on. I think it may have had a couple of upgrades since then, but it's certainly not more than 528 MB RAM with a 1 GHz processor. In any case, I was doing some work this morning in a non-cfrails utilizing project, and it is equally slow (whereas a few months ago, when I was developing heavily on it, it was "normal"). That means it's likely cfrails performance isn't as bad as I thought it was, though I won't stick my neck out and say it performs well.
The further good news is that since I'm working in CF again, I'm wanting to use cfrails for the new work - and that means I'll be adding features to it a lot more often than I was in the last 3 months. Which means that sooner rather than later, it will become useful for someone besides me!
Anyway, we've got a new development box that's been in the works for a little while, with all the latest stuff. And since the current one is forcing me to wait longer for pages to load than the time I spend coding (literally), I'm taking a break from coding until we get it up - which will hopefully be soon.
Finally, if you're one of the few people who have downloaded and used cfrails, I'd like to hear from you if you've had performance issues. So let me know through the contact form until I get some comments up and running.
Last modified on Dec 08, 2006 at 08:32 AM UTC - 6 hrs
Posted by Sam on Nov 22, 2006 at 07:53 AM UTC - 6 hrs
A common problem I've been having and seeing lately is dealing with components that should read files in locations they don't know about. For instance, you have component Model in cfrails, which should read a configuration file in the project that is using it. I didn't want to have to create a mapping for every project, and I didn't want to have to figure out the ../../../../etc between the two directories to pass the value in to Model. So, here's a function which takes care of the mapping for you. If you needed, it would be trivially easy to add a new parameter for base template, if you didn't want to use the actual base template as your basis for inclusion.
It doesn't have any associated automated tests, mainly because I can't figure out how I'd test something like this. So, I tested it manually in 3 different directory structures, and it works fine for me. If you ever find a need for this, and encounter problems, I'd like to know about it - especially the directory structures your using so I can replicate and fix it.
So, without further ado, here's the code:
More...
<cffunction name="includeRelativeToBaseTemplate" description="Given the current template's path, includes a file relative to the base template path" output="true" access="private" >
<!---
why ?
Because sometimes, even though the "current template path" is known from getCurrentTemplatePath,
cfinclude appears to use it's _actual_ current template. For example, you have
A.cfc in dir1, and B.cfc, which extends A.cfc, but it resides in another directory. But, you
want to cfinclude a file within A.cfc relative to the file that instantiated B.cfc. Well, I didn't
find it very simple, so I created this function. =)
--->
<cfargument name="currentTemplate" hint="The template which should be relative to." >
<cfargument name="relativePath" hint="The relative path to the include, from the base template." >
<cfset var local=structNew()>
<cfset local.baseTemplate = getBaseTemplatePath()>
<cfset local.curTemplate = arguments.currentTemplate>
<cfset local.relative=arguments.relativePath>
<!--- in case of unix, convert slashes --->
<cfset local.baseTemplate = replace(local.baseTemplate,"\" ,"/" ,"all" )>
<cfset local.curTemplate = replace(local.curTemplate,"\" ,"/" ,"all" )>
<cfloop from="1" to="#max(listlen(local.baseTemplate,'/'),listlen(local.curTemplate,'/'))#" index="local.i" >
<cfset local.baseDir=listGetAt(local.baseTemplate,local.i,"/" )>
<cfset local.curDir=listGetAt(local.curTemplate,local.i,"/" )>
<cfif local.baseDir neq local.curDir>
<cfbreak>
</cfif>
</cfloop>
<cfset local.goBackTimes=listlen(local.curTemplate,'/')-local.i>
<cfset local.dotDotSlash = repeatString("../" , local.goBackTimes)>
<cfset local.newPath=listDeleteAt(local.baseTemplate,listLen(local.baseTemplate,"/" ),"/" )>
<cfloop from="1" to="#local.i-1#" index="local.k" >
<cfset local.newPath = listDeleteAt(local.newPath,1 ,"/" )>
</cfloop>
<cfif listlen(local.baseTemplate,'/') lt listlen(local.curTemplate,'/')>
<cfset local.dotDotSlashesInRelative = listvaluecount(local.relative,".." ,"/" )>
<cfloop from="1" to="#local.dotDotSlashesInRelative#" index="local.k" >
<cfset local.newPath = listDeleteAt(local.newPath,listLen(local.newPath,"/" ),"/" )>
</cfloop>
<cfset local.relative = replace(local.relative,"../" ,"" ,"all" )>
</cfif>
<cfset local.newPath = local.dotDotSlash & local.newPath & "/" & local.relative>
<!---if fileExists(expandPath(local.newPath)) - cf using different relative path than current? --->
<cftry>
<cfinclude template="#local.newPath#" >
<cfset result = true>
<cfcatch>
<cfset result = false>
</cfcatch>
</cftry>
<cfreturn result>
</cffunction>
Last modified on Nov 22, 2006 at 07:59 AM UTC - 6 hrs
Posted by Sam on Nov 04, 2006 at 07:29 PM UTC - 6 hrs
So, the last couple of weeks I've been hard at work with school stuff, and also we've started a new (well, massively adding on to an existing one) project at work (and now another new one, as of last Wednesday). Because I seem to be so incredibly busy, and the projects need to be done "yesterday" (don't they all?), I built myself a little helper application that should increase my velocity by a ton - no more repetitive busy-work (well, it is greatly reduced anyway).
I've quite unimaginatively called it cfrails, since it was inspired by Ruby on Rails, and you can find it's project page at RIA Forge.
But first, you might want to read Getting Started with cfrails, so you can see how easily 0 lines of real code can create an interface to your database (the only lines are a couple of setup things, and cfcomponent creation).
I'd like to know what you think, so please leave a comment.
Last modified on Nov 04, 2006 at 07:30 PM UTC - 6 hrs
|
Me
|