Should I create traits to represent enum values for my models? - scala

Say I have a model like:
case class User(
id: Int,
name: String,
userType: Int)
Should I then do this:
sealed trait UserType
case object Member() extends UserType
case object Anonymous() extends UserType
I should also somehow associate a value for each UserType.
I would then change the User case class to have a UserType property instead of the int?
I guess then I would create a implicit converter for slick, which I believe would be a MappedColumnType from int to UserType.
Update
This is for using slick db access.

I would do it the other way around. I would have a type for the user depending on the scenario that extends User:
sealed trait User
case class NormalUser(name: String, id: Int) extends User
case class SuperUser(name: String, id: Int, superPowers: Map[String, String]) extends User
And then pattern match on the actual User type when needed.

I'd go with enum:
object UserType extends Enumeration {
type UserType = Value
val Member = Value("Member")
val Anonymous = Value("Anonymous")
}
And the converter as you said:
implicit val userTypeColumnType = MappedColumnType.base[UserType, String](
userType => userType.toString,
UserType.withName
)
Then you can use userType: UserType in your User case class.
In table definition you can have def userType = column[UserType]("user_type")
Update
One of the reasons for choosing enum over trait is that slick is not able to find this implicit converter when you do not put the supertype explicitly.
E.g.
.filter(_.userType === Member)
yields
type mismatch;
[error] found : Member.type
[error] required: slick.lifted.Rep[?]
[error] .filter(_.userType === Member).result
But the following works
.filter(_.userType === Member.asInstanceOf[UserType])
.filter(_.userType === (Member : UserType))

As #Michal Tomanski mentioned below - there are certain problems when using trait / case objects. What you would need to do is this:
sealed trait UserType {
val code: Int
}
case object Member extends UserType {
override val code: Int = 0
}
case object Anonymous extends UserType {
override val code: Int = 1
}
object UserType {
def byId(id: Int): UserType = id match {
case Member.code => Member
case Anonymous.code => Anonymous
case _ => throw new IllegalArgumentException("...")
}
}
implicit val enumColumnType = MappedColumnType.base[UserType, Int](
e => e.code,
i => UserType.byId(i)
)
Above would allow you to do query like this:
UserTable
.filter(_.userType === (Member :: UserType))
.result
This is precisely what #Michal Tomanski pointed out. You can do small trick however to smoothen this up a little bit.
Just modify your trait like this:
sealed trait UserType {
val code: Int
// I added field below
val base: UserType = this
}
and then you can have your query like this:
UserTable
.filter(_.userType === Member.base)
.result
It may be slightly better alternative than casting.
Other than that - I'd follow #Michal Tomanski answer (using Enumeration) assuming that it is sufficient for your case (perhaps solution with trait / case objects is more flexible, but on the other hand there is more plumbing you need to do as can be seen in this answer).

Related

Extend case class from another case class

I have two case class Person and Employee
case class Person(identifier: String) {}
case class Employee (salary: Long) extends Person {}
I am getting following error:
Unspecified value parameters: identifier: String
Error: case class Employee has case ancestor Person, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes
I am new to Scala and not able to understand what I have to do.
Version:
Scala : 2.11
Unfortunately, I'm afraid it is not possible for case class to extend another case class.
The inheritance in "plain" classes would look like:
class Person(val identifier: String) {}
class Employee(override val identifier: String, salary: Long)
extends Person(identifier) {}
val el = new Employee("abc-test", 999)
println(el.identifier) // => "abc-test"
If you would like to achieve a similar effect with case classes, you would need to reach out to traits:
trait Identifiable {
def identifier: String
}
case class Person(identifier: String) extends Identifiable {}
case class Employee(identifier: String, salary: Long)
extends Identifiable {}
val el = Employee("abc-test", 999)
println(el.identifier) // => "abc-test"
Defining extractors
Extractor provides a way for defining a matching statement used in pattern matching. It is defined in an object in unaply method.
Let's consider the first example again adding support for extractors:
class Person(val identifier: String)
class Employee(override val identifier: String, val salary: Long)
extends Person(identifier)
object Person {
def unapply(identifier: String): Option[Person] = {
if (identifier.startsWith("PER-")) {
Some(new Person(identifier))
}
else {
None
}
}
}
object Employee {
def unapply(identifier: String): Option[Employee] = {
if (identifier.startsWith("EMP-")) {
Some(new Employee(identifier, 999))
}
else {
None
}
}
}
Now, let's define a method that will define pattern matching using those extractors:
def process(anInput: String): Unit = {
anInput match {
case Employee(anEmployee) => println(s"Employee identified ${anEmployee.identifier}, $$${anEmployee.salary}")
case Person(aPerson) => println(s"Person identified ${aPerson.identifier}")
case _ => println("Was unable to identify anyone...")
}
}
process("PER-123-test") // => Person identified PER-123-test
process("EMP-321-test") // => Employee identified EMP-321-test, $999
process("Foo-Bar-Test") // => Was unable to identify anyone...
Case classes in Scala add several different features but often you really use only some of them. So the main question you need to answer is which features you really need. Here is a list based on the spec:
remove the need to type val before field names/constructor params
remove the need for new by adding apply method to the companion object
support for pattern matching by adding unapply method to the companion object. (One of nice things of Scala is that pattern-matching is done in a non-magical way, you can implement it for any data type without requiring it to be a case class)
add equals and hashCode implementations based on all the fields
add toString implementations
add copy method (useful because case classes are immutable by default)
implement Product trait
A reasonable guess of the equivalent for case class Person(identifier: String) is
class Person(val identifier: String) extends Product {
def canEqual(other: Any): Boolean = other.isInstanceOf[Person]
override def equals(other: Any): Boolean = other match {
case that: Person => (that canEqual this) && identifier == that.identifier
case _ => false
}
override def hashCode(): Int = identifier.hashCode
override def toString = s"Person($identifier)"
def copy(newIdentifier: String): Person = new Person(newIdentifier)
override def productElement(n: Int): Any = n match {
case 0 => identifier
case _ => throw new IndexOutOfBoundsException(s"Index $n is out of range")
}
override def productArity: Int = 1
}
object Person {
def apply(identifier: String): Person = new Person(identifier)
def unapply(person: Person): Option[String] = if (person eq null) None else Some(person.identifier)
}
case class Employee(override val identifier: String, salary: Long) extends Person(identifier) {}
Actually the main objections to inheriting from a case class and especially making a case class inheriting another one are the Product trait, copy and equals/hashCode because they introduce ambiguity. Adding canEqual partially mitigates the last problem but not the first ones. On the other hand in a hierarchy like yours, you probably don't need the copy method or Product implementation at all. If you don't use Person in pattern matching, you don't need unapply as well. Most probably all you really need case for is apply, toString and hashCode/equals/canEqual.
Inheriting from case classes (even with regular non-case classes, which is not prohibited) is a bad idea. Check this answer out to get an idea why.
You Person does not need to be a case class. It actually does not need to be a class at all:
trait Person {
def identifier: String
}
case class Employee(identifier: String, salary: Long) extends Person

How to make type safe filtration in Scala?

I have different types of entities in db and I want to be able to filter them by some conditions in a type safe way. I'm using Slick for db access, but it doesn't really matter I think.
For example, let's say I have User and Post entities. User has id and email fields, Post has id and title fields.
Right now I'm using ADT to represent filters. Looks like this:
sealed trait Filter {
def byId(id: Long): Filter = and(ById(id))
def byEmail(email: String): Filter = and(ByEmail(email))
def byTitle(title: String): Filter = and(ByTitle(title))
def and(other: Filter): Filter = And(this, other)
}
object Filter {
def apply(): Filter = NoFilter
}
case class ById(id: Long) extends Filter
case class ByEmail(email: String) extends Filter
case class ByTitle(title: String) extends Filter
case class And(a: Filter, b: Filter) extends Filter
case object NoFilter extends Filter
And interpreter for it in UserDao. PostDao looks the same:
trait Dao[A] {
def find(filter: Filter, offset: Int, limit: Int):Seq[A]
}
object UserDao extends Dao[User]{
override def find(filter: Filter, offset: Int, limit: Int): Seq[Any] = {
filterTable(filter).drop(offset).take(limit).result
}
private def filterTable(table: Query[UserTable, User, Seq], filter: Filter): Query[UserTable, User, Seq] =
filter match {
case ById(id) => table.filter(_.id === id)
case ByEmail(email) => table.filter(_.email === email)
case And(a, b) => filterTable(filterTable(table, a), b)
case NoFilter => table
case other =>
log.warn(s"Filter not supported $other")
table
}
}
And is used by generic service:
class Service[A](dao: Dao[A]) {
def find(filter: Filter): Seq[A] = {
// do some stuff
dao.find(filter, offset = 0, limit = 100)
// do some other stuff
}
}
As you see it works, but it's not type safe. Although filtering users by title won't fail it doesn't make sense and could be cause of a bug. I thought maybe I create different sets of ADT for different entities, but then I will have to duplicate filters which are very similar(id filter, for example). So in summary I want to have filters which:
Type safe. You can't create Filter ByTitle for User
You don't need to duplicate common filters like ById, And, Or, In and so on.
How can I do it? Maybe ADT is not what I need?
If you create a bunch of "marker" traits for your domain objects, then you can parametrize your Filter type with the type of data it can be filtering:
trait Filter[-T]
case class ById(id: Long) extends Filter[HasId]
case class ByEmail(email: String) extends Filter[HasEmail]
case class ByTitle(title: String) extends Filter[HasTitle]
case class And[A, B](a: Filter[A], b: Filter[B]) extends Filter[A with B]
case class Or[A, B](a: Filter[A], b: Filter[B]) extends Filter[A with B]
Now, you can declare filterTable to constrain the type of filter it will accept:
def filterTable(table: Query[UserTable, User, Seq], filter: Filter[User]) = ...
So, filterTable(myTable, ById(1) and ByEmail("foo")) will compile, but
filterTable(myTable, ByTitle("foo")) will not.

Compile error in filter with Slick mapped column type

I have trouble filtering on a custom type using slick-3.1.1. The following, self contained example, illustrates my problem:
object IllustrateSlickQuestion {
val sqlDriver = slick.driver.PostgresDriver
import sqlDriver.api._
trait SomeBaseType {
def value: Int
}
object SomeBaseType {
def apply(value: Int): SomeBaseType = SomeType(value)
}
case class SomeType(value: Int) extends SomeBaseType
implicit val someBaseTypeMappedColumnType = MappedColumnType.base[SomeBaseType, Int](_.value, SomeBaseType.apply)
class SomeTable(tag: Tag) extends Table[(SomeBaseType, Option[SomeBaseType])](tag, "my_table") {
def someColumn = column[SomeBaseType]("some_column")
def someNullableColumn = column[Option[SomeBaseType]]("some_nullable_column")
def * = (someColumn, someNullableColumn)
}
val someTable = TableQuery[SomeTable]
// These selects work:
val compilingSelect1 = someTable.filter(_.someColumn inSet Set(SomeType(42)))
val compilingSelect2 = someTable.filter(_.someNullableColumn inSet Set(SomeType(42)))
// Does not compile:
// [error] type mismatch;
// [error] found : IllustrateSlickQuestion.SomeType
// [error] required: slick.lifted.Rep[?]
// [error] val brokenSelect1 = someTable.filter(_.someColumn === SomeType(42))
val brokenSelect1 = someTable.filter(_.someColumn === SomeType(42))
// Does not compile either:
// [error] see above
val brokenSelect2 = someTable.filter(_.someNullableColumn === SomeType(42))
}
This problem goes away if I use SomeType instead of SomeBaseType in my definitions and in the MappedColumnType. That however is not an option in the code I really care about, since there SomeBaseType represents an enumeration. I'm therefore stuck with using inSet instead of === as a workaround.
Am I doing anything wrong, or is this a bug in Slick?
I'm not sure on the exact reason why this happens. I think it has to do with having ambiguous implicit conversions in scope (both to Rep[SomeBaseType] and to Rep[Option[SomeBaseType]]). So the compiler doesn't know which to choose (and thus chooses neither). But I could be wrong. I do have some workarounds for you:
// Adding a type annotation to the filter:
val fixedSelect1 = someTable.filter(_.someColumn === (SomeType(42):SomeBaseType))
// Using a helper method:
def query(someType: Rep[SomeBaseType]) =
someTable.filter(_.someNullableColumn === someType)
query(SomeType(42))
// With compiled queries
val query = Compiled { (someType: Rep[SomeBaseType]) =>
someTable.filter(_.someNullableColumn === someType)
}
This is a Scala issue, not a Slick issue. Extending a case class from an object is not recommended. Your sample code does not show how the value property of the case class is used. Did you mean to override the base class value property? If so, then you need to write:
case class SomeType(override val value: Int) extends SomeBaseType
However, you will encounter issues. You may find that an ADT is preferable to using Enumerator:
sealed trait MyEnum
trait EnumValue1 extends MyEnum
trait EnumValue2 extends MyEnum
trait EnumValue3 extends MyEnum
Then you might be able to write something like this (not tested):
case class SomeType(myEnum: MyEnum) extends AnyVal with MappedTo[Int]
My preference is to use a Java enum:
public enum MyEnum {
EnumValue1 , EnumValue2, EnumValue3
}
And then write:
case class SomeType(myEnum: MyEnum) extends AnyVal with MappedTo[Int]

Scala + Slick + Accord - Custom value class types not working. Just a bad approach?

I am still pretty new to Scala and looking at using Slick.
I also am looking at Accord (github.com/wix/accord) for validation.
Accord's validation seems to be on objects as a whole, but I want to be able to define validators for field types, so I've thought of using type aliasing using value classes so that I can easily re-use validations across various case classes that use those field types.
So, I've defined the following:
object FieldTypes {
implicit class ID(val i: Int) extends AnyVal
implicit class UserPassword(val s: String) extends AnyVal
implicit class Email(val s: String) extends AnyVal
implicit class Name(val s: String) extends AnyVal
implicit class OrgName(val s: String) extends AnyVal
implicit class OrgAlias(val s: String) extends AnyVal
}
package object validators {
implicit val passwordValidator = validator[UserPassword] { _.length is between(8,255) }
implicit val emailValidator = validator[Email] { _ is notEmpty }
implicit val nameValidator = validator[Name] { _ is notEmpty }
implicit val orgNameValidator = validator[OrgName] { _ is notEmpty }
implicit val teamNameValidator = validator[TeamName] { _ is notEmpty }
}
case object Records {
import FieldTypes._
case class OrganizationRecord(id: ID, uuid: UUID, name: OrgName, alias: OrgAlias)
case class UserRecord(id: ID, uuid: UUID, email: Email, password: UserPassword, name: Name)
case class UserToOrganizationRecord(userId: ID, organizationId: ID)
}
class Tables(implicit val p: JdbcProfile) {
import FieldTypes._
import p.api._
implicit object JodaMapping extends GenericJodaSupport(p)
case class LiftedOrganizationRecord(id: Rep[ID], uuid: Rep[UUID], name: Rep[OrgName], alias: Rep[OrgAlias])
implicit object OrganizationRecordShape extends CaseClassShape(LiftedOrganizationRecord.tupled, OrganizationRecord.tupled)
class Organizations(tag: Tag) extends Table[OrganizationRecord](tag, "organizations") {
def id = column[ID]("id", O.PrimaryKey)
def uuid = column[UUID]("uuid", O.Length(36, varying=false))
def name = column[OrgName]("name", O.Length(32, varying=true))
def alias = column[OrgAlias]("alias", O.Length(32, varying=true))
def * = LiftedOrganizationRecord(id, uuid, name, alias)
}
val organizations = TableQuery[Organizations]
}
Unfortunately, I clearly misunderstand or overestimate the power of Scala's implicit conversions. My passwordValidator doesn't seem to recognize that there is a length property to UserPassword and my * declaration on my Organizations table doesn't seem to think that it complies to the shape defined in LiftedOrganizationRecord.
Am I just doing something really dumb here on the whole? Should I not be even trying to use these kinds of custom types and simply use standard types instead, defining my validators in a better way? Or is this an okay way of doing things, but I've just forgotten something simple?
Any advice would be really appreciated!
Thanks to some helpful people on the scala gitter channel, I realized that the core mistake was a misunderstanding of the conversion direction for value classes. I had understood it to be ValueClass -> WrappedValueType, but it's actually WrappedValueType -> ValueClass. Thus, Slick wasn't seeing, as an exmaple, ID as an Int.

Scala List and Subtypes

I want to be able to refer to a list that contains subtypes and pull elements from that list and have them implicitly casted. Example follows:
scala> sealed trait Person { def id: String }
defined trait Person
scala> case class Employee(id: String, name: String) extends Person
defined class Employee
scala> case class Student(id: String, name: String, age: Int) extends Person
defined class Student
scala> val x: List[Person] = List(Employee("1", "Jon"), Student("2", "Jack", 23))
x: List[Person] = List(Employee(1,Jon), Student(2,Jack,23))
scala> x(0).name
<console>:14: error: value name is not a member of Person
x(0).name
^
I know that x(0).asInstanceOf[Employee].name but I was hoping there was a more elegant way with types. Thanks in advance.
The best way is to use pattern matching. Because you are using a sealed trait the match will be exhaustive which is nice.
x(0) match {
case Employee(id, name) => ...
case Student(id, name, age) => ...
}
Well, if you want the employees, you could always use a collect:
val employees = x collect { case employee: Employee => employee }