Class hierarchies with relationships

14 views
Skip to first unread message

Matt Duncan

unread,
Aug 18, 2014, 4:50:41 PM8/18/14
to mapp...@googlegroups.com
I want to use the functionality from "store all in 1 table" https://code.google.com/p/mapperdao/wiki/ClassHierarchyMappings

but instead of mapping simple columns I want to map to a relationship, is this possible?

Something like this:

object InviteEntity extends Entity[Int, SurrogateIntId, InviteT]("invites") {
  val id
= key("id") autogenerated(_.id)
  val t
= column("type") to {
   
case _: TeamInvite => "TeamInvite"
   
case _: EventInvite => "EventInvite"
 
}
  val author
= {
   
case team: TeamInvite => manytoone(TeamEntity) to (_.Author)
   
case event: EventInvite => manytoone(EventEntity) to (_.Author)
 
}
...

where Author is defined by [T <% Inviteable] -- and Team/Event extend Inviteable.

I have a set of case classes(TeamInvite, etc.) where these types are concrete but in the database I really only need to store a reference(id) to the entity I want and some other generic info. I want to get the entity based on the type stored for InviteT and then construct the correct case class using matching. It's very similar to what you have the wiki just lacking relationship capability. Is it feasible? Thanks!

Konstantinos Kougios

unread,
Aug 18, 2014, 4:58:39 PM8/18/14
to mapp...@googlegroups.com
Hi, what a minimal version of your table(s) will look like?

btw, val author= {... } won't work. (Is that meant to be a manytoone(AuthorEntity)? Maybe I got it all wrong)

Cheers
--
You received this message because you are subscribed to the Google Groups "mapperdao" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mapperdao+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matt Duncan

unread,
Aug 19, 2014, 9:37:40 AM8/19/14
to mapp...@googlegroups.com
CREATE TABLE `invites` (
 
`id` int(11) NOT NULL,
 
`author` int(11) NOT NULL,
 
`receiver` int(11) DEFAULT NULL,
 
`message` text,
 
`createdOn` datetime NOT NULL,
 
`type` varchar(45) NOT NULL,
  PRIMARY KEY
(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE
`events` (
 
`id` int(11) NOT NULL AUTO_INCREMENT,
 
`eventType` varchar(15) NOT NULL,
 
`name` varchar(45) NOT NULL,
  PRIMARY KEY
(`id`),
  UNIQUE KEY
`id_UNIQUE` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE
`teams` (
 
`id` int(11) NOT NULL AUTO_INCREMENT,
 
`name` varchar(45) NOT NULL,
 
`createdDate` datetime NOT NULL,
  PRIMARY KEY
(`id`),
  UNIQUE KEY
`id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;


The 'author' column would hold the Id of an entity and based on the type I would map a relationship to the correct entity, then use the same matching process to construct the correct Invite case class in the constructor.

//continuation of InviteEntity  
def constructor(implicit m: ValuesMap) = m(t) match {
   
case "TeamInvite" => new TeamInvite(author, receiver, message, time) with Stored {
      val id
: Int = InviteEntity.id
   
}
   
case "EventInvite" => new EventInvite(author, receiver, message, time) with Stored {
      val id
: Int = InviteEntity.id
   
}
 
}

//In models.scala
case class TeamInvite(
                       
Author: Team,
                       
Receiver: User,
                       message
: String = null,
                       
Time: DateTime
                       
) extends InviteT[Team, User]


case class EventInvite(
                       
Author: Event,
                       
Receiver: User,
                       
//Mediator: User,
                        message
: String = null,
                       
Time: DateTime
                       
) extends InviteT[Event, User, User]

The receiver member would act the same...

Thanks for looking!

Matt Duncan

unread,
Aug 19, 2014, 9:39:03 AM8/19/14
to mapp...@googlegroups.com
Please ignore the extending class on EventInvite, it should only have two type parameters :X

Konstantinos Kougios

unread,
Aug 19, 2014, 4:30:40 PM8/19/14
to mapp...@googlegroups.com
Hi Matt,

    Unfortunately I am still unclear on what you try to do. In your initial email, it seems you try to map InviteT hierarchy to 1 table, but instead you got 3 tables (invites,events,teams).  Are events.id  and teams.id foreign keys to invites.id?

So assuming you want to map them into 1 table , a way to do this is having this table:

CREATE TABLE `invites` (
 
`id` int(11) NOT NULL,

 
`team_id` int(11) NOT NULL, -- foreign key to team table
  `event_id` int(11) NOT NULL, -- foreign key to event table
 
`receiver` int(11) DEFAULT NULL,

 
`message` text,
 
`createdOn` datetime NOT NULL,
 
`type` varchar(45) NOT NULL,

  `eventType` varchar(15) NOT NULL,  -- for EventInvite
  `name` varchar(45) NOT NULL,  -- for both EventInvite, TeamInvite

  `createdDate` datetime NOT NULL, -- for TeamInvite

PRIMARY KEY
(`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;



So now this holds all fields for the InviteT hierarchy and the mapping would be like:


object InviteEntity extends Entity[Int, SurrogateIntId, InviteT]("invites") {
  val id = key("id") autogenerated(_.id)
  val t = column("type") to {
    case _: TeamInvite => "TeamInvite"
    case _: EventInvite => "EventInvite"
  }
  val author = manytoone(AuthorEntity) to (_.Author)
  val event = manytoone(EventEntity) to (_.Author)
  val user = manytoone(UserEntity) to (_.User)
  val eventType = column("eventType") to {
        case e : EventInvite=> e.eventType
        case _ => "-"
}
... and so on


def constructor(implicit m: ValuesMap) = m(t) match {
    case "TeamInvite" => new TeamInvite(author, receiver, message, time) with Stored {
      val id: Int = InviteEntity.id
    }
    case "EventInvite" => new EventInvite(author, receiver, message, time) with Stored {
      val id: Int = InviteEntity.id
    }
  }
}

I am not sure if that is what you're looking for but please let me know if it works for you.

Cheers

Matt Duncan

unread,
Aug 20, 2014, 12:00:44 PM8/20/14
to mapp...@googlegroups.com
I'm sorry my intentions are not clear...let me describe my problem more thoroughly:

I have two sets of domain objects that I want to work in an invite system.
  • Tournament and Event objects are Inviteable
  • Teams and Users are Invitees
All four domain objects from above are distinct case classes with distinct Entity objects and tables that I use with mapperDao.

Invitees can be invited to Inviteable objects. Each set of objects extends their respective abstract class(Inviteable or Invitee) so they have a common set of methods even though they are disparate objects.

InviteT is an abstract class that embodies an actual invite between two objects(a User invited to an Event, a Team invited to a Tournament, etc.) It looks like this:

abstract class InviteT[T <% Inviteable, U <% Invitee] {
  val author
: T
  val receiver
: U
  val
Time: DateTime
  val message
: String
}

I then have case classes that are concrete implementations of InviteT -- they do the job of actually specifying what object T or U is.

case class EventInvite(
                       
Author: Event,
                       
Receiver: User,

                        message
: String = null,
                       
Time: DateTime

                       
) extends InviteT[Event, User]

What I want to do in the database is store the foreign key for any of those 4 domain objects in the "author" column and then use the "type" column to determine which relationship the foreign key is associated with.

From the example for class hierarchies I see that you can use case matching to determine what derivative of the abstract base class is being stored, and the "type" information to construct the right derivative. I want to do the same except that instead of using simple columns I want to use relationships.
On Monday, August 18, 2014 4:58:39 PM UTC-4, Kostas Kougios wrote: <blockquote class="gmail_quote" style="margin: 0
...

Konstantinos Kougios

unread,
Aug 20, 2014, 3:40:41 PM8/20/14
to mapp...@googlegroups.com
Hi Matt, thanks , I think the main problem then is the column "author". It is not an FK to 1 table but rather to 4 (or if I got it right probably to 2) tables. In order for mapperdao to work, the column should be an FK to 1 other entity (or in database terms a FK to 1 single column and declared so that referential integrity is achieved).

Thinking of it in terms of a query, you would have to do the query in 2 steps, because you can't join on "author" and one of the relevant tables until you know what's in invites.type.

In terms of the domain model, it makes sense. Now how can this be mapped to tables?

One way to do this, without loosing referential integrity, type safe and also to have the ability to use mapperdao's query api is this:

- map InviteT hierarchy into 1 table, but the table will contain
    a.    tournament_id (FK to tournament)
    b.    event_id (FK to event)
    c.    team_id (FK to team)
    d.    user_id (FK to user)

Note, a,b,c,d seems like a 1-to-1 relationship to me, according to InviteT

The mapping could be like:

InviteTEntity:
val tournament = onetoone(TournamentEntity) option( _.author match {
    case t:Tournament => Some(t)
    case _=>None
}

-    you might keep invites.type but it is not necessary any more. In the constructor you can check if you got a tournament or an event, a team or a user, and construct the relevant subclass of InviteT.

-    now you can use InviteTEntity.tournament in queries (and InviteTEntity.event etc)

Note that you will need 1 entity object for each of Tournament, Event,Teams , Users. But only 1 entity object for all InviteT's.

Is that something close to what you're looking for?

Cheers,

Kostas
--

Matt Duncan

unread,
Aug 21, 2014, 3:01:39 PM8/21/14
to mapp...@googlegroups.com
Yes this is exactly what I was looking for!!

The only difference is that I am using manytoone because one Event can have many EventInvite relationships, and a User can have many EventInvite as well (I think this is the correct way to do this?)

Can you elaborate on checking in the constructor? After using your suggested technique applying the ValuesMap implicit in the constructor seems to always yield an object -- and I don't see a way to use the raw variable as ColumnInfo to check the option.

object InviteEntity extends Entity[Int, SurrogateIntId, InviteT]("invites") {
  val id
= key("id") autogenerated (_.id)


  val team
= manytoone(TeamEntity) option(_.Author match {
   
case t: Team => Some(t)
   
case _ => None
 
})
  val
event = manytoone(EventEntity) option(_.Author match {
   
case e: Event => Some(e)
   
case _ => None
 
})
  val user
= manytoone(UserEntity) option(_.Author match {
   
case u: User => Some(u)
   
case _ => None
 
})
  val message
= column("message") to (_.message)
  val time
= column("createdOn") to (_.Time)


 
def constructor(implicit m: ValuesMap) = {

   
// m(team) yields Team instead of Option(Team), etc.
    //So I cannot match against each to determine what type of subclass I need to construct.

    (m(team),m(event),m(user)) match {
   
case (Some(t: Team), None, Some(u: User)) => new TeamInvite(t, u, message, time) with Stored {

      val id
: Int = InviteEntity.id
   
}
 
}
}

Thank you so much for taking the time to work through this with me! I truly appreciate all the help you've been offering.

                       message
<span
...

Konstantinos Kougios

unread,
Aug 21, 2014, 4:29:20 PM8/21/14
to mapp...@googlegroups.com
Hi Matt,

    I think now it should be a simple step. The constructor to work, it is based on implicitly converting "m" to the proper value. Usually this happens automatically i.e. if you had a case class MyModel(o:Option[X]), then in the constructor you could do new MyModel(column) and column would be implicitly converted to the correct type.

To make the implicit conversion work, you can do this inside the constructor():

val eventOption:Option[Event] = event

this should implicitly convert event from a many-to-one column into an Option[Event] using m, the ValueMap. The implicit method that will do the trick is part of Entity:

 columnManyToOneToOptionValue(...)

If the implicit call doesn't work (it should) , you can try calling the method straight on and check the error message, i.e.

val eventOption  =  columnManyToOneToOptionValue(event)

In any case, if nothing of the above works, just temporarily change your code to:

(Option(m(team)),Option(m(event)),Option(m(user))) match ...

until we find a better solution.

Cheers
--

Matt Duncan

unread,
Aug 21, 2014, 5:02:19 PM8/21/14
to mapp...@googlegroups.com
Wonderful :) Everything compiles! I will let you know when I have results. Thank you so much for your help!!
...
Reply all
Reply to author
Forward
0 new messages