Re: Composition Classes

89 views
Skip to first unread message

Maxime Lévesque

unread,
Nov 5, 2012, 10:15:56 AM11/5/12
to squ...@googlegroups.com
Squeryl does not have this form of ORM functionnality, as a design choice,
ORM functionnality (in Squeryl), don't go beyond mapping rows to objects and
basic CRUD and schema definition. Everything else if achieved via the DSL,
there is a lot of litterature about why ORMs suck, a big part of it is
the excess
of "magic", or lack of control of the generated SQL. The one good thing that
ORM has (besides allowing to work with objects rather than result sets/cursor)
is to allow code that is more DRY, or helps to apply the "Single
Source Of Truth"
principle. A DSL can provide these benefit almost as well, and with much less
magic (loss of control of the generated SQL).

For this specific case, I will assume that User and AuditInfo live in
two different table,
you will write a little bit more code than you would if you had
composition, but you will
never get a "suprise" join by fetching a User. In many cases it is
stiill possible to
centralize the join logic, you can define this only once :

def fetchUserWithAuditInfo(userName: String):
from(users, auditInfo)((u, ai) =>
where(u.userName === userName and u,id === ai.userId)
)

and since the return type is a Query[(User,AuditIndo)], it can be a
sub query of a more complex one.

If what you had in mind is a User and AuditInfo that live in the same
table, then, why not have a trait
AuditInfo extended by the User ? Then code that needs to deal with
just an AuditInfo can take a user
as argument. If you want a bit more syntactic sugar, have this method in user :

def auditInfo: AuditInfo = this

Does any of this make sense ?

2012/11/2 Joe Zulli <j...@savings.com>:
> Hi everyone,
>
> I'm new to Squeryl, and I'm trying to figure out how to do composition
> classes. It seems that there is no way to do it but I might be wrong.
> Specifically, I want to use composition to store some auditing
> characteristics for my domain objects. Here's roughly what I'm doing:
>
> case class AuditInfo(var createUser: Long = -1, var createTime: Timestamp =
> new Timestamp(System.currentTimeMillis), var updateTime: Timestamp = new
> Timestamp(System.currentTimeMillis))
>
> trait Auditing {
>
> val auditInfo: AuditInfo
>
> }
>
> So I have some auditing code, then my domain classes look like this:
>
> case class User(val userName: String, val email: String, override val
> auditInfo: AuditInfo = new AuditInfo()) extends Auditing
>
> In this example, I want the createUser, createTime and updateTime fields to
> all map to respective columns in the USER table right along side userName
> and email. Is this possible to do in Squeryl? If so, can anyone point me in
> the right direction?
>
> If not, I am happy to take a shot at implementing it myself and submit back
> as a patch. If Max or anyone wants to give me some pointers to get me
> started (where in the code to look, etc...), it would be greatly
> appreciated.
>
> Thanks in advance!
>
> Joe

David Whittaker

unread,
Nov 5, 2012, 12:25:07 PM11/5/12
to squ...@googlegroups.com
If what you had in mind is a User and AuditInfo that live in the same
table, then, why not have a trait
AuditInfo extended by the User 

It sounds like this is what you're looking for.  Case classes complicate things a bit though.  If you put your AuditInfo vars into a trait, they'll be inherited by a case class that extends AuditInfo, but they won't be used in the generated equals or copy methods.  You could define them as abstract methods in the AuditInfo trait, but then you'll have the extra boilerplate of defining them again in the constructor of each case class that implements the trait.  Unfortunately, there isn't a great solution for this, and I tend to go with abstract methods and more boilerplate than I'd like as a solution myself.

I'm with Max on being very against most of the automatic SQL building that ORMs like Hibernate do.  That said, if you really wanted to take a stab at something like Hibernate's @Embedded[1] annotation, I think that might be a reasonable addition to Squeryl.  It would allow for composition of case classes, since they don't handle inheritance very well, and would save a lot of boilerplate for schemas where many tables have common column definitions.  I don't speak for Max on that feeling though and I think it would be a relatively big patch.  You'd have to change how FieldMetaData is collected and stored, how entities are enhanced during queries to track field access through the embedded classes, and how objects are built from ResultSets.

I feel like I should also point out though that, if you're going to use mutable entities, maybe case classes aren't the best choice anyway.

Joe Zulli

unread,
Nov 5, 2012, 3:34:40 PM11/5/12
to squ...@googlegroups.com
Hi David and Max,

Thanks for the replies. Max: your reply definitely makes sense. David: Yes, you hit the nail on the head exactly. I originally was using a trait, but found that it caused several complications unrelated to Squeryl, particularly in the fact that the generated copy method doesn't use them. Huge pain! So my choice is to either deal with all of the extra boiler-plate that you mentioned for all of my case classes (there are a lot) OR try to wrap all of the auditing logic into it's own composite class that is indeed itself a case class (fixes all the case class funkyness, but doesn't work with Squeryl). There may be other solutions, but those two are the only viable ones I can think of at the moment. There is a third options, which is to try to accomplish at least come of the auditing with database triggers, but that won't work for everything (and it introduces it's own set of problems).

I wholeheartedly agree with you and Max on not wanting to bloat Squeryl. Coming from a Java/Hibernate background, I can definitely say that I don't ever want to go back to that :). But I also think/hope that maybe there is a way to offer something very simple that adds a lot of power without all the complexity. What can I say, I'm an idealist :). Maybe I will play around with doing a simplified version of hibernate's @Embedded annotation, and if I get anything working that looks cool, I will send it  over for your blessing?

Also, to anyone else reading this who also has the same auditing concerns as we do at my company, I'd love to hear about how you solved it. Basically our requirement is that every table in addition to having the specific columns that it needs, also has 4 extra columns( create_user, update_user, create_time, update_time) so that later on we can see who created/modified what data, and when. So a typical user table in our DB would look something like:

CREATE TABLE user (
  id int unsigned not null primary key,
  username varchar(255) not null default '',
  email varchar(255) not null default '',
  ....
  create_user int unsigned not null,
  update_user int unsigned not null,
  create_time timestamp not null,
  update_time timestamp not null
);

My goal is to make those auditing fields an invisible to the developers as reasonable possible, since they are not really intended to be used programmatically  but rather by our BI team when doing analysis. 

Thanks again for the help,
Joe

Roland Kaercher

unread,
Nov 6, 2012, 5:43:05 AM11/6/12
to squ...@googlegroups.com
Hello Joe,

I thought of using triggers in the database too but found out squeryl provides a similar mechanism.
 I am using the following to record creation and modification time in multiple entities:

I have this trait which contains the additional fields:

trait ModificationTimeRecording extends KeyedEntity[Long] {
  var lastModified: Timestamp = _
  var created: Timestamp = _
}

Entities which need the fields use it like that:

class Customer(val id: Long,
  var companyName: String) extends KeyedEntity[Long] with ModificationTimeRecording

In my schema I have callbacks like the following which handle the setting/updating of the fields:

override def callbacks = Seq(
    beforeUpdate[ModificationTimeRecording] map (p => {
      p.lastModified = new Timestamp(new Date().getTime)
      p
    }),
    beforeInsert[ModificationTimeRecording] map (p => {
      p.created = new Timestamp(new Date().getTime)
      p.lastModified = new Timestamp(new Date().getTime)
      p
    }))

Kind regards,

Roland
Reply all
Reply to author
Forward
0 new messages