How to manage scala enum with Anorm? - scala

I want to create user roles by using enums (thought that it would be the best idea).
How should I proceed with this?
My current code looks like this:
object UserRole extends Enumeration {
type UserRole = Value
val admin, user, manager = Value
}
case class User(id: Long, firstname: String, lastname: String, password: String, email: String, role: UserRole)
So how should I define the user "simple":
val simple = {
get[Long]("user.id") ~
get[String]("user.firstname") ~
get[String]("user.lastname") ~
get[String]("user.password") ~
get[String]("user.email") ~
get[UserRole]("user.role") map {
case id~firstname~lastname~password~email~role => User(id, firstname, lastname, password, email, role)
}
}
And how should I save it to database?
CREATE TABLE user (
id integer NOT NULL DEFAULT nextval('user_id_seq'),
firstname varchar(60),
lastname varchar(60),
password varchar(255),
email varchar(60),
role varchar(40)
);

Anorm doesn't support scala enums (and scala enums are pretty bad, in my opinion). What I usually do is create another case class with it's own parser and table space, and JOIN it to relevant queries. The UserRole can be parsed in User by composing it within User.simple.
case class UserRole(id: Long, name: String)
object UserRole {
val simple: RowParser[UserRole] = {
get[Long]("roles.id") ~
get[String]("roles.name") map {
case id~name => UserRole(id, name)
}
}
}
case class User(id: Long, firstname: String, lastname: String, password: String, email: String, role: UserRole)
object User {
val simple: RowParser[User] = {
get[Long]("user.id") ~
get[String]("user.firstname") ~
get[String]("user.lastname") ~
get[String]("user.password") ~
get[String]("user.email") ~
UserRole.simple map {
case id~firstname~lastname~password~email~role =>
User(id, firstname, lastname, password, email, role)
}
}
}

As another alternative, you could model your roles as with a sealed trait.
sealed trait UserRole
case object Admin extends UserRole
case object User extends UserRole
case object Manager extends UserRole
Then provide a anorm reader for your type.
implicit val column: Column[UserRole] = {
Column.nonNull1 { (value, meta) =>
val MetaDataItem(qualified, nullable, clazz) = meta
value match {
case "Admin" => Right(Admin)
case "User" => Right(User)
case "Manager" => Right(Manager)
case unknown => Left(TypeDoesNotMatch(s"unknown UserRole from $unknown"))
}
}
}
Then you can use the type in your row parser like get[UserRole]
Note the matching on the string, the obviously a pretty budget but you get the idea.
For more details see the docs here, https://playframework.com/documentation/2.4.x/ScalaAnorm#Column-parsers

Related

smart constructor for multiple types in one doobie query

I have a case class User:
case class User(
email: String,
username: String,
salutation: String,
surname: String
)
An I have a Query that fills this User case class:
sql"""
SELECT email, username, salutation, surname
FROM ???
WHERE ???
""".query[User]
Now I want to have a Type for each content of the User case class like this:
case class User(
email: EmailType,
username: UsernameType,
salutation: SalutationType,
surname: SurnameType
)
And each of this types, has a smart constructor to check, if the data is correct, like this:
case class EmailType private(email: String)
object EmailType {
def fromStringUnsafe(mayBeEmail: String): EmailType = {
// some checking
}
}
I tried, to use the Get[User] wrapper from doobie, but as I understood it, you can only check for one type from the query, but I have 4 types:
email, username, salutation, surname
So my question is, how can I do this with doobie?

How to use scala case class as function parameter to specify different fields to use?

I have some duplication of code due to having to do some grouping on 3 different fields in a case class and then populate a new case class with those. Since they share a common schema it should be possible for me to do a function that can take the input of the 3 different fields and populate accordingly. However, I am not exactly sure how to do this.
Schemas:
case class Transaction(
senderBank: Bank,
receiverBank: Bank,
intermediaryBank: Bank)
case class Bank(
name: String,
country: Option[String],
countryCode: Option[String])
case class GroupedBank(
name: String,
country: Option[String],
countryCode: Option[String],
bankType: String)
Current function I tried to do:
def groupedBank(transactionSeq: Seq[Transaction], bankName: Bank, bankTypeString: String): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(_ => bankName.name).map {
case (key, transactionSeq) =>
val bankGroupedSeq = transactionSeq.map(_ => {
GroupedBank(
name = bankName.name,
country = bankName.country,
countryCode = bankName.countryCode,
bankType = bankTypeString)
})
bankGroupedSeq
}
}
I need to do the grouping for SenderBank, receiverBank, and intermediaryBank. However, I am not sure how to refer to them correctly in the function parameter bankName. So for SenderBank I would want to do something like Transaction.senderBank, which would point to the correct fields for name, country and so on for senderBank. For receiverBank it should be similar, so Transactions.receiverBank, which then refers to the correct fields for receiverBank and so on. And again for intermediaryBank the same logic. My question is therefore how can I accomplish something like this or is there another way that would be more appropriate?
You can pass a function to extract the bank with the correct type from a transaction:
def groupedBank(
transactionSeq: Seq[Transaction],
getBank: Transaction => Bank,
bankTypeString: String
): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(getBank(_).name).map {
case (key, transactionSeq) =>
transactionSeq.map { transaction =>
val bank = getBank(transaction)
GroupedBank(
name = bank.name,
country = bank.country,
countryCode = bank.countryCode,
bankType = bankTypeString)
}
}
}
And then call it like this:
groupedBank(transactionSeq, _.senderBank, "sender")
It could also be a good idea to abstract the bank type concept into a separate trait:
sealed trait BankGroup {
def name: String
def getBank(transaction: Transaction): Bank
def groupBanks(transactionSeq: Seq[Transaction]): Iterable[Seq[GroupedBank]] = {
transactionSeq.groupBy(getBank(_).name).map {
case (key, transactionSeq) =>
transactionSeq.map { transaction =>
val bank = getBank(transaction)
GroupedBank(
name = bank.name,
country = bank.country,
countryCode = bank.countryCode,
bankType = name)
}
}
}
}
object BankGroup {
object Sender extends BankGroup {
def name: String = "sender"
def getBank(transaction: Transaction): Bank = transaction.senderBank
}
object Receiver extends BankGroup {
def name: String = "receiver"
def getBank(transaction: Transaction): Bank = transaction.receiverBank
}
object Intermediary extends BankGroup {
def name: String = "intermediary"
def getBank(transaction: Transaction): Bank = transaction.intermediaryBank
}
val values: Seq[BankGroup] = Seq(Sender, Receiver, Intermediary)
def byName(name: String): BankGroup = values.find(_.name == name)
.getOrElse(sys.error(s"unknown bank type: $name"))
}
And you can call it in one of those ways:
BankGroup.Sender.groupBanks(transactionSeq)
BankGroup.byName("sender").groupBanks(transactionSeq)

How to create entity relationships in slick?

I am working in a project with scala play 2 framework where i am using slick as FRM and postgres database.
In my project customer is an entity. So i create a customer table and customer case class and object also. Another entity is account. The code is given bellow
case class Customer(id: Option[Int],
status: String,
balance: Double,
payable: Double,
created: Option[Instant],
updated: Option[Instant]) extends GenericEntity {
def this(status: String,
balance: Double,
payable: Double) = this(None, status, balance, payable, None, None)
}
class CustomerTable(tag: Tag) extends GenericTable[Customer](tag, "customer"){
override def id = column[Option[Int]]("id")
def status = column[String]("status")
def balance = column[Double]("balance")
def payable = column[Double]("payable")
def account = foreignKey("fk_customer_account", id, Accounts.table)(_.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade)
def * = (id, status, balance, payable, created, updated) <> ((Customer.apply _).tupled, Customer.unapply)
}
object Customers extends GenericService[Customer, CustomerTable] {
override val table = TableQuery[CustomerTable]
val accountTable = TableQuery[AccountTable]
override def copyEntityFields(entity: Customer, id: Option[Int],
created: Option[Instant], updated: Option[Instant]): Customer = {
entity.copy(id = id, created = created, updated = updated)
}
}
Now the problem is how can i create an account object inside the customer object? Or how can i get the account of a customer?
Slick is not ORM its just a functional relational mapper. Nested objects cannot be directly used in slick like Hibernate or any other ORMs Instead
Slick
//Usual ORM mapping works with nested objects
case class Person(name: String, age: Int, address: Address) //nested object
//With Slick
case class Address(addressStr: String, id: Long) //id primary key
case class Person(name: String, age: Int, addressId: Long) //addressId is the id of the address object and its going to the foreign key
Account object in Customer
case class Account(id: Long, ....) // .... means other fields.
case class Customer(accountId: Long, ....)
Place accountId inside the Customer object and make it a foreign key
For more info refer to Slick example provided as part of documentation

How to avoid using null?

Suppose, I have my domain object named "Office":
case class Office(
id: Long,
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
null.asInstanceOf[Long], name, phone, address
)
}
When I create new Office:
new Office("officeName","00000000000", "officeAddress")
I don't specify id field becouse I don't know it. When I save office (by Anorm) I now id and do that:
office.id = officeId
So. I know that using null is non-Scala way. How to avoid using null in my case?
UPDATE #1
Using Option.
Suppose, something like this:
case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}
And, after saving:
office.id = Option(officeId)
But what if I need to find something by office id?
SomeService.findSomethingByOfficeId(office.id.get)
Does it clear? office.id.get looks not so good)
UPDATE #2
Everyone thanks! I've got new ideas from your answers! Greate thanks!
Why not declare the id field as a Option? You should really avoid using null in Scala. Option is preferable since it is type-safe and plays nice with other constructs in the functional paradigm.
Something like (I haven't tested this code):
case class Office(
id: Option[Long],
name: String,
phone: String,
address: String
) {
def this(name: String, phone: String, address: String) = this(
None, name, phone, address
)
}
Just make the id field an Option[Long]; once you have that, you can use it like this:
office.id.map(SomeService.findSomethingByOfficeId)
This will do what you want and return Option[Something]. If office.id is None, map() won't even invoke the finder method and will immediately return None, which is what you want typically.
And if findSomethingByOfficeId returns Option[Something] (which it should) instead of just Something or null/exception, use:
office.id.flatMap(SomeService.findSomethingByOfficeId)
This way, if office.id is None, it will, again, immediately return None; however, if it's Some(123), it will pass that 123 into findSomethingByOfficeId; now if the finder returns a Some(something) it will return Some(something), if however the finder returns None, it will again return None.
if findSomethingByOfficeId can return null and you can't change its source code, wrap any calls to it with Option(...)—that will convert nulls to None and wrap any other values in Some(...); if it can throw an exception when it can't find the something, wrap calls to it with Try(...).toOption to get the same effect (although this will also convert any unrelated exceptions to None, which is probably undesirable, but which you can fix with recoverWith).
The general guideline is always avoid null and exceptions in Scala code (as you stated); always prefer Option[T] with either map or flatMap chaining, or using the monadic for syntactic sugar hiding the use of map and flatMap.
Runnable example:
object OptionDemo extends App {
case class Something(name: String)
case class Office(id: Option[Long])
def findSomethingByOfficeId(officeId: Long) = {
if (officeId == 123) Some(Something("London")) else None
}
val office1 = Office(id = None)
val office2 = Office(id = Some(456))
val office3 = Office(id = Some(123))
println(office1.id.flatMap(findSomethingByOfficeId))
println(office2.id.flatMap(findSomethingByOfficeId))
println(office3.id.flatMap(findSomethingByOfficeId))
}
Output:
None
None
Some(Something(London))
For a great introduction to Scala's rather useful Option[T] type, see http://danielwestheide.com/blog/2012/12/19/the-neophytes-guide-to-scala-part-5-the-option-type.html.
When using id: Option[Long] , extract the option value for instance with
if (office.id.isDefined) {
val Some(id) = office.id
SomeService.findSomethingByOfficeId(id)
}
or perhaps for instance
office.id match {
case None => Array()
case Some(id) => SomeService.findSomethingByOfficeId(id)
}
Also you can define case classes and objects as follows,
trait OId
case object NoId extends OId
case class Id(value: Long) extends OId
case class Office (
id: OId = NoId,
name: String,
phone: String,
address: String
)
Note that by defaulting id for example to NoId , there is no need to declare a call to this. Then
val office = Office (Id(123), "name","phone","addr")
val officeNoId = Office (name = "name",phone="phone",address="addr")
If the id member is defined last, then there is no need to name the member names,
val office = Office ("name","phone","addr")
office: Office = Office(name,phone,addr,NoId)
As of invoking (neatly) a method,
office.id match {
case NoId => Array()
case Id(value) => SomeService.findSomethingByOfficeId(value)
}
I prefer more strong restriction for object Id property:
trait Id[+T] {
class ObjectHasNoIdExcepthion extends Throwable
def id : T = throw new ObjectHasNoIdExcepthion
}
case class Office(
name: String,
phone: String,
address: String
) extends Id[Long]
object Office {
def apply(_id : Long, name: String, phone: String, address: String) =
new Office(name, phone, address) {
override def id : Long = _id
}
}
And if I try to get Id for object what is not stored in DB, I get exception and this mean that something wrong in program behavior.
val officeWithoutId =
Office("officeName","00000000000", "officeAddress")
officeWithoutId.id // Throw exception
// for-comprehension and Try monad can be used
// update office:
for {
id <- Try { officeWithoutId.id }
newOffice = officeWithoutId.copy(name = "newOffice")
} yield OfficeDAL.update(id, newOffice)
// find office by id:
for {
id <- Try { officeWithoutId.id }
} yield OfficeDAL.findById(id)
val officeWithId =
Office(1000L, "officeName","00000000000", "officeAddress")
officeWithId.id // Ok
Pros:
1) method apply with id parameter can be incapsulated in DAL logic
private[dal] def apply (_id : Long, name: String, ...
2) copy method always create new object with empty id (safty if you change data)
3) update method is safety (object not be overridden by default, id always need to be specified)
Cons:
1) Special serealization/deserealization logic needed for store id property (json for webservices, etc)
P.S.
this approach is good if you have immutable object (ADT) and store it to DB with id + object version instead object replace.

Using extractors to parse text files

I'm trying to improve a CSV parsing routine and feel that extractors could be useful here but can't figure them out. Suppose there's a file with user ids and emails:
1,alice#alice.com
2,bob#bob.com
3,carol#carol.com
If the User class is defined as case class User(id: Int, email: String) everything is pretty easy with something like
lines map { line =>
line split "," match {
case Array(id, email) => User(id.toInt, email)
}
}
What I don't understand is how to deal with the case where User class can have complex properties e.g
case class Email(username: String, host: string)
case class User(id: Int, email: Email)
You probably want to use a regular expression to extract the contents of the email address. Maybe something like this:
val lines = Vector(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com")
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val EmailRe = """(.*)#(.*\.com)""".r // substitute a real email address regex here
lines.map { line =>
line.split(",") match {
case Array(id, EmailRe(uname, host)) => User(id.toInt, Email(uname, host))
}
}
Here you go, an example using a custom Extractor.
// 1,Alice,21212,Baltimore,MD" -> User(1, Alice, Address(21212, Baltimore, MD))
Define a custom Extractor that creates the objects out of given String:
object UserExtractor {
def unapply(s: String) : Option[User] = try {
Some( User(s) )
}
catch {
// bettor handling of bad cases
case e: Throwable => None
}
}
Case classes to hold the data with a custom apply on Comapnion object on User:
case class Address(code: String, cit: String, county: String)
case class User(id: Int, name: String, address: Address)
object User {
def apply(s: String) : User = s.split(",") match {
case Array(id, name, code, city, county) => User(id.toInt, name, Address(code, city, county) )
}
}
Unapplying on a valid string (in the example valid means the correct number of fields).
"1,Alice,21212,Baltimore,MD" match { case UserExtractor(u) => u }
res0: User = User(1,Alice,Address(21212,Baltimore,MD))
More tests could be added with more custom apply methods.
I'd use a single RegexExtractor :
val lines = List(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com"
)
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val idAndEMail = """^([^,]+),([^#]+)#(.+)$""".r
and define a function that transforms a line to the an User :
def lineToUserWithMail(line: String) : Option[User] =
idAndEMail.findFirstMatchIn(line) map {
case userWithEmail(id,user,host) => User(id.toInt, Email(user,host) )
}
Applying the function to all lines
lines flatMap lineToUserWithMail
//res29: List[User] = List(User(1,Email(alice,alice.com)), User(2,Email(bob,bob.com)), User(3,Email(carol,carol.com)))
Alternatively you could implement custom Extractors on the case classe by adding an unnapply Method. But for that case it wouldn't be worth the pain.
Here is an example for unapply
class Email(user:String, host:String)
object Email {
def unapply(s: String) : Option[(String,String)] = s.split("#") match {
case Array(user, host) => Some( (user,host) )
case _ => None
}
}
"bob#bob.com" match {
case Email(u,h) => println( s"$u , $h" )
}
// prints bob , bob.com
A word of warning on using Regex to parse CSV-data. It's not as easy as you might think, i would recommend to use a CSV-Reader as http://supercsv.sourceforge.net/ which handles some nasty edge cases out of the box.