Generating column mappings - scala

I'm using the play-slick plugin 3.0.1 with Play! 2.6.6 and seem to have run into a problem.
What I'm trying to do is to implement "grant" based permission system from a DB table containing several columns. Since this table can be broad, I wanted to find a way to programmatically generate column names and read bits from each column, and evaluate to the presence or absence of a grant.
For example, the table might look like:
granted_for grantee read write
id1 id2 1 0
Next I set things up like so:
import centralapp.api.string.CamelToUnderscore
import models.permissions.grants.Grant
import slick.ast.{BaseTypedType, TypedType}
import slick.jdbc
import slick.jdbc.JdbcType
import slick.jdbc.MySQLProfile.api._
import slick.lifted.ProvenShape
trait HasName {
/**
* #note "HasName"
*/
def simpleName: String = getClass.getSimpleName.replaceAll("[^a-zA-Z0-9_]", "")
/**
* #note "has_name"
*/
def classNameLower: String = simpleName.toUnderscoreLower
/**
* #note "HAS_NAME"
*/
def classNameUpper: String = simpleName.toUnderscoreUpper
}
And then a trait that defines a DB column abstraction:
trait DBColumn[C, T <: Table[_]] extends HasName {
/**
* the name of the column
*/
def columnName: String = classNameLower
/**
* get the column representation
*
* #param c the TypedType representation of this column's type
* #param table the table for which the column is being generated
* #return a Rep[C] (representation of type C)
*/
def col(implicit c: TypedType[C], table: T): Rep[C] = table.column[C](columnName)
}
And then use that to generate "Grant" column mappings:
package object db {
/**
* creates a grant column mapping from a boolean column for a given grant
*
* #param g the grant to obtain if the column is set to "true"
* #tparam G the subtype of the grant
* #return the JDBC type for the desired value
*/
def optGrantFromBoolCol[G <: Grant](g: G): JdbcType[Option[G]] with BaseTypedType[Option[G]] = MappedColumnType.base[Option[G], Boolean](
_.isDefined,
{
case true => Some(g)
case false => None
}
)
abstract class GrantCol[G <: Grant, T <: Table[_]](grant: G) extends DBColumn[Option[G], T] {
// class Grant also extends HasName; here I want to ensure the column name is exactly the same as the string representation of the grant.
override def columnName: String = grant.classNameLower
/**
* get the column representation
*
* #param table the table for which the column is being generated
* #return a Rep[C] (representation of type C)
*/
def grantCol(implicit table: T): jdbc.MySQLProfile.api.Rep[Option[G]] = col(optGrantFromBoolCol(grant), table)
}
}
And then I tried to use this like so:
object Columns {
case object GrantedFor extends DBColumn[String, MyTable]
case object Grantee extends DBColumn[String, MyTable]
case object Read extends GrantCol[UserGrant, MyTable](usergrants.Read)
}
// and then used it in a table:
class MyTable(tag: Tag) extends Table[SomeClass](tag, "my_table") {
implicit def table = this
val grantedFor = Columns.GrantedFor.col
val grantee = Columns.Grantee.col
val read = Columns.Read.grantCol
def * : ProvenShape[SomeClass] =
(grantedFor, grantee, read) <> // <- this is the problem: Cannot resolve symbol "<>"
(
???, // valid constructor from tuple
??? // valid unapply
)
def other : ProvenShape[OtherClass] =
(grantedFor, grantee) <> (
???,
???
)
}
As described in the comments, I see that the moment there's a "Grant" column in the tuple, the resolution for the <> symbol starts failing. I cannot seem to see what the problem here could be.
Any pointers?

Related

Update and delete queries not working in play-slick

Iam trying to write a simple CRUD application for play-slick. I can Insert into the database and display all users, but I cannot update or delete users. My entire code is below
package models
import javax.inject.{ Inject, Singleton }
import play.api.db.slick.DatabaseConfigProvider
import slick.jdbc.JdbcProfile
import scala.concurrent.{ Future, ExecutionContext }
/**
* A repository for people.
*
* #param dbConfigProvider The Play db config provider. Play will inject this for you.
*/
#Singleton
class PersonRepository #Inject() (dbConfigProvider: DatabaseConfigProvider)(implicit ec: ExecutionContext) {
// We want the JdbcProfile for this provider
private val dbConfig = dbConfigProvider.get[JdbcProfile]
// These imports are important, the first one brings db into scope, which will let you do the actual db operations.
// The second one brings the Slick DSL into scope, which lets you define the table and other queries.
import dbConfig._
import profile.api._
/**
* Here we define the table. It will have a name of people
*/
private class PeopleTable(tag: Tag) extends Table[Person](tag, "people") {
/** The ID column, which is the primary key, and auto incremented */
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
/** The name column */
def name = column[String]("name")
/** The age column */
def age = column[Int]("age")
/**
* This is the tables default "projection".
*
* It defines how the columns are converted to and from the Person object.
*
* In this case, we are simply passing the id, name and page parameters to the Person case classes
* apply and unapply methods.
*/
def * = (id, name, age) <> ((Person.apply _).tupled, Person.unapply)
}
/**
* The starting point for all queries on the people table.
*/
private val people = TableQuery[PeopleTable]
/**
* Create a person with the given name and age.
*
* This is an asynchronous operation, it will return a future of the created person, which can be used to obtain the
* id for that person.
*/
def create(name: String, age: Int): Future[Person] = db.run {
// We create a projection of just the name and age columns, since we're not inserting a value for the id column
(people.map(p => (p.name, p.age))
// Now define it to return the id, because we want to know what id was generated for the person
returning people.map(_.id)
// And we define a transformation for the returned value, which combines our original parameters with the
// returned id
into ((nameAge, id) => Person(id, nameAge._1, nameAge._2))
// And finally, insert the person into the database
) += (name, age)
}
/**
* List all the people in the database.
*/
def list(): Future[Seq[Person]] = db.run {
people.result
}
def del(id: Int) : Future[Seq[Person]] = db.run {
people.filter(_.id === id).delete
}
def update (id: Int, name: String) : Future[Seq[Person]] = {
db.run(people.filter(_.id === id).update("john"))
}
}
It might also be possible that I have excluded some imports, so please check to see if i need to import anything.
I also get this error and I don't know what it means:
type mismatch; found :
UserData.this.dbConfig.profile.ProfileAction[Int,slick.dbio.NoStream,slick.dbio.Effect.Write]
(which expands to)
slick.sql.FixedSqlAction[Int,slick.dbio.NoStream,slick.dbio.Effect.Write]
required:
slick.dbio.DBIOAction[Seq[models.User],slick.dbio.NoStream,Nothing]
I think you are having a problem with the return types when you delete or update.
AFAIK they return the number of rows that have been modified.
Try this:
def del(id: Int) : Future[Int] = db.run {
people.filter(_.id === id).delete
}
def update (id: Int, name: String) : Future[Int] = {
db.run(people.filter(_.id === id).update("john"))
}

Scala implicit class

I am seeing some unexpected behavior with an implicit class. I have a case class that is retrieved from Mongo, and in certain cases wrapped into an implicit class with convenience lazy vals to retrieve data from other relevant collections. Code looks something like
case class MyClass(
_id: UUID, ...,
//some fields
)
and then
object MyImplicits {
implicit class MyRichClass(c: MyClass){
lazy val field1: Future[Iterable[T1]] = db.findAll[T1](bson)
lazy val field2: Future[Iterable[T2]] = db.findAll[T2](bson)
}
}
and the conversion is used like
import ...MyImplicits.MyRichClass
val c: MyClass...
c.field1.map(...)
My problem is Futures in field1 and field2 always contain an empty Iterable. I have spent best part of today debugging my queries, and they work correctly both in test code and Mongo cli.
Any tips would be greatly appreciated.
I have a wrapper around org.mongodb.scala.MongoClient, that has:
/**
* Finds all instances of T using raw mongo query
*
* #param bson search query
* #tparam T
* #return
*/
private[service] def findAll[T: ClassTag](bson: Bson): Future[Iterable[T]] = {
val colName = resolveCollection[T]
logger.info("colName = " + colName)
db.getCollection[T](colName).find(equal("ownerId", "user1")).toFuture().map(t => {
logger.info("t = " + t)
t
})
}
/**
* Returns collection name for given type
* #tparam T
* #return
*/
private def resolveCollection[T: ClassTag]: String = {
scala.reflect.classTag[T] match {
case `...tag` => "collectionName"
....
}
}
The
equal("ownerId", "user1")
is hardcoded for debugging purposes, as well as extra loggers.
There is actually no issues with the code. I was literally querying a wrong database, so, yes - all collections were in fact empty.

too many arguments for method apply: (car: play.api.data.Form[models.CarroFormData])

I have this error bellow and can't find the solution or how to debug what's being passed to apply.
Can anyone help?
too many arguments for method apply: (car:
play.api.data.Form[models.CarroFormData])(implicit messages:
play.api.i18n.Messages)play.twirl.api.HtmlFormat.Appendable in class
index
Controller Form
def add = Action { implicit request =>
CarroForm.form.bindFromRequest.fold(
// if any error in submitted data
errorForm => Ok(views.html.admin.index(errorForm, Seq.empty[Carro])),
data => repo.create(carro.name, carro.description, carro.img, carro.keywords).map { _ =>
// If successful, we simply redirect to the index page.
Redirect(routes.application.index)
})
}
This is the Model
package dal
import javax.inject.{ Inject, Singleton }
import play.api.db.slick.DatabaseConfigProvider
import slick.driver.JdbcProfile
import models._
import scala.concurrent.{ Future, ExecutionContext }
/**
* A repository for people.
*
* #param dbConfigProvider The Play db config provider. Play will inject this for you.
*/
#Singleton
class CarroRepository #Inject() (dbConfigProvider: DatabaseConfigProvider)(implicit ec: ExecutionContext) {
// We want the JdbcProfile for this provider
private val dbConfig = dbConfigProvider.get[JdbcProfile]
// These imports are important, the first one brings db into scope, which will let you do the actual db operations.
// The second one brings the Slick DSL into scope, which lets you define the table and other queries.
import dbConfig._
import driver.api._
/**
* Here we define the table. It will have a name of people
*/
private class CarroTable(tag: Tag) extends Table[Carro](tag, "carro") {
/** The ID column, which is the primary key, and auto incremented */
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
/** The name column */
def name = column[String]("name")
/** The description column */
def description = column[String]("description")
/** The img column */
def img = column[String]("img")
/** The keywords column */
def keywords = column[String]("keywords")
/**
* This is the tables default "projection".
*
* It defines how the columns are converted to and from the Person object.
*
* In this case, we are simply passing the id, name and page parameters to the Person case classes
* apply and unapply methods.
*/
def * = (id, name, description, img, keywords) <> ((Carro.apply _).tupled, Carro.unapply)
}
/**
* The starting point for all queries on the people table.
*/
private val carro = TableQuery[CarroTable]
/**
* Create a person with the given name and age.
*
* This is an asynchronous operation, it will return a future of the created person, which can be used to obtain the
* id for that person.
*/
def create(name: String, description: String, img:String, keywords: String): Future[Carro] = db.run {
// We create a projection of just the name and age columns, since we're not inserting a value for the id column
(carro.map(p => (p.name, p.description, p.img, p.keywords))
// Now define it to return the id, because we want to know what id was generated for the person
returning carro.map(_.id)
// And we define a transformation for the returned value, which combines our original parameters with the
// returned id
into ((nameAge, id) => Carro(id, nameAge._1, nameAge._2, nameAge._3, nameAge._4))
// And finally, insert the person into the database
) += (name, description, img, keywords)
}
/**
* List all the people in the database.
*/
def list(): Future[Seq[Carro]] = db.run {
carro.result
}
}
package models
import play.api.data.Form
import play.api.data.Forms._
import play.api.libs.json._
case class Carro(id: Long, name:String, description:String, img:String, keywords:String)
case class CarroFormData(name: String, description: String, img: String, keywords: String)
object CarroForm {
val form = Form(
mapping(
"name" -> nonEmptyText,
"description" -> nonEmptyText,
"img" -> nonEmptyText,
"keywords" -> nonEmptyText
)(CarroFormData.apply)(CarroFormData.unapply)
)
}
object Carros {
var carros: Seq[Carro] = Seq()
def add(carros: Carro): String = {
carros = carros :+ carro.copy(id = carro.length) // manual id increment
"User successfully added"
}
def delete(id: Long): Option[Int] = {
val originalSize = carro.length
carro = carro.filterNot(_.id == id)
Some(originalSize - carro.length) // returning the number of deleted users
}
def get(id: Long): Option[Carro] = carro.find(_.id == id)
def listAll: Seq[Carro] = carro
implicit val carroFormat = Json.format[Carro]
}
View Code
#(car: Form[CarroFormData])(implicit messages: Messages)
#import helper._
#main(new Main("Car Dealers", "Compra e venda de carros", "logo.png", "carro, compra, venda")) {
<div class="container">
<h1>Hello</h1>
#form(routes.AdminCarro.add()) {
#inputText(person("name"))
#inputText(person("description"))
#inputText(person("img"))
#inputText(person("keywords"))
)
<div class="buttons">
<input type="submit" value="Add Car"/>
</div>
}
</div>
}
At your controller code:
def add = Action { implicit request =>
CarroForm.form.bindFromRequest.fold(
// if any error in submitted data
errorForm => Ok(views.html.admin.index(errorForm, Seq.empty[Carro])),
data => repo.create(carro.name, carro.description, carro.img, carro.keywords).map { _ =>
// If successful, we simply redirect to the index page.
Redirect(routes.application.index)
}
)
}
At the errorForm, you are calling the index view with two arguments:
Ok(views.html.admin.index(errorForm, Seq.empty[Carro]))
But your view declares just one argument:
#(car: Form[CarroFormData])(implicit messages: Messages)
Just remove the Seq.empty[Carro] from the call in your controller and everything should work as expected. If you still getting the same error, see if there are other places where this view is called the same wrong way (with two arguments) or try to sbt clean your project before sbt run.

Shows too many arguments for method

I am trying to insert few data in apple_reportDB but I get the error as Too many arguments for method in the insert query
import scala.slick.driver.MysqlDriver.simple._
case class AppleReport(quantity:String,price:String,sale_amount:String)
//tables
class Suppliers(tag: Tag)
extends Table[AppleReport](tag, "apple_report") {
def quantity=column[String]("quantity")
def price=column[String]("price")
def sale_amount=column[String]("sale_amount")
def * =(quantity,price,sale_amount) <> (AffiliateReportFields.tupled,AffiliateReportFields.unapply)
}
//declaring interface
val suppliers: TableQuery[Suppliers] = TableQuery[Suppliers]
val db=Database.forURL("jdbc:mysql://localhost:3306/apple_reportDB","root","",null, driver="com.mysql.jdbc.Driver")
db.withSession { implicit session =>
//create table
suppliers.ddl.create
//Insert data
suppliers += ("apple","cow","cat")
}
Your Suppliers table extends Table[AppleReport]. Hence your insert statement expects a single object of case class AppleReport.
However you are calling the method with 3 Strings ("apple","cow","cat") and so the error. Change it to AppleReport("apple","cow","cat") and your code will work

Scala: class type required but T found

I've found similar issues of this particular problem, however the problem was due to someone trying to instantiate T directly. Here I'm trying to create a trait that is a general interface to extend classes and store them automatically in a database such as Riak using classOf[T]. Using Scala 2.10.
Here's my code:
trait RiakWriteable[T] {
/**
* bucket name of data in Riak holding class data
*/
def bucketName: String
/**
* determine whether secondary indices will be added
*/
def enable2i: Boolean
/**
* the actual bucket
*/
val bucket: Bucket = enable2i match {
case true => DB.client.createBucket(bucketName).enableForSearch().execute()
case false => DB.client.createBucket(bucketName).disableSearch().execute()
}
/**
* register the scala module for Jackson
*/
val converter = {
val c = new JSONConverter[T](classOf[T], bucketName)
JSONConverter.registerJacksonModule(DefaultScalaModule)
c
}
/**
* store operation
*/
def store = bucket.store(this).withConverter(converter).withRetrier(DB.retrier).execute()
/**
* fetch operation
*/
def fetch(id: String): Option[T] = {
val u = bucket.fetch(id, classOf[T]).withConverter(converter).withRetrier(DB.retrier).r(DB.N_READ).execute()
u match {
case null => None
case _ => Some(u)
}
}
}
Compiler error is class type required but T found.
Example usage (pseudo-code):
class Foo
object Foo extends RiakWriteable[Foo]
Foo.store(object)
So I'm guessing that a manifest of T is not being properly defined. Do I need to implicitly define this somewhere?
Thanks!
Here's an intermediary solution, though it leaves out the converter registration (which I may leave permanently for this use case, not sure yet).
/**
* trait for adding write methods to classes
*/
trait RiakWriteable[T] {
/**
* bucket name of data in Riak holding class data
*/
def bucketName: String
/**
* determine whether secondary indices will be added
*/
def enable2i: Boolean
/**
* the actual bucket
*/
val bucket: Bucket = enable2i match {
case true => DB.client.createBucket(bucketName).enableForSearch().execute()
case false => DB.client.createBucket(bucketName).disableSearch().execute()
}
/**
* store operation
*/
def store(o: T) = bucket.store(o).withRetrier(DB.retrier).execute()
/**
* fetch operation
*/
def fetch(id: String)(implicit m: ClassTag[T]) = {
val u = bucket.fetch(id, classTag[T].runtimeClass).withRetrier(DB.retrier).r(DB.N_READ).execute()
u match {
case null => None
case _ => Some(u)
}
}
}