• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
Scala active record
 

Scala active record

on

  • 3,424 views

 

Statistics

Views

Total Views
3,424
Views on SlideShare
1,763
Embed Views
1,661

Actions

Likes
5
Downloads
23
Comments
2

6 Embeds 1,661

http://teppei.hateblo.jp 1644
http://tokyo-ninja.hatenablog.jp 11
http://www.feedspot.com 3
http://cloud.feedly.com 1
https://www.google.co.jp 1
http://www.google.co.jp 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

12 of 2 previous next

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • How does one remove an association from a HABTM relation. Not the actual record, just the association.
    Are you sure you want to
    Your message goes here
    Processing…
  • Did a presentation involving ActiveRecord recently. You can watch it here: http://slideshare.net/SebastianNozzi/play-la-rails
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Scala active record Scala active record Presentation Transcript

    • Scala-ActiveRecordType-safe Active Record model for Scala@teppei_tosa_en
    • Who am I鉄平 土佐TEPPEI TOSAiron peace place name
    • The first official conference in Japan.http://scalaconf.jp/en/
    • Typesafe members came toJapan and gave speeches.
    • Talked about the case example of buildingour original BRMS “BIWARD” in Scala.
    • Japanese engineers talked aboutScala tips or their libraries.• “Stackable-controller” by @gakuzzzzhttps://github.com/t2v/stackable-controller• “How we write and use Scala libraries notto cry” by @tototoshihttp://tototoshi.github.io/slides/how-we-write-and-use-scala-libraries-scalaconfjp2013/#1For example,http://scalaconf.jp/en/
    • Scala-ActiveRecordType-safe Active Record model for Scala• https://github.com/aselab/scala-activerecord• Latest version : 0.2.2• Licence : MIT
    • Features• Squeryl wrapper• type-safe (most part)• Rails ActiveRecord-like operability• Auto transaction control• validations• Associations• Testing support
    • Most of the otherORM LibrariesWrap SQLval selectCountries = SQL(“Select * from Countries”)Have to define mappings from the results of SQL tothe models.val countries = selectCountries().map(row =>row[String](“code”) -> row[String](“name”)).toList
    • The motivation ofScala-ActiveRecord• Want to define the model mapping moreeasily.• Want not to define the find methods ineach model classes.• Want not to write SQLs.
    • Other libraries example1. Anorm2. Slick ( ScalaQuery )3. Squeryl
    • 1.Anorm• Anorm doesn’t have the Model layer.• Have to define similar methods in eachClass.case class Person(id:Pk[Long], name:String)object Person {! def create(person:Person):Unit = {! ! DB.withConnection { implicit connection =>! ! ! SQL("insert int person(name) values ({name}")! ! ! ! .on(name -> person.name)! ! ! ! .executeUpdate()! ! }! }! ...}
    • 2. Slick ( ScalaQuery )• Query Interface is good.• Definding tables syntax is redundant.• Have to define the mapping between tableand model in each table.case class Member(id:Int, name:String, email:Option[String])object Members extends Table[Member]("MEMBERS") {! def id = column[Int]("ID", O.PrimaryKey, O.AutoInc)! def name = column[String]("Name")! def email = column[Option[String]]("EMAIL")! def * = id.? ~ name ~ email <> (Member, Member.unapply _)}
    • 3. Squeryl• The best one in these libraries.• Scala-ActiveRecord wraps this with someimprovement.1. Optimize the generated SQLs2. Automate the transaction control3. Use CoC approach to build relationship
    • When queries are combinedSqueryl generates sub-query SQL.Squerylval query = from(table)(t => where(t.id.~ > 20) select(t))from(query)(t => where(t.name like "%test%) select(t))Scalaselect * from! (Select * from table where table.id > 20) q1where q1.name like "test"SQLIt negatively affect performance.
    • When queries are combinedSqueryl generates sub-query SQL.Scala-ActiveRecordval query = Table.where(_.id.~ > 20)query.where(_.name like "%test%").toListScalaselect * from tablewhere table.id > 20 and table.name like "test"SQLIt can generate more simple SQL statement.
    • Automatethe transaction controlSquerylScala-ActiveRecord• Call “inTransaction” automatically at accessingIterable#iterator.• When the save or delete method is called,“inTransaction” is executed by default.• Off course, you can call “inTransaction” expressly.inTransaction {books.insert(new Author(1, "Michel","Folco"))! !val a = from(authors)(a=> where(a.lastName === "Folco") select(a))}
    • Use CoC approach tobuild relationship.Squerylobject Schema extends Schema{! val foo = table[Foo]! val bar = table[Bar]! val fooToBar = oneToManyRelation(Foo, Bar).via(! ! (f,b) => f.barId === b.id! )}class Foo(var barId:Long) extends SomeEntity {! lazy val bar:ManyToOne[Bar] = schema.fooToBar.right(this)}class Bar(var bar:String) extends SomeEntity {! lazy val foos:OneToMany[Foo] = schema.fooToBar.left(this)}
    • Use CoC approach tobuild relationship.Scala-ActiveRecordobject Table extends ActiveRecordTabels {! val foo = table[Foo]! val bar = table[Bar]}class Foo(var barId:Long) extends ActiveRecord {! lazy val bar = belongsTo[Bar]}class Bar(var bar:String) extends ActiveRecord {! lazy val foos = hasMany[Foo]}
    • Getting Started
    • Define the dependencyin SBT project definitionAdd the following settings in build.sbt or project/Build.scala.libraryDependencies ++= Seq("com.github.aselab" %% "scala-activerecord" % "0.2.2","org.slf4j" % "slf4j-nop" % "1.7.2", // optional"com.h2database" % "h2" % "1.3.170" // optional)resolvers += Resolver.sonatypeRepo("releases")
    • Using Scala ActiveRecordPlay2.1 PluginAdd the following settings in project/Build.scalaval appDependencies = Seq("com.github.aselab" %% "scala-activerecord" % "0.2.2","com.github.aselab" %% "scala-activerecord-play2" % "0.2.2",jdbc,"com.h2database" % "h2" % "1.3.170")val main = play.Project(appName, appVersion, appDependencies).settings(resolvers ++= Seq(Resolver.sonatypeRepo("releases")))Add the following settings in conf/play.plugins9999:com.github.aselab.activerecord.ActiveRecordPlugin
    • Database SupportH2 databaseMySQLPostgrSQLDerbyOracle
    • Defining Schema
    • Model implementationcase class Person(var name:String, var age:Int)! extends ActiveRecordobject Person! extends ActiveRecordCompanion[Person]Schema definitionobject Tables extends ActiveRecordTable {! val people = table[Person]}
    • CRUD
    • Createval person = Person("person1", 25)person.save // return trueval person = Preson("person1", 25).create// return Person("person1", 25)
    • ReadPerson.find(1)// Some(Person("person1"))Person.toList// List(person("person1”), ...)Person.findBy("name", "john")// Some(Person("John"))Person.where(_.name === "john").headOption// Some(Person("john"))
    • UpdatePerson.find(1).foreach { p =>! p.name = "Ichiro"! p.age = 37! p.save}Person.forceUpdate( _.id === 1)(! _.name := "ichiro", _.age := 37)
    • DeletePerson.where(_.name === "john").foreach(_.delete)Person.find(1) match {! case Some(person) => person.delete! case _ =>}Person.delete(1)
    • Query Interface
    • Find single objectval client = Client.find(10)// Some(Client) or Noneval John = Client.findBy("name", "john")// Some(Client("john")) or Noneval john = Client.findBy(("name", "john"), ("age",25))// Some(Client("john",25)) or None
    • Get the search result as ListScalaClients.where(c =>! c.name === "john" and c.age.~ > 25).toListClients! .where(_.name == "john")! .where(_.age.~ > 25)! .toListgenerated SQLselect clients.name, clients.age, clients.idfrom clientswhere clients.name = "john" and clients.age > 25
    • Using iterable methodsval client = Client.head// First Client or RecordNotFoundExceptionval client = Client.lastOption// Some(Last Client) or Noneval (adults, children) = Client.partition(_.age >= 20)// Parts of clients
    • OrderingClient.orderBy(_.name)Client.orderBy(_.name asc)Client.orderBy(_.name asc, _.age desc)
    • LimitClient.limit(10)OffsetClient.page(2, 5)ExistenceClient.exists(_.name like “john%”)// true or false
    • Specify selected fieldsClient.select(_.name).toList// List[String]Client.select(c => (c.name, c.age)).toList// List[(String, Int)]
    • Combine QueriesScalaClients.where(_.name like "john%")! .orderBy(_.age desc)! .where(_.age.~ < 25)! .page(2, 5)! .toListgenerated SQLselect clients.name, clients.age, clients.idfrom clientswhere ((clients.name like "john%") and (clients.age < 25))order by clients.age desclimit 5 offset 2
    • Cache Controlval orders = Order.where(_.age.~ > 20)//execute this SQL query and cheche the queryorders.toList//dont execute the SQL queryorders.toListWhen the query is implicitly converted, the query is cached.
    • Validations
    • Annotation-basedValidationcase class User(! @Required name:String,! @Length(max=20) profile:String,! @Range(min=0, max=150) age:Int) extends ActiveRecordObject User extends ActiveRecordCompanion[User]
    • Exampleval user = user("", "Profile", 25).createuser.isValid // falseuser.hasErrors // trueuser.errors.messages // Seq("Name is required")user.hasError("name") // trueUser("", "profile", 15).saveEither match {case Right(user) => println(user.name)case Left(errors) => println(errors.message)}// "Name is required"
    • Callbacks
    • Available hooks•beforeValidation•beforeCreate•afterCreate•beforeUpdate•afterUpdate•beforeSave•afterSave•beforeDelete•afterDelete
    • Examplecase class User(login:String) extends ActiveRecord {! @Transient! var password:String = _! var hashedPassword:String = _! override def beforeSave() {! ! hashedPassword = SomeLibrary.encrypt(password)! }}val user = User("john")user.password = "raw_password"user.save// stored encrypted password
    • Relationship
    • One-to-Manycase class User(name:String) extends ActiveRecord {! val groupId:Option[Long] = None! lazy val group = belongsTo[Group]}case class Group(name:String) extends ActiveRecord {! lazy val users = hasMany[User]}groupsidnameusersidgroup_idname
    • One-to-Manyval user1 = User("user1").createval group1 = Group("group1").creategroup1.users << user1group1.users.toList// List(User("user1"))user1.group.getOrElse(Group("group2"))// Group("group1")
    • Generated SQL sampleScalagroup1.users.where(_.name like "user%")! .orderBy(_.id desc)! .limit(5)! .toListgenerated SQLSelect users.name, users.idFrom usersWhere ((users.group_id = 1) And (users.name like "user%"))Order by users.id Desclimit 5 offset 0
    • Many-to-Many (HABTM)case class User(name:String) extends ActiveRecord {! lazy val groups = hasAndBelongsToMany[Group]}case class Group(name:String) extends ActiveRecord {! lazy val users = hasAndBelongsToMany[user]}groupsidnamegroups_usersleft_idright_idusersidname
    • val user1 = User("user1").createval group1 = Group("group1").createval group2 = Group("group2").createuser1.groups := List(group1, group2)user1.groups.toList// List(Group("group1"), Group("group2"))group1.users.toList// List(User("user1"))Many-to-Many (HABTM)
    • Many-to-Many(hasManyThrough)groupsidnamemembershipsiduser_idgroup_idisAdminusersidname
    • Many-to-Many(hasManyThrough)case class Membership(! userId:Long, projectid:Long, isAdmin:Boolean = false) extends ActiveRecord {! lazy val user = belongsTo[User]! lazy val group = belongsTo[Group]}case class User(name:String) extends ActiveRecord {! lazy val memberships = hasMany[Membership]! lazy val groups = hasManyThrough[Group, Membership](memberships)}case class Group(name:String) extends ActiveRecord {! lazy val memberships = hasmany[Membership]! lazy val users = hasManyThrough[User, Membership](memberships)}
    • Conditions Optionscase class Group(name:String) extends ActiveRecord {! lazy val adminUsers =! ! hasMany[User](conditions = Map("isAdmin" -> true))}group.adminUsers << user// user.isAdmin == true
    • ForeignKey optioncase class Comment(name:String) extends ActiveRecord {! val authorId:Long! lazy val author= belongsTo[User](foreignKey = "authorId")}
    • Join TablesScalaClient.joins[Order](! (client, order) => client.id === order.clientId).where(! (client, order) => client.age.~ < 20 and order.price.~ > 1000).select(! (client, order) => (client.name, client.age, order.price)).toListgenerated SQLSelect clients.name, clients.age, order.priceFrom clients inner join orders on (clients.id = orders.client_id)Where ((clients.age < 20) and (groups.price > 1000))
    • Eager loading associationsThe solution for N+1 problem.ScalaOrder.includes(_.client).limit(10).map {! order => order.client.name}.mkString("n")generated SQLSelect orders.price, orders.idFrom orders limit 10 offset 0Select clients.name, clients.age, clients.idFrom clients inner join orders on (clients.id = orders.client_id)Where (orders.id in (1,2,3,4,5,6,7,8,9,10))
    • Logging and Debugging
    • See the generated SQLsUse the toSql methodprintln(User.where(_.name like "john%").orderBy(_.age desc).toSql)Set logging level with “debug” in logback.xml<configuration><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><encoder><pattern>%highlight(%-5level) %cyan(%logger{15}) - %msg %n</pattern></encoder></appender><root level="DEBUG"><appender-ref ref="STDOUT" /></root></configuration>
    • In SBT consolebuild.sbt or project/Build.scalainitialCommands in console := """import com.github.aselab.activerecord._import com.github.aselab.activerecord.dsl._import models._SomeTables.initialize(Map("schema" -> "models.SomeTables"))"""In the console> consolescala> User.forceInsertAll{ (1 to 10000).map{i => User("name" + i)} }scala> User.where(_.name === "name10").toList
    • Testing
    • Setting for testbuild.sbt or project/Build.scalalibraryDependencies ++= Seq("com.github.aselab" %% "scala-activerecord" % "0.2.2","com.github.aselab" %% "scala-activerecord-specs" % "0.2.2" % "test","org.specs2" %% "specs2" % "1.12.3" % "test")resolvers += Resolver.sonatypeRepo("releases")application.conftest {schema = "models.Tables"driver = "org.h2.Driver"jdbcurl = "jdbc:h2:mem:test"}
    • Test Exampleimport com.github.aselab.activerecord._object SomeModelSpecs extends ActiveRecordSpecification {override val config= Map("schema" -> "com.example.models.Tables")override def beforeAll = {super.beforeAllSomeModel("test1").create}override def afterAll = {super.afterAll}"sample" should {// Some specifications code}}
    • Performance
    • ActiveRecord Overhead• How Sql statements are generated.• The time to create active-record objectfrom the results of query.
    • ORM Race•Anorm•Slick•Squerl•Scala-ActiveRecord
    • Race Condition• Create the “User” table which has only 3 columns• Insert 1000 records into the “User” table• Select all from the table with same statements• Time their trip to the end of creation objects• Exclude the time to creating DB connection• Use Play framework 2.1.1• Use H2-database• Run in Global.onStart• Run 5 times• Compare with the average times
    • The RacersAnorm SquerylSlick Scala-ActiveRecordSQL("SELECT * FROM USER").as(User.simple *)Query(Users).listfrom(AppDB.user)(s => select(s)).toListUser.all.toList
    • The Race Results39.8ms116.8ms177.2ms258.8msSquerylAnormScala-ActiveRecordSlick
    • Future
    • Validation at compiling(with “Macro”)• The findBy method and conditions ofassociation will be type-safe.• Validate whether foreign-key is specifiedwith existing key or not.
    • Support SerializationForm Model XMLJSONMessagePackValidationViewBindViewHelper
    • Support Web framework• CRUD controller• Form Helper for Play and Scalatra• Code generator as SBT plugin
    • Secret
    • DEMO withYATTER(Yet Another twiTTER )
    • YATTER’s tablesFollowsiduseridfollows_usersiduser_idfollow_idUsersidnameTweetsiduserIdtextManyToManyOneToManyOneToOne(But not supported yet)
    • https://github.com/ironpeace/yatter
    • #scalajpMt.FUJI
    • Tokyo Station
    • Japanese Castle
    • Sushi
    • Okonomiyaki
    • @teppei_tosa_enhttps://github.com/aselab/scala-activerecordhttps://github.com/ironpeace/yatterThank you