After sleeping on it I came up with a decision on how to fix the delete up
issue.  

I've just committed an update which changes the behavior of this to a way
that seems much safer and is, actually, the way it was before (except for
iterators).

Let's say you have a user who hasOne address (user.addressId =
address.addressId).  

The following code will delete the user but not the address.

<cfset user.delete() />

The following code will cause a dbms-specific constraint error to be thrown.
You can't delete the user's address.
 
<cfset user.getAddress().delete() />

Here's how you'd delete the address instead, assuming the user's addressId
can be null.  If not, you'll need to assign a new address or delete the
user.

<cfset address = user.getAddress() />
<cfset user.removeAddress() />
<cfset user.save() />
<cfset address.delete() />

(Should removeAddress return the address that was removed?  Akin to a pop()
method?)

Changing the structure a bit, let's say your user hasMany addresses without
a linking table.  (User.userId = address.userId).  In this case it's quite
easy to delete addresses.

The following code will delete the first address out of the user's address
iterator:

<cfset user.getAddressIterator().delete(1) />

This will delete the address.  No need to save the user.  The getQuery and
getArray methods on the iterator reflect the changes.  

This is another way to do the exact same thing:

<cfset user.getAddressIterator().getAt(1).delete() />

This also causes the iterator's getQuery and getArray methods to reflect the
deletion.  Actually, the iterator simply notices that the item was deleted
next time you call those methods.

Changing the structure to a linking relationship now.  user.userId =
userAddress.userId and userAddress.addresId = address.addressId

Given a linking structure, the following code will not actually delete the
address.  It will, however, delete the link between the user and the
address. This is because the iterator is responsible for the delete. 

<cfset user.getAddressIterator().delete(1) />

If you wanted to delete the address from the user and delete the address the
following code would work:

<!--- get the address we want to unlink and delete --->
<cfset address = user.getAddressIterator().getAt(1) />
<!--- delete the link between the user and the address --->
<cfset user.getAddressIterator().delete(address) />
<!--- delete the address --->
<cfset address.delete() />

This code will cause a dbms specific constraint violation error to be
thrown:

<cfset user.getAddressIterator().getAt(1).delete() />

The error is thrown because the records don't "cascade" deletes at all, much
less up the chain.  To support the iterator there's a userAddress record
that makes reference to the address.  If you try to directly delete the
address a constraint exists and it can't be done.

I also added one new method to the iterators: isDirty().  This will tell you
if any records loaded into the iterator are dirty.

Now, on to a few other notes about recent updates:

Let's say you have a linking relationship between a user and an address with
only one address (to make this example work).  I gave this example
yesterday:

User.getAddressIterator().getAt(1) ==
User.getUserAddressIterator().getAt(1).getAddress()

The reason is that the linking address iterator actually looks into the
unlinked iterator to find instances of objects.  So, if you do the
following:

User.getUserAddressIterator().getAt(1).getAddress().setStreeet("1234 Foo")

Then User.getAddressIterator().getAt(1).getStreet() will equal "1234 Foo".

However, if you create a new instance of that same address via the reactor
factory (or in any other way), they will be different instances and you
might run into concurrency issues.  This is the same as it's always been,
really (accept for the instances being the same in the iterator and linked
iterator.

Also, let's say you add the following XML to your config:

<object name="User">
        <hasMany name="Address">
                <link name="UserAddress" />
        </hasMany>
</object>

<object name="UserAddress">
        <hasOne name="User">
                <relate from="userId" to="userId" />
        </hasOne>
        <hasOne name="Address">
                <relate from="addressId" to="addressId" />
        </hasOne>
</object>

Because the User has a link through the UserAddress it will implicitly add a
hasMany relationship (if not already defined) to the UserAddress.  So the
example above is equal to the following:

<object name="User">
        <hasMany name="Address">
                <link name="UserAddress" />
        </hasMany>
        <hasMany name=" UserAddress">
                <relate from="userId" to="userId" />
        </hasMany>
</object>

<object name="UserAddress">
        <hasOne name="User">
                <relate from="userId" to="userId" />
        </hasOne>
        <hasOne name="Address">
                <relate from="addressId" to="addressId" />
        </hasOne>
</object>

If the relationship already exists reactor won't create it.  

I think that's it for now.  I'm going to get the samples working and run
another commit later.  

FYI - Paul Kenny has been looking into validation.  More on that soon.

Doug 


-----Original Message-----
From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED] On Behalf
Of Doug Hughes
Sent: Tuesday, April 18, 2006 7:19 PM
To: [email protected]
Subject: RE: [Reactor For CF] New Commit committed

The more I think about this, the more obvious it is that it was half-witted
to delete up.  

Seriously! The concept that deleting an address would delete a user?  Ha!

Furthermore, if a user hasOne address (user.addresId = address.addressId)
then you can't guarantee that there won't be errors on delete anyhow.  Any
number of users could haveOne of the address you're deleting and the DB
server would throw an error.  So, if you can't delete something because of
constraints, you can't delete it.  Fine! 

I also don't think that deleting an address should reset any values on the
user.  In the grand scheme of things, I don't think it'd make a difference.
If you want to remove an address from a user call removeAddress() (which is
a new method, by the way!)

I think that I'm going to simply remove this "ability".

There is room for cascading on delete and making it optional on saves.  

Right now, with the delete method, if you pass in any list of name/value
pairs the corresponding record will be loaded and deleted.  I could add one
"reserved" argument which would be used to indicate if you wanted the delete
to cascade.  For example, the following would delete without a cascade:

User.delete()

Or

User.delete(userId=123)

The following would delete with a cascade:

User.delete(true)

Or 

User.delete(cascadeDelete=true, userId=123)

I'm thinking that cascadeDelete would only "work" when any other arguments
are passed in when it's the first argument and it's a Boolean.  For example:

User.delete(userId=123, cascadeDelete=true)

This would delete the record with a userID of 123 and a cascadeDelete of
true.

On saves this might be easier.  I don't recall for sure, but I don't think
save accepts arguments.  In this case the arguments are easier.  This code
would cause any loaded dirty children (anywhere down in the proverbial tree)
to save themselves:

User.save()

This would only save the current user but not any dirty children:

User.save(false)

Or 

User.save(cascadeSave=false)

Thoughts? 

Doug

-----Original Message-----
From: [EMAIL PROTECTED] [mailto:[EMAIL PROTECTED] On Behalf
Of Sean Corfield
Sent: Tuesday, April 18, 2006 7:04 PM
To: [email protected]
Subject: Re: [Reactor For CF] New Commit committed

On 4/18/06, Doug Hughes <[EMAIL PROTECTED]> wrote:
> I replaced the event model to facilitate cascading saves and basically
> everything that's new with the iterator.

Figured.

> About the user.getAddress().delete()... I suppose it's obvious I know it's
> dangerous if you look at the code.  There's a comment that reads as
follows:
>
> <!--- delete the parent.  (By running the next line of code you agree not
to
> sue Doug if something goes horribly wrong.) --->
>
> So... yea, dangerous. I do not intend for this to remain as such forever.

OK. Good to know. Right now it would be unusable in most applications,
I believe, since it would cascade *UP* which is definitely not good.
Cascading *down* is fine (as long as there is an option to *not* do
it).

> > // assume parent/foo/bar
> > foo = parent.getFoo();
> > bar = parent.getBar();
> > foo.delete();
> > // parent and foo are deleted, bar is orphaned
>
> Yup.   I'm not stuck on it.  What do you think this should do?

If foo really is a dependent object (i.e., parent requires foo and foo
requires parent), then I think it should be an error to delete foo (or
bar). Deleting parent should delete foo and bar (if casading deletes
is enabled).

I just don't think you should be able to delete from the bottom up.
--
Sean A Corfield -- http://corfield.org/
Got frameworks?

"If you're not annoying somebody, you're not really alive."
-- Margaret Atwood

 

-- Reactor for ColdFusion Mailing List -- [email protected]
-- Archives at http://www.mail-archive.com/reactor%40doughughes.net/



 

-- Reactor for ColdFusion Mailing List -- [email protected]
-- Archives at http://www.mail-archive.com/reactor%40doughughes.net/



 

-- Reactor for ColdFusion Mailing List -- [email protected]
-- Archives at http://www.mail-archive.com/reactor%40doughughes.net/


Reply via email to