Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at cf.Objective() 2009 (Minneapolis, MN) with: Jason Dean

Learning ColdFusion 9: ORM Inheritance Mapping

By Ben Nadel on
Tags: ColdFusion

Yesterday, in the comments to my blog post on using EntityNew() vs. the NEW operator, Robert Rawlins asked me about persisting inherited objects with ColdFusion 9's new Object-Relational Mapping (ORM) functionality. This is something that I've looked at, but never really tried; as such, I figured there was no time like the present. When it comes to dealing with inheritance-based objects, there are three methods available, but to me, only two of them seem relevant; these are the two methods in which the sub-classes are stored in their own tables. Between these two methods of persistence, the only difference is that one uses a discriminator (type-differentiator in the base table) and one does not.

While using a discriminator value seems like a very natural thing from a database point of view, I really want to start thinking in terms of objects, not databases. As such, I think we should first explore the inheritance method that does not have a discriminator. For the following demos, I am going to use the domain of a rental store, such as Blockbuster, that can rent both DVDs and Games. And since both DVDs and Games can be rented, in this domain, each item can been seen as a special sub-class of rental:

 
 
 
 
 
 
ColdFusion 9 ORM Inheritance Functionality: UML Diagram. 
 
 
 

In the above UML(ish) diagram, the GameRental and the DVDRental domain objects both extend the core Rental object. Each of the sub-classes contains a foreign key reference to the primary key (ID) of the Rental record as well as its own, specialized information. Keeping this design in mind, let's take a look at our ColdFusion components. First, we will look at the base class, Rental.cfc:

Rental.cfc

  • <!---
  • This object is ORM enabled (hence the persistence attribute).
  • This object is meant to be sub-classed by other types of
  • rental objects.
  • --->
  • <cfcomponent
  • output="false"
  • hint="I am the core rental object object. Other sub-classes will extend me to define specific types of rentals."
  • persistent="true"
  • table="rental">
  •  
  • <!--- Define the properties. --->
  •  
  • <cfproperty
  • name="id"
  • type="numeric"
  • setter="false"
  • hint="I am the unique ID of this rental item at the persistence layer."
  •  
  • fieldtype="id"
  • ormtype="integer"
  • generator="identity"
  • length="10"
  • notnull="true"
  • />
  •  
  • <cfproperty
  • name="sku"
  • type="string"
  • validate="string"
  • validateparams="{ minlength=1, maxlength=30 }"
  • hint="I am the internal inventory number for this product."
  •  
  • fieldtype="column"
  • ormtype="string"
  • length="30"
  • notnull="true"
  • />
  •  
  • <cfproperty
  • name="name"
  • type="string"
  • validate="string"
  • validateparams="{ minlength=1, maxlength=50 }"
  •  
  • fieldtype="column"
  • ormtype="string"
  • length="50"
  • notnull="true"
  • />
  •  
  • </cfcomponent>

The Rental.cfc ColdFusion component contains the core rental information, but does not have any information about its sub-classes. As far as the ORM system is concerned, the data contains in this class will be persisted in the "rental" table.

Now, let's take a look at the sub-classes. First, we'll look at the GameRental.cfc:

GameRental.cfc (Extends Rental.cfc)

  • <!---
  • The GameRental.cfc object is a specialized form or
  • Rental. Persistence for this object is turned on.
  • --->
  • <cfcomponent
  • extends="Rental"
  • output="false"
  • hint="I am a game rental - a specialized form of Rental object."
  • persistent="true"
  • table="game_rental"
  • joincolumn="rental_id">
  •  
  • <!--- Define the properties. --->
  •  
  • <cfproperty
  • name="gameSystem"
  • type="string"
  • validate="string"
  • validateparams="{ minlength=1, maxlength=50 }"
  • hint="I am the game system for which this game works."
  •  
  • fieldtype="column"
  • column="game_system"
  • ormtype="string"
  • length="50"
  • notnull="true"
  • />
  •  
  • <cfproperty
  • name="rating"
  • type="string"
  • validate="regex"
  • validateparams="{ pattern=(?i)(ec|e|e10|t|m|ao|rp) }"
  • hint="I am the content rating of this game."
  •  
  • fieldtype="column"
  • ormtype="string"
  • length="5"
  • notnull="true"
  • />
  •  
  • </cfcomponent>

As you can see here, just as with any inheritance-based object, the GameRental.cfc class extends the base Rental.cfc class. Because this is being used by the ORM system, however, we have to provide a bit of additional information. The Table attribute tells the ORM system where to persist the data. The JoinColumn attribute tells the ORM system how to relate the sub-class table record back to the base class table record. So, going by the code above, our "game_rental" table will have a "rental_id" column which is a foreign key that references the primary key column, "id," of the "rental" table.

The DVDRental.cfc object works in exactly the same way as the GameRental.cfc:

DVDRental.cfc (Extends Rental.cfc)

  • <!---
  • The DVDRental.cfc object is a specialized form or
  • Rental. Persistence for this object is turned on.
  • --->
  • <cfcomponent
  • extends="Rental"
  • output="false"
  • hint="I am a DVD rental - a specialized form of Rental object."
  • persistent="true"
  • table="dvd_rental"
  • joincolumn="rental_id">
  •  
  • <!--- Define the properties. --->
  •  
  • <cfproperty
  • name="type"
  • type="string"
  • validate="string"
  • validateparams="{ minlength=1, maxlength=20 }"
  • hint="I am the type of DVD."
  •  
  • fieldtype="column"
  • ormtype="string"
  • length="20"
  • notnull="true"
  • />
  •  
  • <cfproperty
  • name="rating"
  • type="string"
  • validate="regex"
  • validateparams="{ pattern=(?i)(g|pg(-13)?|r|nc17) }"
  • hint="I am the movie rating."
  •  
  • fieldtype="column"
  • ormtype="string"
  • length="5"
  • notnull="true"
  • />
  •  
  • </cfcomponent>

Again, this sub-class acts just like the previous one, extending the Rental.cfc class using a foreign key column.

When the ColdFusion ORM system rebuilds the database, using these domain models, we get the following database tables (this was done on a Derby Embedded database):

rental

  • id (integer 10) required
  • sku (varchar 30) required
  • name (varchar 50) required

game_rental

  • rental_id (integer 10) required
  • game_system (varchar 50) required
  • rating (varchar 5) required

dvd_rental

  • rental_id (integer 10) required
  • type (varchar 20) required
  • rating (varchar 5) required

As you can see, each sub-class database table contains a foreign key (rental_id) that will be used to associate it back to the base table (rental).

Now that we have our domain model mapped out and our database built, let's finally put it to some use. In this demo, we will create and persist one instance of each sub-class:

  • <!--- Load a new game rental sub-type. --->
  • <cfset gameRental = entityNew( "GameRental" ) />
  •  
  • <!--- Set the game rental properties. --->
  • <cfset gameRental.setSKU( "G-SF201" ) />
  • <cfset gameRental.setName( "Street Fighter" ) />
  • <cfset gameRental.setGameSystem( "Sega Saturn" ) />
  • <cfset gameRental.setRating( "E" ) />
  •  
  • <!--- Save the game. --->
  • <cfset entitySave( gameRental ) />
  •  
  • <!--- Output the persisted game rental object. --->
  • <cfdump
  • var="#gameRental#"
  • label="Game Rental"
  • />
  •  
  •  
  • <br />
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <br />
  •  
  •  
  • <!--- Load a new DVD rental sub-type. --->
  • <cfset dvdRental = entityNew( "DVDRental" ) />
  •  
  • <!--- Set the DVD rental properties. --->
  • <cfset dvdRental.setSKU( "DVD-WHMS01" ) />
  • <cfset dvdRental.setName( "When Harry Met Sally" ) />
  • <cfset dvdRental.setType( "HD" ) />
  • <cfset dvdRental.setRating( "PG-13" ) />
  •  
  • <!--- Save the DVD. --->
  • <cfset entitySave( dvdRental ) />
  •  
  • <!--- Output the persisted DVD rental object. --->
  • <cfdump
  • var="#dvdRental#"
  • label="DVD Rental"
  • />
  •  
  •  
  • <br />
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <br />
  •  
  •  
  • <!--- Load all of the rental records. --->
  • <cfset rentals = entityLoad( "Rental" ) />
  •  
  • <!--- Output all of the rentals. --->
  • <cfdump
  • var="#rentals#"
  • label="All Rentals"
  • />
  •  
  •  
  • <br />
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <br />
  •  
  •  
  • <!--- Load all of the game rental records. --->
  • <cfset gameRentals = entityLoad( "GameRental" ) />
  •  
  • <!--- Output all of the game rentals. --->
  • <cfdump
  • var="#gameRentals#"
  • label="All Game Rentals"
  • />
  •  
  •  
  • <br />
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <br />
  •  
  •  
  • <!--- Load all of the dvd rental records. --->
  • <cfset dvdRentals = entityLoad( "DVDRental" ) />
  •  
  • <!--- Output all of the dvd rentals. --->
  • <cfdump
  • var="#dvdRentals#"
  • label="All DVD Rentals"
  • />

Notice that when we create the sub-classes, we are setting properties that were defined in both the sub-class as well as the base class. When we run this code, we get the following output:

 
 
 
 
 
 
ColdFusion 9 ORM Inheritance Functionality: Working Without A Discriminator. 
 
 
 

When we create the sub-classes and use them, there's nothing that cool going on - they act like normal objects. But, look at what we get when we start loading objects based on entity names. Notice that we can load all objects using the base class entity name or, we can load collections of specific sub-classes uses the sub-class entity name. Even cooler than that, though, when we load objects based on the base class entity name, ColdFusion's ORM system knows how to load the sub-classes even though the base class does not contain any information about the sub-classes! Sweet-ass-sweet!

So that's inheritance without a discriminator. Now, let's look at using a discriminator. If you look at the database tables created above, you'll notice that the base class table, "rental," does not lend any insight into which sub-classes might be extending it. With a discriminator, we can add a "Type" column to the base class table that will tell us which sub-class (if any) is extending the record. In order to do this, all we have to do is add a DiscriminatorColumn attribute to our base class and a DiscriminatorValue attribute to our sub-classes.

Rental.cfc

  • <!---
  • This object is ORM enabled (hence the persistence attribute).
  • This object is meant to be sub-classed by other types of
  • rental objects.
  • --->
  • <cfcomponent
  • output="false"
  • hint="I am the core rental object object. Other sub-classes will extend me to define specific types of rentals."
  • persistent="true"
  • table="rental"
  • discriminatorcolumn="rental_type">
  •  
  • <!--- .... same as before .... --->
  •  
  • </cfcomponent>

Notice now that our Rental.cfc CFComponent tag has a DiscriminatorColumn attribute with value, "rental_type." This will tell ColdFusion 9's ORM system to add the column, "rental_type" to the base table, "rental." And, in fact, when we have the ORM system rebuild our database, we get the following rental table:

rental

  • id (integer 10) required
  • rental_type (varchar 255) required
  • sku (varcahr 30) required
  • name (varchar 50) required

That new "rental_type" column gets populated based on the type of sub-class that extends the Rental.cfc object. And, the value that gets put in that column is defined by the DiscriminatorValue attribute in the sub-class CFComponent tags:

GameRental.cfc (Extends Rental.cfc)

  • <!---
  • The GameRental.cfc object is a specialized form or
  • Rental. Persistence for this object is turned on.
  • --->
  • <cfcomponent
  • extends="Rental"
  • output="false"
  • hint="I am a game rental - a specialized form of Rental object."
  • persistent="true"
  • table="game_rental"
  • joincolumn="rental_id"
  • discriminatorvalue="game">
  •  
  • <!--- .... same as before .... --->
  •  
  • </cfcomponent>

Here, our CFComponent tag has the DiscriminatorValue, "game."

DVDRental.cfc (Extends Rental.cfc)

  • <!---
  • The DVDRental.cfc object is a specialized form or
  • Rental. Persistence for this object is turned on.
  • --->
  • <cfcomponent
  • extends="Rental"
  • output="false"
  • hint="I am a DVD rental - a specialized form of Rental object."
  • persistent="true"
  • table="dvd_rental"
  • joincolumn="rental_id"
  • discriminatorvalue="dvd">
  •  
  • <!--- .... same as before .... --->
  •  
  • </cfcomponent>

Here, our CFComponent tag has the DiscriminatorValue, "dvd."

Now that we have our discriminator column and values in place, let's see how they can be used. Assuming that our previous demo has already run and populated the database, we are now going to re-query the ORM system for all of our rentals:

  • <!--- Load all of the rental records. --->
  • <cfset rentals = entityLoad( "Rental" ) />
  •  
  • <!--- Output all of the rentals. --->
  • <cfdump
  • var="#rentals#"
  • label="All Rentals"
  • />

When we run this code, we get the following output:

 
 
 
 
 
 
ColdFusion 9 ORM Inheritance Functionality: Working With A DiscriminatorColumn And DiscriminatorValue. 
 
 
 

What you might notice is that this output looks exactly like the all-rentals output we got in our first demo. All of the discriminator stuff that we just added is transparent to us from an object-based view point. But, when we look at the database, you will see that our DiscriminatorColumn, "rental_type," was properly populated:

 
 
 
 
 
 
ColdFusion 9 ORM Inheritance Functionality: Database Generated / Populated When Using A DiscriminatorColumn And DiscriminatorValue. 
 
 
 

At this point, you might be asking yourself what the point of the discriminator is? After all, if we can load objects based on sub-class entity names and the discriminator type is transparent to us when using the base class entity name... well, what purpose does it serve? To be honest, I don't know enough to fully answer that question. What I can tell you is that just because we are using the ORM system in this set of examples doesn't mean that we are tied to the ORM system or all data retrieval. We could easily run a standard CFQuery tag and use the discriminator column to gather information:

  • <!---
  • Query for game rentals, but don't bother joining to the
  • sub-class tables since we don't need any of that information
  • at this time.
  • --->
  • <cfquery name="rentals">
  • SELECT
  • id,
  • sku,
  • name
  • FROM
  • rental
  • WHERE
  • rental_type = 'game'
  • ORDER BY
  • name asc
  • </cfquery>
  •  
  • <!--- Output our rental name (we only have one record). --->
  • <cfoutput>
  • Records: #rentals.recordCount#<br />
  • Name: #rentals.name#<br />
  • </cfoutput>

Here, we are using the DiscriminatorColumn to optimize our raw SQL statement. And, when we run this, we get the following CFOutput:

Records: 1
Name: Street Fighter

So, while it seems that the discriminator value is not readily available to us when dealing with ORM-enabled objects, it is still a good value to have in place when we need to go in and write some highly optimized SQL.

Using inheritance in ColdFusion components is a very natural and powerful thing to do; and, seeing that ColdFusion 9's new ORM functionality can map that to proper database persistence is very exciting. I am curious to know how it writes the queries to return all the base records since it would have to do a number of joins, perhaps UNION ALL'd together? I don't know enough about logging to figure that out (can someone lend some easy insight?). But, I will just assume that it is doing it very efficiently. Anyway, this is cool stuff.

One final note regarding this inheritance stuff is that the base record does not have to be sub-classed at all. Even in this context, you could create a new base entity, "Rental," populate it, and save it. If you do so, the "rental_type" discriminator column is populated with the base class entity name, "Rental," and the loaded object will not have any sub-class properties - only the core, base class properties.




Reader Comments

@John,

Thank my man. I saw Rupesh's article, but I am not sure how to use the console. Don't I have to boot up CF from the console in order to see the output there? I will re-read his article.

Excellent stuff Ben!

Thanks a great deal for taking the time to look at this, isn't it a simple implementation!?!? All of this ORM stuff really couldn't be any simpler to implement, really VERY exciting stuff.

Your article doesn't really leave me with any unanswered questions, however, I do wonder if this works deeper than just a single level, so imagine taking your example further, whereby your dvd rentals have different types 'DVD', 'HD-DVD' and 'Blueray' could you create objects for each of those, creating a final comprised object 3 levels deep opposed to the two that you currently have? just something which crossed my mind.

HD-DVDRental -> DVDRental -> Rental

I know that's a horribly poor use case example but just wanted to explore going deeper than the single level of inheritance.

Thanks again mate, really do appreciate your thorough explanation.

Rob

@Robert,

I assume that would work. If they can extend 1 level deep, I assume they can extend N levels deep. But, that's just a guess. I am sure the SQL would get much more complicated!

@John,

That would be awesome my man!

Ben, the main reason for single-table inheritance being the generally preferred option is performance. By using a single table to hold all of the subtypes, you remove a lot of joins that have to be added when using multiple tables. When you start getting into larger sets of objects or deeper inheritance, the joins will start affecting the speed of the query.

Technically, the single-table option isn't the "best" from a database normalization point of view, since you will end up with null columns for some of your objects (for the properties that don't apply to that subtype). When disk space and database size were bigger issues, this concern was valid. Now that gigantic hard drives are available for pennies per gigabyte, the benefit of faster queries outweighs any obsession with rigid normalization rules.

So, you can do it either way. But most people go the single-table route for the speed.

@Brian,

That makes a lot of sense. And, I think I've seen things like that in databases that were more designed for reporting. But, as you are saying - in times where harddrive space is very cheap, it is becoming less and less of a concern.

One of the concerns I had about the distributed table inheritance is that it has to a bunch of joins. When getting a particular type, using one INNER JOIN is probably quite efficient; but, what about when it gets all the base records and has to join to all sub-class tables. That is the kind of thing that I "hope" is done efficiently, but would be nervous about running those queries too often.

Well, it's done as "effectively" as one can do joins on indexed foreign keys. Which is pretty efficiently, but it will never be as fast as not having to do the joins at all. ;-)

@Brian,

Right, but my curiosity is in that when you have several sub-classes, I wonder how it rounds up all the tables. In my mind I see something like this:

SELECT * FROM base b INNER JOIN subA sa
ON b.id = sa.base_id

UNION ALL

SELECT * FROM base b INNER JOIN subB sb
ON b.id = sb.base_id

UNION ALL

SELECT * FROM base b INNER JOIN subB sc
ON b.id = sc.base_id

One day, I'll figure out how to turn on my logging and actually look at the SQL that Hibernate is generating.

@Ben,

The discriminator column is available in case you are:

Working with a "legacy" database (that is, a database not designed from the standpoint of Hibernate's conventions).

Working with a "standard" database (that is, a database designed from the standpoint of Hibernate's conventions) but are extra-paranoid about data integrity and wish to impose extra elements into the schema that will help enforce data integrity from misbehaved applications and from unwary SQL-happy programmers.

If your database is being designed for a Hibernate-based project and will only be used by that Hibernate-based project, then Hibernate is perfectly happy not to use the discriminator column.

Justice

@Justice,

Ah gotcha. I also like the idea of being able to gather optimized queries that do not require a join if all you need to know is the type of JOIN that would be created.

@Brian,

Even though hard drive space is cheap and querying a single table is faster, when using a single table for sub-classes you have to be careful of modification anomalies.

Wikipedia has a good explanation of modification anomalies at http://en.wikipedia.org/wiki/Database_normalization#Free_the_database_of_modification_anomalies

I am not saying don't denormalize your database, just make sure to take all aspects into consideration.

Also from my understanding database normalization didn't have to do with disk space; but rather to do with data integrity and countering modification anomalies, while lower disk costs were a positive by-product of it.

@Ben,

Awesome article, I rather like the dumping of all base objects giving the relevant sub-class information too.

Good things to keep in mind when manually managing denormalized tables, Andrew. But Hibernate makes it very difficult (virtually impossible) to encounter modification anomalies, so I wouldn't worry about that in a Hibernate-managed setup.

@Brian,

You may be correct in saying that Hibernate handles the modification anomalies, however saying not to worry about it may not be a good idea for every situation.

In a single entry point system, letting Coldfusion and Hibernate handle the database setup maybe a valid option. However if that system has the chance of growing (which a lot of projects do) into a system that has multiple entry-points (ie: other systems, merging of databases); putting a little fore-thought in and using some database normalization (if not at least third normal form) will save you future headaches and days/weeks of re-design and data-porting.

I have had to do this on multiple occasions to someone else's database (usually in Access) that has grown too large for their design, and it costs a lot more in time and money the second time through (especially considering systems also have to be modified to handle the new database design).

This is only advice based from my personal experience, so please feel free to ignore it if you feel it is not relevant to your situation.

Thanks a lot for posting this Ben. I haven't done much playing with the CF9 ORM stuff, but for some reason I was curious about how it handles persistence of subclassed objects. Thanks (again) for doing the testing for me!

Just an FYI, Rob Kolosky logged the SQL created from using inheritance mapping and this is what he got:

SELECT
. . . . rental0_.id as id44_,
. . . . rental0_.sku as sku44_,
. . . . rental0_.name as name44_,
. . . . rental0_1_.type as type45_,
. . . . rental0_1_.rating as rating45_,
. . . . rental0_2_.game_system as game2_46_,
. . . . rental0_2_.rating as rating46_,
. . . . rental0_.rentalType as rentalType44_
from
. . . . tblRental rental0_
left outer join
. . . . tblDVDRental rental0_1_
on
. . . . rental0_.id=rental0_1_.rentalID
left outer join
. . . . tblGameRental rental0_2_
on
. . . . rental0_.id=rental0_2_.rentalID

So, it looks like the underlying SQL does not use any kind of UNION ALL'ing. Since Hibernate is such a tried and true product, I have to assume that this it the optimal way to go and that it performs much better than several UNION ALL statements???

@Ben,

I don't think this approach is more optimal on a database level, however it is probably the easiest approach to code up while giving similar results.

By this I mean: when coding any type of UNION, you have to make sure the column structure of the multiple select statements are exactly the same. This approach means you have to compare structures and align them correctly (adding nulls for columns that are not available in a statement).

However, by doing OUTER JOINs you do not really need to compare columns, the RDBMS will just add nulls if the column does not relate to that table or its JOINs. The short comings of having your data spread-out over multiple columns instead of aligned up nicely, is easily side-stepped by expecting it and adding code to the framework to handle it.

@Andrew,

Yeah, I hear what you're saying. That makes sense. I guess, there's just something in my gut that feels odd about trying to join several different tables to the same row of the root data table. But, I guess I shouldn't even concentrate on that - rather, on the niceness that ORM abstracts this whole concept away from us.

@Ben,

I know what you mean by it not feeling right and I am rather against it myself (especially for intermediate to advanced systems, like enterprise level applications).

In saying that, I can understand the purpose of ORM to simplify the processes of less data intensive applications.

@Andrew,

The purpose of an ORM like Hibernate is to abstract away the procedural database from an object-oriented application, as well as take the place of a full-fledged object-oriented data-access layer.

An ORM like Hibernate is fully capable of supporting, and fully intended for, enterprise applications backed by a normalized OLTP database. Its purpose is not to simplify data access (that bit comes for free as a side-effect). Its purpose is to abstract away the procedural database from an object-oriented application, as well as take the place of a full-fledged object-oriented data-access layer developed in-house.

Justice

Hi Ben!

My first question to you Ben! It's a great moment to me! I'm an young belgian cf-developper. In my office, you are famous :)

I'm beginning to learn how to work Coldfusion ORM system.
At first sight, it look usefull but I think that the Coldfusion Documentation is a little poor.

My question is:
How to make advanced criteria with EntityLoad ?
Like "like", < <= > >= , "OR , XOR", etc...
One hour I'm googling and it seems that nobody have to do that...

(Note: Sorry for my english which is a bit poor ;)
(Note: Please continue to post so helpfull post! Thank you for all)

Recess, I just post my comment that I find my answer...

I was thinking foolishly that ORMExecuteQuery return a standard CFQuery object... But no, it returns a "ORM object".

Thank you for your help ;)

@Loic,

Thank you very much for the kind words! That really means a lot to me. My experience with ORM stuff is not too great. But, if you look at John Whish's blog, he talks briefly about the type of data that get returned from ORMExecuteQuery():

http://www.aliaspooryorik.com/blog/index.cfm/e/posts.details/post/entitytoquery-does-not-return-a-coldfusion-query-271

Apparently, there is a bug that will be fixed in the CF9.0.1 updater that will make the query returned here more compatible with the standard ColdFusion query object.

@Ben,

I meant, returned from EntityToQuery() (passing in the object array returned from ORMExecuteQuery()... a bit rusty on that stuff).

I seem to be having an issue with property inheritance. Can you think of a reason why an extended class like GameRental would not see it's base class'(Rental) properties or methods where it can act upon them. It loads the object, but I can't seem to get it to show me the info for the properties or the methods. If I open the CFDocs for the item I made it will show me the inherited properties and functions though.

@Henry,
After further investigation, the functions all in the extended object exist from the base object and work, you just can't see them when you do a dump of the objects. Anyone else seeing this? Anyone else have a good solution?

@Henry,

I believe this is a known bug in the caching of component meta data in CF9. I am not sure if the latest 9.0.1 release has fixed this; I think it may still be outstanding.

The good news is, the functions still work so your code shouldn't break. It just makes debugging by use of CFDump harder and more unsettling.

Would there be a way to entityLoad an array of rental objects which have been saved as 'rental', but not as 'game rental' or 'dvd rental'?

Cheers

ORM nub . . .sorry.

Assert you're building a rental store. The store cfc has a prop called rentals.(o2m) In the rental example above, in the store obj, what would the cfc= prop be - rentals?

<cfproperty name="rentals" cascade="save-update" type="array" fieldtype="one-to-many" cfc="model.reviews" fkcolumn="businessID" >

Since the rental could be a game or dvd? or would the way to do it is have a prop for games AND prop for dvds. If you wanted a list of all rentals just have a method that returned an array of getGames() and getDVDs?

@Trip,

Unfortunately, I hesitate to give you any solid advice on this as my real-world experience with ORM is fairly non-existent. I will have to defer to any of the other experts that have left comments.

@Trip,

I'm not totally sure I understand the question but, if you wanted to get all the sub-types of Product (e.g. DVDs/Books) then you'd do:

entityLoad( "DVDRental" );

or;

ORMExecuteQuery( "from DVDRental" );

@John,

Thanks for the response Ben and John. Yea, asking questions about this stuff is difficult.

You have a store.cfc and one of it properties is rentals. Rentals could be either an DVD object or a Game object. I'm thinking the use of the discriminator would be used. But the rentals property would be an array of all rental objects.

I get the part I could do EntityLoad("rentals") and that would return an array of all rentals with "link" between rentals/dvd and the "link" between rentals/game as demonstrated in the last dump above.

The piece I'm confused about is when setting up the property in the store.cfc that "holds" the rentals. What would the cfc attribute of the property be

<cfproperty name="rentals" cascade="save-update" type="array" fieldtype="one-to-many" cfc="model.????" />

I think having to write this out I believe I may have figured out it should be model.rental.

So calling store.getRentals() would return them all. Does that sound reasonable?

I wish i had time to run that real quick to see if store.getRentals() would return rentals of all types with "links" intact. However i won't be able to do that until this evening.

Obviously I'm not building a rental store, just using the example. In reality I have a Business to advertisement relationship and there are 3 types of advertising. I want to call business.getAds() and get all types.

@Trip,

What you wrote sounds correct in theory. Using your Rental cfc would be what you need to set the property up correctly. The cfc is always the "table" you want to link to. It should work the way you want to, but as always, be careful loading all your items. I might be tempted to use HQL to load a subset of the items so you can page through them without setting them all up at the same time. Even the Hibernate is fast, you can get burned loading too many objects at a time.

@Henry,

Thanks for the response. Yes I ran across a great thread over on the google orm group that describes "watch outs" in maintaining associations properly so you're dropping/loading objects appropriately.

http://groups.google.com/group/cf-orm-dev/browse_thread/thread/a07ece5f7d509b3d

In the example they have hundreds of members per group. In my scenario there are 2-4 advertisements per business. With that said architecture of which side of the relationship maintains the relationship is confusing in reference to how child are loaded. Knowing there would be 2-4 child objects would HQL still be the best(most effective) means of pulling the businesses ads?

When I run:

<cfdump
var="#gameRentals#"
label="All Game Rentals"
/>

I only get the properties of the child and not of the parent also, is this expected?? Any ideas on how to have it display the all the properties?

Thanks!!

It is a bug James. I am pretty sure that it has been noted to Adobe.

@Trip sorry I didn't see your post. It sounds like you would be fine with 2 - 4 items. I think when you are hitting higher numbers it becomes more of an issue. Not to mention moving the DB naming side of things to just the objects is a really good solution.

@Henry

That works somewhat. I now see all the properties but if the 'child' property has any other many-to-many properties, they are discarded and not displayed. Seems like a band-aid to a deeper problem....

@Jame Yeah, all the solutions I have done where I have to serialize the objects, I write a method on the object that gets the data for serialization that way it is all what I need. So package the object up however you need and serialize it.

I have looked to see if the bug has been reported. I cannot find one. Does anyone know where I can find the reported bug so I can vote for it?

It's something that I really think needs to be fixed. I am having the same issue with and only seeing the properties of the child. Thanks for the workaround, works, but not ideal.

Thanks

@Henry,

Sorry for the delayed response. Thank you very much for the post. I'd love to see an article that concentrates on just the preserving of relationship and which side is most effective. I'm sure this has a dependency on what your doing but there has to be a basic rule of thumb. I've seen it referenced many places but but it's usually just a sentence. And once throw inverse into to the mix, it seems the correct/appropriate inverse placement and value is crucial from performance perspective.

Thanks to everyone on this thread!!! Huge help!!