Hi Elliot,
The relationship between SearchOptions and User is really onetoone but
obviously we can't model this in Transfer which is why I have to use
manytoone. In this scenario User will always be unique.
I didn't agree with this, not entirely anyway, as I have used one2one
on numerous occasions, but that was just my opinion. If anyone else
sees this as a valuable enhancement please speak up, and maybe it can
get added as an enhancement request.
Cheers,
Bob
--
Bob Silverberg
www.silverwareconsulting.com
one2one is considered an edge case because it's not particularly good
DB design... if you have a one2one join, you should consider putting
the data together in the same table. Since supporting questionable DB
design isn't within Transfer's charter, it's a question of Mark's
time being spent on supporting something that shouldn't really be
necessary in the first place or working on any of the bajillions of
other enhancements or bug fixes that are essential.
Meanwhile, by means of a one2many on the 1:1 tables, you can achieve
what's essentially the same behavior (or at least similar) insofar as
getters and setters are concerned. Since a one2one is the same as a
one2many with the one minor difference that the joining table will
only ever have one related row, there's no particular reason not to
go ahead and use it. If you want to enforce the 1:1 nature of the
tables, just create a decorator, override the generated setFoo()
method to indicate your preferred behavior (throw error? overwrite
original record? backup record then overwrite?).
I guess that brings up yet another point against this... what's the
desired behavior when calling Bar.setFoo(Foo) on a 1:1 relationship?
There's too many viable options in this case to have consistent and
acceptable results. So we're back to using a one2many and overriding
the generated methods in a decorator. If you have a one2one
relationship, this is not just your only option but quite possibly
the most elegant option available.
J
I have to disagree with this. One2one is a useful relationship when
attempting to implement supertypes and subtypes. Let's say I have a
User object, with properties like UserId, UserName and Password. I
also have different types of users. Administrators have a Department,
EmployeeCode, etc. Members have a JoinedDate, AffiliateCode, etc.
My belief is that optimal DB design would yield a User table, with the
common fields, an Adminstrator table, with Admin only fields, and a
Member table with member only fields. The PK of each table would be
UserId, thus creating a one2one relationship. The alternative that
you are suggesting would be to put all of the Admin only fields AND
all of the Member only fields into the User table. That would result
in multiple unused fields on each and every record in the table. It's
true that in today's world of cheap hardware having a denormalized
design like that isn't necesarily a bad thing, but I also think it's
not accurate to say that it's better design. I may be among the
minority but I still think that normalization is an admirable goal,
and although I might use a denormalized design like you are describing
to make things simpler, I would feel as if I were making a compromise
on design, not implementing a better design.
--
Bob Silverberg
www.silverwareconsulting.com
I have to disagree with this. One2one is a useful relationship when
attempting to implement supertypes and subtypes. Let's say I have a
User object, with properties like UserId, UserName and Password. I
also have different types of users. Administrators have a Department,
EmployeeCode, etc. Members have a JoinedDate, AffiliateCode, etc.
I think there's more to it than just the diversion of resources...
one2one is considered an edge case because it's not particularly good
DB design... if you have a one2one join, you should consider putting
the data together in the same table. Since supporting questionable DB
design isn't within Transfer's charter, it's a question of Mark's
time being spent on supporting something that shouldn't really be
necessary in the first place or working on any of the bajillions of
other enhancements or bug fixes that are essential.
Meanwhile, by means of a one2many on the 1:1 tables, you can achieve
what's essentially the same behavior (or at least similar) insofar as
getters and setters are concerned. Since a one2one is the same as a
one2many with the one minor difference that the joining table will
only ever have one related row, there's no particular reason not to
go ahead and use it.
If you want to enforce the 1:1 nature of the
tables, just create a decorator, override the generated setFoo()
method to indicate your preferred behavior (throw error? overwrite
original record? backup record then overwrite?).
I guess that brings up yet another point against this... what's the
desired behavior when calling Bar.setFoo(Foo) on a 1:1 relationship?
There's too many viable options in this case to have consistent and
acceptable results.
I am in no way against supporting 1:1 joins in Transfer... I'm just
stating what I was told when I wanted it and how I've worked around
it since then. Just wanted to clarify that. Also, I do think that
there is plenty of support for this sort of situation that supporting
them doesn't really need to be a priority for Mark (as far as I'm
concerned) until someone wants to cough up some ching for the
enhancement. If that happened I'm sure he'd be happy to add it.
Until then, I get the impression that demand for it is limited to a
few, albeit vocal, requesters. *g*
But wait! There's more! See inline below:
On Sep 2, 2008, at 11:16 AM, Brian Kotek wrote:
> I don't think it should be Transfer's job to determine what is or
> is not "questionable" database design. The whole point of an ORM is
> to automate the mapping an object model into relational data. As
> soon as we start making decisions based on database design, we're
> back to the old (and, unfortunately, valid) argument that most ORMs
> force your database structure onto your object model, instead of
> the other way around. What might be considered a "bad" relational
> model for a traditional OLTP database goes out the window pretty
> quickly when you start talking about ORMs. We aren't building an
> OLTP schema, we're building an object persistence schema, and the
> two can very easily end up in conflict.
Agreed... I wasn't saying it was Transfer's place to stand in
judgement of people's DB architecture decisions, but that it was
Mark's place to stand in judgement of what he's going to implement
based on the feedback he gets from the user base and the quality of
the product that results. OTOH, supporting getDatasource() was
accepted, and that's something that encountered a great deal of
discussion.
> It also results in an API that is unnecessarily difficult to use.
> Within Book, if I want to get the Product, I have to do something
> like:
>
> product = getProductArray()
> product = product[1]
>
> instead of simply getProduct(). Not only is it more work, but it is
> misleading code, as well as relying on the "magic number" 1, and
> magic numbers make my skin crawl. :-)
Even under the current circumstances you don't need to use magic
numbers. Something like this should even be generatable (I think I
maked up a new werd):
<cffunction name="getProduct" access="public"
returntype="model.product" output="false">
<cfset props = {bookId:getBookId()}>
<cfreturn getTransfer().readByPropertyMap("product",props) />
</cffunction>
<cffunction name="setProduct" access="public" returntype="void"
output="false">
<cfargument name="product" type="model.product" required="true" />
<cfset arguments.product.setBookId(getId)>
<cfset getTransfer.save(Product)>
</cffunction>
readByPropertyMap() even throws an error if more than one row is
found... which, I guess, is sort of half-way support for 1:1 support,
eh? ;)
>
>
> If you want to enforce the 1:1 nature of the
> tables, just create a decorator, override the generated setFoo()
> method to indicate your preferred behavior (throw error? overwrite
> original record? backup record then overwrite?).
>
> Since I generate a great deal of my code, having to manually write
> a Decorator to address this makes my life more difficult. The
> answer to many previous feature requests could have been "write a
> decorator method to handle it", but that doesn't mean I can't ask
> for an easier way. ;-)
Indeed not, and it would have been very very useful to me when I
first started using Transfer because of the way I designed my
databases back then.
>
>
>
> I guess that brings up yet another point against this... what's the
> desired behavior when calling Bar.setFoo(Foo) on a 1:1 relationship?
> There's too many viable options in this case to have consistent and
> acceptable results.
>
> I don't follow. If I have a Product and a Book, and I call
> book.setProduct(product), what exactly is the problem? How is this
> any different than the way one calls setParentFoo() or addFoo()?
My point was basically that if you were going to implement 1:1s,
given a record where the other side of the relationship already
exists when you call setFoo(Foo), do you overwrite the other record?
Do you update the other record? Is it up to the dev to say "if hasFoo
(), removeFoo(); setFoo()"? Is there just one way to set this up to
work? m2ms and o2ms don't have this issue to begin with... so I am
curious about it. And since I haven't really worried about using 1:1s
since I started using Transfer, I haven't had to mess with it at
all... so I really don't know and am asking how this would work.
> Obviously I've worked around this issue, but I would argue that it
> was enough of a pain (especially once you have multiple subtype/
> supertype relationships in your model) to warrant some
> consideration. Mark can (and I'm sure will hehe) jump in, but on
> the surface it seems that unless the inheritance mapping feature is
> coming in the forseeable future, something like this might be a
> valid interim solution.
Or set up your generator to recognize 1:1 relationships and generate
get/set methods based on the one I've posted above and you're in
business... no biggie. The Decorator API is one of the more
powerfuller things we've got at our disposal in Transfer and there's
almost nothing you can't do with it.
Laterz,
J
I suppose I should say this first of all:
I am in no way against supporting 1:1 joins in Transfer... I'm just
stating what I was told when I wanted it and how I've worked around
it since then. Just wanted to clarify that. Also, I do think that
there is plenty of support for this sort of situation that supporting
them doesn't really need to be a priority for Mark (as far as I'm
concerned) until someone wants to cough up some ching for the
enhancement. If that happened I'm sure he'd be happy to add it.
Until then, I get the impression that demand for it is limited to a
few, albeit vocal, requesters. *g*
Agreed... I wasn't saying it was Transfer's place to stand in
judgement of people's DB architecture decisions, but that it was
Mark's place to stand in judgement of what he's going to implement
based on the feedback he gets from the user base and the quality of
the product that results. OTOH, supporting getDatasource() was
accepted, and that's something that encountered a great deal of
discussion.
Even under the current circumstances you don't need to use magic
numbers. Something like this should even be generatable (I think I
maked up a new werd):
<cffunction name="getProduct" access="public"
returntype="model.product" output="false">
<cfset props = {bookId:getBookId()}>
<cfreturn getTransfer().readByPropertyMap("product",props) />
</cffunction>
<cffunction name="setProduct" access="public" returntype="void"
output="false">
<cfargument name="product" type="model.product" required="true" />
<cfset arguments.product.setBookId(getId)>
<cfset getTransfer.save(Product)>
</cffunction>
readByPropertyMap() even throws an error if more than one row is
found... which, I guess, is sort of half-way support for 1:1 support,
eh? ;)
My point was basically that if you were going to implement 1:1s,
given a record where the other side of the relationship already
exists when you call setFoo(Foo), do you overwrite the other record?
Do you update the other record? Is it up to the dev to say "if hasFoo
(), removeFoo(); setFoo()"? Is there just one way to set this up to
work? m2ms and o2ms don't have this issue to begin with... so I am
curious about it. And since I haven't really worried about using 1:1s
since I started using Transfer, I haven't had to mess with it at
all... so I really don't know and am asking how this would work.
Or set up your generator to recognize 1:1 relationships and generate
get/set methods based on the one I've posted above and you're in
business... no biggie.
The Decorator API is one of the more
powerfuller things we've got at our disposal in Transfer and there's
almost nothing you can't do with it.