I'm having trouble using Amazon's DynamoDBMapper in Scala code. The main sticking point is getting the JVM to recognize #DynamoDBHashkey when it is used in a case class, like:
case class MyCoolCaseClass(#DynamoDBHashKey(attributeName = "my_id") myId: String) {}
Any pointers from someone who has integrated this client library into a Scala project? (I'm hoping to not simply fallback to the low-level API, though that may be a decent decision once exhausting my options with the Mapper).
I had to do this:
import annotation.meta.beanGetter
import beans.BeanProperty
import com.amazonaws.services.dynamodbv2.datamodeling._
#DynamoDBTable(tableName="DEMOTAB")
case class DemoItem( // it's a case class for the free stuff, but can be ordinary class
#(DynamoDBHashKey #beanGetter) // would not work without meta annotation
#BeanProperty var id:String, // must be var or mapper can't instantiate one
#BeanProperty var number:Integer
) {
def this() = this(null, null) // needed by DynamoDB Mapper to instantiate
}
The DynamoDB mapper uses reflection to find the getters and setters. The SDK assumes Java-style conventions, i.e. that your getters and setters start with "get" or "is", and setters start with "set". You can see the reflection code on github.
I've been able to get it working, but it feels just like writing Java :(
#DynamoDBTable(tableName = "awesome_table")
class TheBestClass {
private var id : Integer = _
#DynamoDBHashKey
def getId() = id
def setId(_id: Integer) = id = _id
}
This works for me, including the boolean
#DynamoDBTable(tableName = "User")
case class User(
#(DynamoDBHashKey #field)
#(DynamoDBAutoGeneratedKey #field)
#BeanProperty var id: String,
#(DynamoDBAttribute #field)
#BeanProperty var firstName: String,
#(DynamoDBAttribute #field)
#BeanProperty var lastName: String,
#(DynamoDBAttribute #field)
#BeanProperty var active: Boolean
)
{
def this() = this(null, null, null, false)
}
Related
Scala does not get first class support as Kotlin in Spring.
I tried to create a Spring Boot API application with Scala.
Spring Boot 2.2.0.M5
Spring Data JPA
H2
Scala 2.13
I created a JPA Entity with case class like:
#Entity
case class Post(#BeanProperty title: String, #BeanProperty content: String) {
def this() {
this(null, null)
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#BeanProperty
var id: Long = _
#BeanProperty
val createdOn: LocalDateTime = LocalDateTime.now()
override def toString: String = s"Post[id:$id, title:$title, content:$content, createdOn:$createdOn]"
}
And create a Repository using trait, it works.
trait PostRepository extends JpaRepository[Post, Long]
I want to try bean validation.
class PostForm {
#NotNull
#NotEmpty
#BeanProperty var title: String = _
#BeanProperty var content: String = _
}
And in the controller, create a POST method like:
#PostMapping
def save(#RequestBody #Valid form: PostForm, errors: BindingResult) = errors.hasErrors match {
case true => {
badRequest().build()
}
case _ => {
val data = Post(title = form.title, content = form.content)
val saved = posts.save(data)
created(ServletUriComponentsBuilder.fromCurrentContextPath().path("/{id}").buildAndExpand(saved.id).toUri).build()
}
}
It works.
But the model classes are little tedious. I am trying to use a case class like the following:
case class PostForm(#NotNull #NotEmpty #BeanProperty title: String, #BeanProperty content: String)
The validation does not work.
When we are modeling for JPA etc, case class or generic class is better?
Why we can not apply the Bean Validation annotations as Kotlin data clase in the case class?
Update: Got this work like:
case class PostForm(#(NotNull#field) #(NotEmpty#field) #BeanProperty title: String, #BeanProperty content: String)
The source codes is hosted on my Github.
Case class fields are considered as vals by default, which means you can't set a new value to them. #BeanProperty, however, is to automatically generate field setters and getters.
You may try adding var keywords to the fields explicitly.
case class PostForm(
#NotNull #NotEmpty #BeanProperty var title: String,
#BeanProperty var content: String
)
I'm fairly new to Scala and I have a question about the best way to copy a case class while preserving data that comes from traits. For example, let's say I have the following:
trait Auditing {
var createTime: Timestamp = new Timestamp(System.currentTimeMillis)
}
case class User(val userName: String, val email: String) extends Auditing
val user = User("Joe", "joe#blah.com")
Then I want to make a new copy with one parameter changed:
val user2 = user.copy(email = "joe#newemail.com")
Now, in the example above, the property createTime does not get copied over because it is not defined in the constructor of the User case class. So my question is: assuming that moving createTime into the constructor is not an option, what is the best way for getting a copy of the User object that includes the value from the trait?
I'm using Scala 2.9.1
Thanks in advance!
Joe
You can override the copy method with that behavior.
case class User(val userName: String, val email: String) extends Auditing
{
def copy(userName = this.userName, email = this.email) {
val copiedUser = User(userName, email)
copiedUser.createTime = createTime
copiedUser
}
}
While I see no other solution than Reuben's, I don't understand the requirement to leave the constructor args untouched. This would be the most natural solution:
case class User(userName: String, email: String,
override val createTime:Timestamp = new Timestamp(System.currentTimeMillis))
extends Auditing
If you don't want the user to be able to overwrite createTime, you can still use:
case class User private (userName: String, email: String,
override val createTime:Timestamp) extends Auditing {
def this(userName: String, email: String) =
this(userName, email, new Timestamp(System.currentTimeMillis))
}
The only drawback is that you need to write new User("Joe", "joe#blah.com"), as the primary constructor is now private.
You might be better of not using a case class. You can easily implement the
functionality you need yourself. The below code implements the copy method you wanted, a constructor without new, hides the original constructor, and creates an extractor so that you can use User in case statements.
class User private(val userName: String,
val email: String,
val timeStamp: Timestamp =
new Timestamp(System.currentTimeMillis)) {
def copy(uName: String = userName,
eMail: String = email) =
new User(uName, eMail, timeStamp)
}
object User {
def apply(userName: String, email: String) =
new User(userName, email)
def unapply(u: User) = Some((u.userName, u.email, u.timeStamp))
}
Im using Scala, Squeryl and MySql to build a web app.
I found it easy to persist simple data as strings or integers. But what about when i have relations between objects and i need to use foreign keys. In my app i have areas, and sub areas which have an attribute of type Area (Area where they belong) so my Area and Subarea are like these
class Area(
var idArea: String,
#BeanProperty
var name:String,
#BeanProperty
var letter: String,
#BeanProperty
var color: String
)
extends Idable {
def this() = this("","", "","")
}
class SubArea(var idSubArea: String,
#BeanProperty
var name: String,
#BeanProperty
var area:Area
) extends Idable {
def this() = this("","",null )
How do i define the schema, so my SubArea table has an Area id, foreign key to my Area Table??
For the time being my SubArea schema is like these
object SubAreaSchema extends Schema {
val subAreas=table[SubArea]
on(subAreas)(subArea => declare(
subArea.id is (autoIncremented),
subArea.name is (unique)
))
}
You can define the relation in your schema with:
val areaToSubAreas = oneToManyRelation(areas, subAreas).via((a,sA) => s.idArea === sa.areaId)
To make that work, you would want to modify your SubArea class load the foreign key's id directly, as below with the areaId:String.
class SubArea(var idSubArea: String,
#BeanProperty
var name: String,
#BeanProperty
var areaId: String)
and then in the method body, if you want to have access to the object, you can use:
def area = areaToSubAreas.right(this)
which will yield an ManyToOne[Area] which you query, or use headOption on to convert to an Option[Area].
Conversely, if you need to reference the subareas on Area, you can use:
def subAreas = areaToSubAreas.left(this)
which will yield an OneToMany[Area] which can be iterated over, or you can also call toList.
Is it possible to implement BeanProperty for Optional variables? It would be useful with JPA.
It would be great if:
#BeanProperty var status: Option[String]
would add the following methods to the class:
def setStatus(s: String) { status = Some(s) }
def getStatus: String = status.get
Unfortunately not, but there is a simple workaround that brings best of both worlds:
#BeanProperty
var status: String
def statusOption = Option(status)
Note that JPA does not understand Option[T]. BTW if you use field-access as opposed to getter/setter access in JPA, #BeanProperty isn't even needed - the JPA provider will scan Java fields instead.
Is it possible to use named arguments in a Scala constructor, and later on override getters and setters without breaking the constructor interface or making the code extremely ugly?
Take the following bit of scala code
class Person( var FirstName: String, var LastName: String )
Nice and clean. This would create a simple class called person, which we could use in the following way
val john = new Person( FirstName="John", LastName="Doe" )
john.FirstName = "Joe"
println( john.FirstName )
Later, we decide we want to add some validation to the FirstName setter. As such, we create a new private local variable and override the getter and setter methods
class Person( var _FirstName: String, var _LastName: String ) {
def FirstName = _FirstName
def FirstName_= (value:String) = _FirstName = value
}
Still somewhat clean, however in order to do this, we've had to change the constructor argument names, thus breaking the external interface.
The first solution to this problem I came up with was
class Person {
var _FirstName:String = null
var LastName:String = null
def FirstName = _FirstName
def FirstName_= (value:String) = _FirstName = value
def this( FirstName: String, LastName: String ){
this()
this._FirstName = FirstName
this.LastName = LastName
}
}
Which is somewhat ugly and inelegant, and removes most of the nice reasons I was using scala in the first place.
Is there a better way of doing this?
tl;dr How to override getters/setters for members defined in the default constructor without making the code ugly or changing the public interface?
Did you consider using an companion object?
class Person private (f: String, l: String ) {
var FirstName = f
var LastName = l
}
object Person {
def apply(FirstName:String, LastName:String) =
new Person(FirstName, LastName)
}
If you're not already using implicit conversions to create the arguments, you can do something like this:
def validateName(s: String) = {
if (s.length>0 && s(0).isUpper) s
else throw new IllegalArgumentException(s+" is not a name!")
}
object Example {
private[Example] class ValidatedName(val s: String) { }
class Person(var firstName: ValidatedName, var lastName: String) { }
implicit def string2valid(s: String) = new ValidatedName(validateName(s))
implicit def valid2string(v: ValidatedName) = v.s
}
scala> new Example.Person("Joe","Schmoe")
res17: Example.Person = Example$Person#51887dd5
scala> new Example.Person("ee","cummings")
java.lang.IllegalArgumentException: ee is not a name!
It's not binary compatible, but it is source compatible (again, if the names weren't already relying upon implicit conversions).
Another slightly longer possibility is to create a stealth ancestor:
class CheckedPerson(private var first: String, var lastName: String) {
def firstName = first
def firstName_=(s: String) { first = validateName(s) }
}
class Person(firstName: String, lastName: String) extends
CheckedPerson(validateName(firstName),lastName) { }
for which I'm not sure about binary compatibility, but will definitely give source compatibility.
No. There is currently no way to do that, it's currently not the focus of research.
It is one of my major pet peeves I have with the language: There is no sensible way to combine constructor arguments and self-defined getter/setter methods.
If you're not happy with the functionality class Person( var FirstName: String, var LastName: String ) provides, it basically means "back to Java's verboseness".