saving with circumflex-orm

171 views
Skip to first unread message

sveri

unread,
Apr 18, 2012, 1:21:34 PM4/18/12
to circumfl...@googlegroups.com
Hi everybody,

i am fairly new to scala at all, but it caught my interest while i digged into the play framework.
Offering anorm as sql layer for play in scala did bother my, because i dont like writing my own queries anymore, looking for alternatives it seemed to me that circumflex-orm fits much better for my programming needs.

Now i try to integrate this into my play 2 project, but only successfull to the half. I was able to retrieve records from my database with the following function:

object Task extends Task with Table[Long, Task] {
  def findById(id: Long): Option[Task] = (this AS "t").map(t =>
    SELECT(t.*).FROM(t).WHERE(t.id EQ id).unique)
}

Debbuging into my code i could see that the retrieval did work and the values were set accordingly.

But i just cannot figure out how to:

1. print the value from my class variables,
  val task = Task.findById(1)
  println(task.name)
  returns "task.name" instead the value of task.name

and
2. how to store something in database. I read the docs and according to them it should suffice to call the .save() function on a task, but doing
  val task = new Taks("foo", "bar")
  println(task.save())
returns "1" what shouldnt be, as there are already records in the DB and the primary key "1" is already used.

This is my class:

class Task extends Record[Long, Task] with IdentityGenerator[Long, Task] {
 
  def this(name: String, description: String) = {
    this()
    this.name := name
    this.description := description
  }
 
  val id = "id".BIGINT.NOT_NULL.AUTO_INCREMENT
  val name = "name".VARCHAR(255).NOT_NULL
  val description = "description".TEXT.NOT_NULL
  val createdAt = "created_at".TIMESTAMP
  val dueTo = "due_to".DATE

  def PRIMARY_KEY = id
  def relation = Task
}

Maybe if i knew more about scala i would find what is wrong, but i just cannot figure out how to do it right.

Any help is appreciated.

Thanks in Advance,
Sven

Valery Aligorsky

unread,
Apr 19, 2012, 2:08:15 AM4/19/12
to circumfl...@googlegroups.com
Hi Sven,

The answer for the first question.

You should use `task.name()` instead of `task.name`. you should read how to get values of the fields at http://circumflex.ru/docs/orm/assembly.html#record , after the table of `types`.

You don't need to define this query 
```
def findById(id: Long): Option[Task] = (this AS "t").map(t =>
    SELECT(t.*).FROM(t).WHERE(t.id EQ id).unique)
```
since every `Relation`  has method `get`, that work like `findById` method. It is defined in file `relation.scala`  file
```
def get(id: PK): Option[R] = cache.getOption(id.toString, {
    val r = this.AS("root")
    r.criteria.add(r.PRIMARY_KEY EQ id).unique()
  })
```

The answer for the second answer that "1" is count of affected rows in a DB.

Best regards, 
Valery Aligorsky.

среда, 18 апреля 2012 г., 21:21:34 UTC+4 пользователь sveri написал:

sveri

unread,
Apr 19, 2012, 3:47:37 AM4/19/12
to circumfl...@googlegroups.com
Hi Valery,

thank you for your quick reply.

Using task.name() works very well, i dont know why i havn't seen that before.

Regarding the save() problem it still remains. Calling it returns 1, and like you said, that means, that 1 row was affected, so far so good. But i cannot see the changes applied (in that case, having a new row) in the database. It looks the same as before.
Do i need to persist them somehow? Maybe call a task.storeThemIntoTheDBRightNowDoIt()?

And another thing that strucked me right now is question on interaction with the play framework.
I can do formbinding there like this:

  val taskForm: Form[Tasks] = Form(
      mapping(
          "tasks" -> list(mapping(
              "id" -> text,
              "name" -> text,
              "description" -> text
          )(Task.apply)(Task.unapply))
      )(Tasks.apply)(Tasks.unapply)
  )

With two classes like this:
case class Task(name: String, description: String)
case class Tasks(tasks: List[Task])

The problem is the constructor. Using a Record class my Task look like this:
case class Task extends Record[Long, Task] with IdentityGenerator[Long, Task] {

  val id = "id".BIGINT.NOT_NULL.AUTO_INCREMENT
  val name = "name".VARCHAR(255).NOT_NULL
  val description = "description".TEXT.NOT_NULL

  def PRIMARY_KEY = id
  def relation = Task
}

But i would need something like this:
case class Task(name: String, description: String) extends Record[Long, Task] with IdentityGenerator[Long, Task] {

  val id = "id".BIGINT.NOT_NULL.AUTO_INCREMENT
  val name = "name".VARCHAR(255).NOT_NULL
  val description = "description".TEXT.NOT_NULL

  def PRIMARY_KEY = id
  def relation = Task
}

Is that possible with circumflex-orm?

Thanks for your help,
Sven

Boris Okunskiy

unread,
Apr 19, 2012, 4:31:20 AM4/19/12
to circumfl...@googlegroups.com
Greetings Sven,

Regarding your first question, about `save()`. First of all, set the logger level for `ru.circumflex.orm` to DEBUG and examine the exact query, make sure it says `UPDATE`.

The actual reason, however, is in transaction demarcation. All communications with database should occur only within `Context.executeInNew { ctx => … }` block, which provides semantics for "real" database transactions. For web applications, the good old pattern "Transaction per request" works perfectly, this is how we do it out-of-box when Circumflex ORM works together with Circumflex Web Framework.

Well, since you work with Play, you should wrap all request processing logic inside `Context.executeInNew` block. The easiest way to do so is to create a simple filter and then map it to all application URLs. The filter itself is quite simple:

class CircumflexContextFilter extends ServletFilter {

  import ru.circumflex.core._

  def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) {
    Context.executeInNew { ctx =>
      chain.doFilter(req, res)
    }
  }
 
}

Frankly, I don't know if Play provides another means to wrap application request processing, so probably you should dig around their lifecycle constructions. For more information please refer to http://circumflex.ru/api/2.1/circumflex-web/src/main/scala/filter.scala.html (the "Main Lifecycle" section -- this is how we do it for Web Framework.

If you run into any troubles with transaction demarcation with ORM, please feel free to post your questions into the group. You see, our documentation is a bit out-of-date, and there ain't much information on common transaction-related scenarios and issues.

---

Okay, now about the constructor thing. Circumflex requires all records to have the default zero-argument constructors (this way Circumflex ORM instantiates records when it parses result sets). So the easiest way to solve your problem is to add another constructor:

def this(name: String, description: String) {
  this()
  this.name := name
  this.description := description
}

And it would work perfectly fine with `new Task("foo", "bar")`, but since you use case classes there, the only way to achieve what you intend is to supply additional `def apply(name: String, description: String): Task` method to the `object Task` (the companion object).

I hope it helps,

Best regards,
Boris Okunskiy

sveri

unread,
Apr 19, 2012, 6:39:26 AM4/19/12
to circumfl...@googlegroups.com
Hi Boris,

thanks for your extensive explanation.
Everything works now as i wanted it, thanks to your hints, especially the context was important. I see if i can put it into the lifecycle of play, but for now it is enough to write the context around every save/update.

Best regards,
Sven

Boris Okunskiy

unread,
Apr 19, 2012, 6:48:26 AM4/19/12
to circumfl...@googlegroups.com
You are welcome,

It is generally not recommended to wrap every database operation into separate transactions. Besides obvious overhead on connection acquisition and releases, Circumflex ORM tries its best to speedup various aspects within single transaction (occasional reads by id, direct and inverse associations lookups, etc.). So it is generally advised to attach transaction demarcation logic to some global lifecycle construct (such as Filter).

Just give it a try and let me know, if it works.

Best regards,
Boris Okunskiy

sveri

unread,
Apr 19, 2012, 7:09:15 AM4/19/12
to circumfl...@googlegroups.com
Well my plan was to wrap all database actions into one transaction per request, as far as possible.
So you mean a Context.executeInNew{} opens one connection and then closes it again? And it might be better to have a connection openend via session for instance?

Best regards,
Sven

Boris Okunskiy

unread,
Apr 19, 2012, 7:15:40 AM4/19/12
to circumfl...@googlegroups.com
No, the connection is acquired from connection pool only when it is required (e.g. when you perform your first select in transaction). Default transaction manager then commits this active transaction when the context is over.

Please refer to the source code of `DefaultTransactionManager`: http://circumflex.ru/api/2.1/circumflex-orm/src/main/scala/config.scala.html

Best regards,
Boris Okunskiy

On Apr 19, 2012, at 2:39 PM, sveri wrote:

Reply all
Reply to author
Forward
0 new messages