skinny-framework / skinny-framework

:monorail: "Scala on Rails" - A full-stack web app framework for rapid development in Scala

Home Page:https://skinny-framework.github.io/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

findById with #includes doesn't return a result as expected when assocations are absent

yamitzky opened this issue · comments

I found a weird behavior about ORM's eager loading (not sure if it is a "bug")

I define a model like;

case class Article(
  id: Int,
  title: String,
  body: String,
  createdAt: DateTime,
  userId: Int,
  user: Option[User] = None
)
object Article extends SkinnyCRUDMapperWithId[Int, Article] {
  override lazy val tableName = "articles"
  override lazy val defaultAlias = createAlias("a")
  override def idToRawValue(id: Int): Any = id
  override def rawValueToId(value: Any): Int = value.toString.toInt

  override def extract(rs: WrappedResultSet, rn: ResultName[Article]): Article = {
    autoConstruct(rs, rn, "user")
  }

  lazy val userOpt = {
    belongsTo[User](
      right = User,
      merge = (a, u) => a.copy(user = u)
    ).includes[User](
        merge = (as, us) => as map { a =>
          us.find(u => a.user.exists(_.id == u.id))
            .map(u => a.copy(user = Some(u)))
            .getOrElse(a)
        })
  }
}

Then, find articles like;

Article.includes(Article.userOpt).findById(article_id) // type is Option[Article]
// notice that findAll().headOption returns same result

When user is found, article is not empty. But when user is not found, article is empty!

On the other hand, I tried a case not using eager loading, and found different behavior.

object Article extends SkinnyCRUDMapperWithId[Int, Article] {
  /* skip */

  belongsTo[User](
    right = User,
    merge = (a, u) => a.copy(user = u)
  ).includes[User](
      merge = (as, us) => as map { a =>
        us.find(u => a.user.exists(_.id == u.id))
          .map(u => a.copy(user = Some(u)))
          .getOrElse(a)
      }).byDefault
}
Article.findById(article_id) returns 

In this case, article is not empty whether user is empty or not, and I think this is more natural behavior.

So, the question is that, is this an expected specification and can I avoid the default eager loading's behavior(wanna always get non-empty result)?


Environment:

select a.id as i_on_a, a.title as t_on_a, a.body as b_on_a, a.created_at as ca_on_a, a.user_id as ui_on_a , u.id as i_on_u, u.name as n_on_u, u.gender as g_on_u, u.attr1 as a1_on_u, u.attr2 as a2_on_u, u.attr3 as a3_on_u, u.attr4 as a4_on_u, u.attr5 as a5_on_u, u.attr6 as a6_on_u, u.attr7 as a7_on_u, u.attr8 as a8_on_u, u.attr9 as a9_on_u, u.attr10 as a10_on_u, u.attr11 as a11_on_u, u.attr12 as a12_on_u, u.attr13 as a13_on_u, u.attr14 as a14_on_u, u.attr15 as a15_on_u, u.attr16 as a16_on_u, u.attr17 as a17_on_u, u.attr18 as a18_on_u, u.attr19 as a19_on_u, u.attr20 as a20_on_u, u.attr21 as a21_on_u, u.attr22 as a22_on_u, u.attr23 as a23_on_u from articles a left join users u on a.user_id = u.id where a.id = 1;

This seems to be a bug. I just took a look at this issue and figured out the reason(my silly mistake).
I'll fix this issue later and release 1.3.9 as soon as possible.
As far as this case, using #joins instead will work as expected (right, I guess you're just evaluating skinny's eager loading feature).
Thank you for your report!

Thank you for the answer, and I am loooooking forward the fix!