-
Notifications
You must be signed in to change notification settings - Fork 758
ORM Cascading Proposal #41
Comments
If the field type is struct or pointer to struct, it is has_one; If the field type is slice, map or pointer to slice or map, it is has _many. So I think the two tags could be merged to one tag. |
there is still difference for has_one and belongs_to, check Active Record pattern http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference, as it's the basis for my suggestion |
omitting 'has_many' seem a valid suggestion though |
I'd like to see some support for many-to-many relationships too. |
I want to use xorm cascade loading like below: type Country struct {
Id int64
Name string
}
type Group struct {
Id int64
Name string
}
type UserGroup struct {
UserId int64 `xorm:"index"`
GroupId int64 `xorm:"index"`
}
type UserFamily struct {
UserId int64 `xorm:"index"`
FamilyId int64 `xorm:"index"`
}
type Device struct {
Id int64
UserId int64 `xorm:"index"`
Name string
}
type User struct {
Id int64
Name string
Country Country `xorm:"cascade(country_id)"` // one to one
Father User `xorm:"cascade(father_id)"` //one to one
Devices []Device `xorm:"cascade(user_id)"` // one to many
Families []User `xorm:"cascade(user_family, user_id, family_id)"` // many to many
Groups []Group `xorm:"cascade(user_group, user_id, group_id)"` // many to many
} |
This sounds good. type Device struct { Wouldn't it make sense to reference back the User object instead of the UserId? |
Of course, both yours and the below are ok. type Device struct {
Id int64
User User `xorm:"index cascade(user_id)"`
Name string
} |
cascade tag wording is like customer mapper for the FK field, but it's missing the owning issue, the meaning of "belongs_to" is the owning rights so it also meaning if owner got deleted the type Order struct {
Customer Customer `xorm:"belongs_to"` // customer_id FK association, and customer_id as default mapper
}
type Customer struct {
Order []Order // has many required default lazy loaded, need an API for lazy loading
} meaning that a customer got deleted all its orders will be deleted too. |
has_one is also required as there are situation where FK association is placed on the owner in most one-to-one situation type Nose struct {
Face Face // default face_id mapper
}
type Face struct {
Nose Nose `xorm:"has_one"`// FK lookup from inverse table (Nose)
} and still, delete face will also delete nose |
has_one is very useful indeed. Shouldn't we be able to specify if the CASCADE should delete? I'm coming from python to quite used to sqlalchemy and think they do it quite well. http://docs.sqlalchemy.org/en/rel_0_9/orm/session.html#cascades. Realise you may not be heading the same way, but worth just checking. |
@ahall I suppose Active Record by its infamous RoR framework is providing better example/pattern? |
@ahall just read sqlalchemy the cascade served the save/update/delete behaviour, which is exactly the same usage from using grails, and grails is following active record pattern plus cascade mapping behaviour to override default behaviour, from my experience I've only find that "delete-orphan" mostly used. |
Yep I'm only suggesting by default it wont cascade the deletes and you can use e.g. a cascade tag to specify if you want it to delete on cascade. sqlalchemy my default wont cascade the deletes unless you tell it to as you dont always want it to delete on cascade. |
This would be extra nice to have. Has anyone started working on it? |
I think currently no one is working on it. I think it should be discussed more. |
Basic ORM preconditions:
One-to-One: type Nose struct {
Id int64
TheFace *Face `xorm:"belongs_to(the_face_id)”` // optional, use belongs_to if you wanna mapping different column name
}
type Face struct {
Id int64
TheNose *Nose `xorm:”has_one(the_face_id)”`
}
// 'belongs_to' clause usage:
// belongs_to([mapping column name] [, <mapping table name>])
// 'has_one' clause usage:
// has_one([mapping column name] [, <mapping table name>]) create table face (id bigint generated by default as identity (start with 1),
primary key (id));
create table nose (id bigint generated by default as identity (start with 1),
the_face_id bigint not null,
primary key (id));
alter table nose add constraint fk_nose_the_face_id foreign key (the_face_id) references face (id); implementation notes:
face := &Face{ TheNose:Nose{} }
engine.Insert(&face) // insert both nose and face record with association made
One-to-Many: type Order struct {
Id int64
TheCustomer *Customer `xorm:"belongs_to(the_customer_id)”` // optional, use belongs_to if you wanna mapping different column name
}
type Customer struct {
Id int64
TheOrders []*Order `xorm:”has_many(the_customer_id)"`
}
// 'has_many' clause usage:
// has_many([mapping column name] [, <mapping table name>]) create table customer (id bigint generated by default as identity (start with 1),
primary key (id));
create table order (id bigint generated by default as identity (start with 1),
the_customer_id bigint not null,
primary key (id));
alter table order add constraint fk_order_the_customer_id foreign key (the_customer_id) reference customer (id); Many-to-Many: (yet supported) Using has_many fetching strategy: lazy: type Customer struct {
Id int64
TheOrders []*Order `xorm:”has_many(the_customer_id)”` // default will be lazy
}
// 'lazy' clause usage:
// lazy // default behavior works with declaring it, and with no limit max fetch size and no table ordering
// lazy(<max fetch size:int>[, <order by fields:string>]) eager_select: type Customer struct {
Id int64
TheOrders []*Order `xorm:”has_many(the_customer_id) eager_select”`
}
// 'eager_select' clause usage:
// eager_select // default with no limit max fetch size and no table ordering
// eager_select(<max fetch size:int>[,<order by fields:string>]) eager_join: type Customer struct {
Id int64
TheOrders []*Order `xorm:”has_many(the_customer_id) eager_join”`
}
// 'eager_join' clause usage:
// eager_join // default with no table ordering
// eager_join(<order by fields:string>) Adding and Removing associations: Only work on the inverse side of domain object for removing and adding associations: Removing association:
// retrieved var order1 Order
order1.TheCustomer = nil
engine.Update(&order1)
Adding association:
// retrieved var order1 Order
order1.TheCustomer = differentCustomer
engine.Update(&order1)
// insert new record
order := Order{ TheCustomer:customer1 }
engine.Insert(&order) |
Perfect work!!! And some rules could be considered:
|
I've updated has_many clause usage above:
Which is a conflicted design to above comments. extended to:
so you above example can be: type CustomerOrder struct {
CustomerId int64
OrderId int64
}
type Customer struct {
Id int64
TheOrders []*Order `xorm:”has_many(customer_id, customer_order, order_id)”`
}
type Order struct {
Id int64
TheCustomers []*Order `xorm:”has_many(order_id, customer_order, customer_id)”`
} And with above design, it also means that for many-to-many declaration, that all 3 params are needed. @lunny any thought on adding/removing many-to-many associations without adding new APIs? |
For Many to Many, lazy load need a method to manually load the data, we have missed multiple has_many issue:
consider following a domain has multiple has_many:
|
Sounds good to me. Shame there is no way of auto lazyload due to language restrictions. |
How to add or remove associations simply. It's currently diffculty. |
Proposal:
The text was updated successfully, but these errors were encountered: