How to retrieve data from many to many relationship with Lift - lift

Let say I have 3 tables: Book, Author and BookAuthor.
Book has id, title
Author has id, name
BookAuthor has id, book_id, author_id
I want to find all authors of a book. Can anyone guide me how to do this with Lifts mapper syntax?

class Book extends LongKeyedMapper[Book]
with IdPK
with ManyToMany {
def getSingleton = Book
object title extends MappedString(this, 255)
object authors extends MappedManyToMany(
BookAuthors, BookAuthors.book, BookAuthors.author, Author)
}
object Book extends Book with LongKeyedMetaMapper[Book]
class Author extends LongKeyedMapper[Author]
with CreatedUpdated with IdPK
with ManyToMany {
def getSingleton = Author
object firstName extends MappedString(this, 255)
object lastName extends MappedText(this)
object email extends MappedEmail(this, 150)
object books extends MappedManyToMany( BookAuthors,
BookAuthors.author, BookAuthors.book, Book)
}
object Author extends Author with LongKeyedMetaMapper[Author]
val warandpeace = Book.create.title("War and Peace").saveMe
val fred = Author.create.firstName("Fred").saveMe
fred.books += warandpeace
fred.saveMe
val bob = Author.create.firstName("Bob").saveMe
bob.books += warandpeace
bob.saveMe
// then to find all the authors of the book:
val authors = warandpeace.authors

Here is the mapper for the book:
class Book extends LongKeyedMapper[Book] with IdPk with OneToMany[Long, Book] {
def getSingleton = Book
object title extends MappedString(this, 200)
object BookAuthor extends MappedOneToMany(BookAuthor, BookAuthor.id)
}
object Book extends Book with LongKeyedMetaMapper[Book]
The trait IdPk will take care of the id of Book. Then for BookAuthor:
class BookAuthor extends LongKeyedMapper[BookAuthor] with IdPk with OneToOne[Long, BookAuthor] {
def getSingleton = BookAuthor
object Author extends MappedOneToOne(Author, Author.id)
}
object BookAuthor extends BookAuthor with LongKeyedMetaMapper[BookAuthor]
Then for Author, a simple mapper:
class Author extends LongKeyedMapper[Author] with IdPk {
def getSingleton = Author
object name extends MappedString(this, 200)
}
object Author extends Author with LongKeyedMetaMapper[Author]
Then the call to find all authors of a book (here myBook ):
myBook.BookAuthor.map(x => x.Author.name)
If you want to make more complicated join requests without having to filter everything in Scala, you can always use DB and you can always find more info about mapper here.

Related

Slick schema/design guidelines

Suppose I have such schema (simplified), using slick 2.1:
// Banks
case class Bank(id: Int, name: String)
class Banks(tag: Tag) extends Table[Bank](tag, "banks") {
def id = column[Int]("id", O.PrimaryKey)
def name = column[String]("name")
def * = (id, name) <> (Bank.tupled, Bank.unapply)
}
lazy val banks = TableQuery[Banks]
Each bank has, say, 1:1 BankInfo, which I keep in separate table:
// Bank Info
case class BankInfo(bank_id: Int, ...)
class BankInfos(tag: Tag) extends Table[BankInfo](tag, "bank_infos") {
def bankId = column[Int]("bank_id", O.PrimaryKey)
...
}
lazy val bankInfos = TableQuery[BankInfos]
And each bank has associated 1:M BankItems:
// Bank Item
case class BankItem(id: Int, bank_id: Int, ...)
class BankItems(tag: Tag) extends Table[BankItem](tag, "bank_items") {
def id = column[Int]("id", O.PrimaryKey)
def bankId = column[Int]("bank_id")
...
}
lazy val bankItems = TableQuery[BankItems]
So, if I used ORM, I would have had convenient accessors for associated data, something like bank.info or bank.items. I've read "Migrating from ORMs", and I understand that Slick doesn't support full relation mapping, though I've seen example where foreignKey was used.
Basically, where should I place my code to access related data (I want to access all BankItems for some Bank, and its BankInfo). Should it be implemented in case classes, or in Table classes, or elsewhere? Can someone give me a practical advice on what is a "standard practice" in this case?

Convenient way of inserting values to Cassandra with Phantom

Does anyone know of a convenient way of inserting values to Cassandra via phatom-dsl? Currently I'm doing this:
case class Entry(id: UUID, firstName: String, lastName: String)
sealed class EntryTable extends CassandraTable[EntryTable, Entry] {
override val tableName = "entries"
object id extends UUIDColumn(this) with Index[UUID]
object firstName extends StringColumn(this)
object lastName extends StringColumn(this)
override def fromRow(r: dsl.Row): Entry = {
Entry(id(r), firstName(r), firstName(r))
}
}
object EntryTable extends EntryTable {
private val connector = CassandraConnector.apply(Set(InetAddress.getByName("localhost")))
implicit val keySpace = KeySpace("keyspace")
def insert(e: Entry) = {
connector.withSessionDo(implicit session => {
insert().value(_.id, e.id)).value(_.firstName, e.firstName).value(_.lastName, e.lastName).future()
}
}
}
But I would like to do:
def insert(e: Entry) = {
connector.withSessionDo(implicit session => {
insert().value(e).future()
}
}
Which would be way more convenient when the case class has many fields. Any ideas?
You are using the API slightly wrong and we are in the process of publishing multiple tutorials to make the "new" way public. In the mean time, a basic version of it is available here and this branch in the activator template is also describing everything you need to know.
Specifically, the way to insert records is described here.

In Lift with MongoDB, storing lists of heterogeneous data

I need to make a web service that can will contain lists of objects. One list can contain objects of many types. Here, for example, I have a library of media items. Each item can be either a link or a video, each with their own metadata.
I want to do this with the Lift web framework, because I need something that compiles to WAR and I've used Lift before.
I thought that using MongoDB for this sort of storage would work as, by definition, it's supposed to be able to handle collections of heterogeneous items.
I can define types of BSON objects to be stored in Lift records, but I can't seem to stick away from creating only one type of object in one record/collection. Ideally, I'd like each "thing" (for lack of a better word) in my Library to be either a video or a link. For example:
[
{
"type" : "Video",
"title" : "Story",
"videoID" : "123ab4",
"description": "Feature-length epic",
"time" : 12.6
},
{
"type" : "link",
"url" : "http://www.google.com",
"title": "Search for stuff"
}
]
I should be able to do it with the right type of inheritance, but the way all of the record objects's parents inherit from the object throws me off. Can I get this to work? Having a collection of different things that Lift can use as such?
Here's what I have so far. I haven't tested it, but even if it works what it does is NOT what I want.
import net.liftweb.record._
import net.liftweb.record.field._
import net.liftweb.mongodb._
import net.liftweb.mongodb.record._
import net.liftweb.mongodb.record.field._
class VideoShelf private () extends BsonRecord[VideoShelf] {
def meta = VideoShelf
object title extends StringField (this, 256)
object videoID extends StringField (this, 32 )
object description extends StringField (this, 256)
object time extends DecimalField(this, 0 )
}
object VideoShelf extends VideoShelf with BsonMetaRecord[VideoShelf]
class LinkShelf private () extends BsonRecord[LinkShelf] {
def meta = LinkShelf
object url extends StringField(this, 128)
object title extends StringField(this, 256)
}
object LinkShelf extends LinkShelf with BsonMetaRecord[LinkShelf]
class MediaLibrary private () extends MongoRecord[MediaLibrary] with ObjectIdPk[MediaLibrary] {
def meta = MediaLibrary
///////////////////////////////////////
///////////////////////////////////////
// What I want is this record type to
// contain either of these:
///////////////////////////////////////
object videoshelf extends BsonRecordField(this, VideoShelf)
object linkshelf extends BsonRecordField(this, LinkShelf )
}
object MediaLibrary extends MediaLibrary with MongoMetaRecord[MediaLibrary]
How can I get this?
After seeking more, I found this post: https://groups.google.com/forum/#!topic/liftweb/LmkhvDgrgrI
That led me to this conclusion, which I think is correct, though it has not been tested yet. I may be missing some pieces for it to fully work.
import net.liftweb.record._
import net.liftweb.record.field._
import net.liftweb.mongodb._
import net.liftweb.mongodb.record._
import net.liftweb.mongodb.record.field._
/**
* The base record type for library objects.
*/
trait MediaLibrary[T <: MediaLibrary[T]] extends MongoRecord[T] with ObjectIdPk[T] {
self: T =>
}
/**
* Items in the library that are videos.
*/
class VideoItem extends MediaLibrary[VideoItem] {
def meta = VideoItem
object title extends StringField (this, 256)
object videoID extends StringField (this, 32 )
object description extends StringField (this, 256)
object time extends DecimalField(this, 0 )
}
object VideoItem extends VideoItem with MongoMetaRecord[VideoItem]
/**
* Items in the library that are links.
*/
class LinkItem extends MediaLibrary[LinkItem] {
def meta = LinkItem
object url extends StringField (this, 256)
object title extends StringField (this, 256)
}
object LinkItem extends LinkItem with MongoMetaRecord[LinkItem]
UPDATE
I've also just found out that there is a MongoDB-specific record that's a list of case classes. That seems to be exactly what I need! It's the power of Scala and Mongo being used hand in hand! That's what I wanted from the start. I'll have to try that tomorrow.

MongoDB field name in Lift's Record Field

I have a pre-existing database, and this code:
class User private() extends MongoRecord[User] with StringPk[User] {
def meta = User
object createdAt extends DateTimeField(this)
object lastLogin extends DateTimeField(this)
object password extends StringField(this, 128)
object salt extends StringField(this, 128)
}
object User extends User with MongoMetaRecord[User]
The problem is, in my database, the field createdAt is called created-at, and similar with lastLogin. Looking into the docs, there are several object fields that could be doing the job (label, name, title, uniqueFieldId), so which one would it be?
The member name seems to be the one looking for. As in
object createdAt extends DateTimeField(this) {
def name = "created-at"
}

How would you implement a self relation for the MegaProtoUser?

I'm trying to implement a a relation for the user class modelling the "friends" concept with the Lift Mapper framework. My attempt goes something like this
object User extends User with MetaMegaProtoUser[User] {
override def dbTableName = "users" // define the DB table name
override def screenWrap = Full(<lift:surround with="default" at="content">
<lift:bind /></lift:surround>)
// define the order fields will appear in forms and output
override def fieldOrder = List(id, firstName, lastName, email,
locale, timezone, password)
// comment this line out to require email validations
override def skipEmailValidation = true
}
class User extends MegaProtoUser[User] with OneToMany[Long,User]
{
def getSingleton = User // what's the "meta" server
object friends extends MappedOneToMany(User, User.id)
}
but it fails to compile because of a type mismatch
[error] src/main/scala/code/model/User.scala:30: type mismatch;
[error] found : net.liftweb.mapper.MappedLongIndex[code.model.User]
[error] required: net.liftweb.mapper.MappedForeignKey[Long,?,code.model.User]
[error] object friends extends MappedOneToMany(User, User.id)
[error] ^
What would be the way to go about this?
Regards
If you look at the error message you see that the OneToMany helper needs a foreign key in the "many" end of the relation pointing back to this "one". User.id is the primary key and therefore doesn't fit.
If you think about it what you want is really a ManyToMany-relationship.
That you can create like this:
object User extends User with MetaMegaProtoUser[User] with MappedManyToMany {
...
object friends extends MappedManyToMany(Friends, Friends.friend, Friends.friendee, User)
where you have a join table:
class Friends extends Mapper[Friends] {
object friend extends MappedLongForeignKey(this, User) {
override def dbIndexed_? = true
}
object friendee extends MappedLongForeignKey(this, User) {
override def dbIndexed_? = true
}
def getSingleton = Friends
}
object Friends extends Friends with MetaMapper[Friends]
If you want the friend relation to be reflexive, you'll have to do some further work.
This is an extremely late answer I guess. I would suggest posting to the lift mailing list for extremely fast replies: https://groups.google.com/forum/?fromgroups#!forum/liftweb