├── project ├── build.properties ├── scalikejdbc.properties └── plugins.sbt ├── screenshot.png ├── app ├── controllers │ ├── Root.scala │ ├── JsRouter.scala │ ├── Skills.scala │ ├── Companies.scala │ └── Programmers.scala ├── models │ ├── ProgrammerSkill.scala │ ├── Skill.scala │ ├── Company.scala │ └── Programmer.scala ├── modules │ └── GlobalModule.scala ├── utils │ └── DBInitializer.scala ├── views │ └── index.scala.html └── assets │ └── javascripts │ └── main.coffee ├── activator.properties ├── LICENSE ├── conf ├── logback.xml ├── routes ├── db │ └── migration │ │ └── default │ │ └── V1__create_tables.sql └── application.conf ├── test ├── resources │ └── logback-test.xml ├── models │ ├── CompanySpec.scala │ ├── SkillSpec.scala │ └── ProgrammerSpec.scala └── settings │ └── DBSettings.scala ├── .gitignore ├── tutorial └── index.html └── README.md /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.1.6 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/scalikejdbc/hello-scalikejdbc/HEAD/screenshot.png -------------------------------------------------------------------------------- /app/controllers/Root.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api.mvc._ 4 | 5 | class Root extends Controller { 6 | 7 | def index = Action { 8 | Ok(views.html.index()) 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/models/ProgrammerSkill.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import scalikejdbc._ 4 | 5 | case class ProgrammerSkill(programmerId: Long, skillId: Long) 6 | 7 | object ProgrammerSkill extends SQLSyntaxSupport[ProgrammerSkill] { 8 | val ps = ProgrammerSkill.syntax("ps") 9 | } 10 | 11 | -------------------------------------------------------------------------------- /app/modules/GlobalModule.scala: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import javax.inject._ 4 | 5 | @Singleton 6 | class Global { 7 | } 8 | 9 | class GlobalModule extends com.google.inject.AbstractModule { 10 | def configure() = { 11 | bind(classOf[Global]).asEagerSingleton 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /project/scalikejdbc.properties: -------------------------------------------------------------------------------- 1 | jdbc.driver=org.h2.Driver 2 | jdbc.url=jdbc:h2:file:db/sandbox 3 | jdbc.username=sa 4 | jdbc.password= 5 | jdbc.schema= 6 | generator.packageName=devteam.model2 7 | geneartor.lineBreak=LF 8 | generator.template=queryDsl 9 | generator.testTemplate=specs2unit 10 | generator.encoding=UTF-8 11 | 12 | -------------------------------------------------------------------------------- /activator.properties: -------------------------------------------------------------------------------- 1 | name=scalikejdbc-activator-template 2 | title=Hello ScalikeJDBC! 3 | description=Just write SQL and get things done! ScalikeJDBC is A tidy SQL-based DB access library for Scala developers. This library naturally wraps JDBC APIs and provides you easy-to-use APIs. This template is built with ScalikeJDBC, Play2, Backbone.js and CoffeeScript. 4 | tags=Scala,Database,BackboneJs,CoffeeScript 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Kazuhiro Sera 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | resolvers ++= Seq( 2 | "sonatype releases" at "http://oss.sonatype.org/content/repositories/releases", 3 | "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" 4 | ) 5 | libraryDependencies += "com.h2database" % "h2" % "1.4.197" 6 | 7 | addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2") 8 | addSbtPlugin("org.scalikejdbc" % "scalikejdbc-mapper-generator" % "3.3.0") 9 | addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.6.16") 10 | addSbtPlugin("com.typesafe.sbt" % "sbt-coffeescript" % "1.0.2") 11 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.3.4") 12 | -------------------------------------------------------------------------------- /conf/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %coloredLevel - %logger - %message%n%xException 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %coloredLevel - %logger - %message%n%xException 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .settings 2 | .classpath 3 | .project 4 | *.iml 5 | *.ipr 6 | *.iws 7 | dist/ 8 | lib_managed/ 9 | project/activator* 10 | project/boot/ 11 | project/plugins/project/ 12 | target/ 13 | 14 | # use glob syntax. 15 | syntax: glob 16 | *.ser 17 | *.class 18 | *~ 19 | *.bak 20 | #*.off 21 | *.old 22 | 23 | # eclipse conf file 24 | .settings 25 | .classpath 26 | .project 27 | .manager 28 | .scala_dependencies 29 | 30 | # idea 31 | .idea 32 | *.iml 33 | 34 | # building 35 | target 36 | build 37 | null 38 | tmp* 39 | temp* 40 | dist 41 | test-output 42 | build.log 43 | 44 | # other scm 45 | .svn 46 | .CVS 47 | .hg* 48 | 49 | # switch to regexp syntax. 50 | # syntax: regexp 51 | # ^\.pc/ 52 | 53 | #SHITTY output not in target directory 54 | build.log 55 | .DS_Store 56 | derby.log 57 | 58 | *.db 59 | 60 | .lib 61 | sbt 62 | 63 | logs 64 | sandbox/db 65 | -------------------------------------------------------------------------------- /app/controllers/JsRouter.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import play.api.mvc._ 4 | import play.api.routing._ 5 | 6 | class JsRouter extends Controller { 7 | 8 | def javascriptRoutes = Action { implicit request => 9 | import routes.javascript._ 10 | Ok( 11 | JavaScriptReverseRouter("jsRoutes")( 12 | Companies.all, 13 | Companies.show, 14 | Companies.create, 15 | Companies.delete, 16 | Programmers.all, 17 | Programmers.show, 18 | Programmers.create, 19 | Programmers.addSkill, 20 | Programmers.deleteSkill, 21 | Programmers.joinCompany, 22 | Programmers.leaveCompany, 23 | Programmers.delete, 24 | Root.index, 25 | Skills.all, 26 | Skills.show, 27 | Skills.create, 28 | Skills.delete)).as("text/javascript") 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tutorial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Hello ScalikeJDBC! - Activator Template 4 | 5 | 6 | 7 |
8 |

Run snippets on sbt console

9 |

You can try snippets on sbt console as follows.

10 | 11 |

12 | // basic import
13 | import scalikejdbc._
14 | 
15 | // load conf/application.conf
16 | import scalikejdbc.config._
17 | DBs.setupAll
18 | 
19 | // prepare data
20 | import models._, utils._
21 | DBInitializer.run()
22 | GlobalSettings.sqlFormatter = SQLFormatterSettings("utils.HibernateSQLFormatter")
23 | 
24 | implicit val autoSession = AutoSession
25 | 
26 | // run snippets
27 | val c = Company.syntax("c")
28 | val companies: List[Company] = withSQL {
29 |   select.from(Company as c).where.isNull(c.deletedAt).orderBy(c.id)
30 | }.map(Company(c)).list.apply()
31 | 
32 |
33 | 34 |
35 |

More Examples

36 |

Let's move on to app/models/Company.scala which contains some examples.

37 |
38 | 39 |
40 |

Next Steps

41 |

For more information, check out the manual on the website.

42 |
43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/controllers/Skills.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import javax.inject.{ Inject, Singleton } 4 | import com.github.tototoshi.play2.json4s.native._ 5 | import models._ 6 | import org.json4s._ 7 | import org.json4s.ext.JodaTimeSerializers 8 | import play.api.data.Forms._ 9 | import play.api.data._ 10 | import play.api.data.validation.Constraints._ 11 | import play.api.mvc._ 12 | 13 | @Singleton 14 | class Skills @Inject() (json4s: Json4s) extends Controller { 15 | 16 | import json4s.implicits._ 17 | implicit val formats = DefaultFormats ++ JodaTimeSerializers.all 18 | 19 | def all = Action { 20 | Ok(Extraction.decompose(Skill.findAll)) 21 | } 22 | 23 | def show(id: Long) = Action { 24 | Skill.find(id) match { 25 | case Some(skill) => Ok(Extraction.decompose(skill)) 26 | case _ => NotFound 27 | } 28 | } 29 | 30 | case class SkillForm(name: String) 31 | 32 | private val skillForm = Form( 33 | mapping("name" -> text.verifying(nonEmpty))(SkillForm.apply)(SkillForm.unapply)) 34 | 35 | def create = Action { implicit req => 36 | skillForm.bindFromRequest.fold( 37 | formWithErrors => BadRequest("invalid parameters"), 38 | form => { 39 | val skill = Skill.create(name = form.name) 40 | Created.withHeaders(LOCATION -> s"/skills/${skill.id}") 41 | NoContent 42 | }) 43 | } 44 | 45 | def delete(id: Long) = Action { 46 | Skill.find(id) match { 47 | case Some(skill) => 48 | skill.destroy() 49 | NoContent 50 | case _ => NotFound 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /conf/routes: -------------------------------------------------------------------------------- 1 | # Routes 2 | # ~~~~ 3 | 4 | GET / controllers.Root.index 5 | 6 | GET /companies controllers.Companies.all 7 | GET /companies/:id controllers.Companies.show(id: Long) 8 | POST /companies controllers.Companies.create 9 | DELETE /companies/:id controllers.Companies.delete(id: Long) 10 | 11 | GET /programmers controllers.Programmers.all 12 | GET /programmers/:id controllers.Programmers.show(id: Long) 13 | POST /programmers controllers.Programmers.create 14 | DELETE /programmers/:id controllers.Programmers.delete(id: Long) 15 | POST /programmers/:id/company/:companyId controllers.Programmers.joinCompany(id: Long, companyId: Long) 16 | DELETE /programmers/:id/company controllers.Programmers.leaveCompany(id: Long) 17 | POST /programmers/:id/skills/:skillId controllers.Programmers.addSkill(id: Long, skillId: Long) 18 | DELETE /programmers/:id/skills/:skillId controllers.Programmers.deleteSkill(id: Long, skillId: Long) 19 | 20 | GET /skills controllers.Skills.all 21 | GET /skills/:id controllers.Skills.show(id: Long) 22 | POST /skills controllers.Skills.create 23 | DELETE /skills/:id controllers.Skills.delete(id: Long) 24 | 25 | GET /assets/javascripts/routes controllers.JsRouter.javascriptRoutes 26 | GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) 27 | 28 | -------------------------------------------------------------------------------- /app/controllers/Companies.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import javax.inject.{ Inject, Singleton } 4 | import com.github.tototoshi.play2.json4s.native._ 5 | import models._ 6 | import org.json4s._ 7 | import org.json4s.ext.JodaTimeSerializers 8 | import play.api.data.Forms._ 9 | import play.api.data._ 10 | import play.api.data.validation.Constraints._ 11 | import play.api.mvc._ 12 | 13 | @Singleton 14 | class Companies @Inject() (json4s: Json4s) extends Controller { 15 | 16 | import json4s.implicits._ 17 | implicit val formats = DefaultFormats ++ JodaTimeSerializers.all 18 | 19 | def all = Action { 20 | Ok(Extraction.decompose(Company.findAll)) 21 | } 22 | 23 | def show(id: Long) = Action { 24 | Company.find(id) match { 25 | case Some(company) => Ok(Extraction.decompose(company)) 26 | case _ => NotFound 27 | } 28 | } 29 | 30 | case class CompanyForm(name: String, url: Option[String] = None) 31 | 32 | private val companyForm = Form( 33 | mapping( 34 | "name" -> text.verifying(nonEmpty), 35 | "url" -> optional(text))(CompanyForm.apply)(CompanyForm.unapply)) 36 | 37 | def create = Action { implicit req => 38 | companyForm.bindFromRequest.fold( 39 | formWithErrors => BadRequest("invalid parameters"), 40 | form => { 41 | val company = Company.create(name = form.name, url = form.url) 42 | Created.withHeaders(LOCATION -> s"/companies/${company.id}") 43 | NoContent 44 | }) 45 | } 46 | 47 | def delete(id: Long) = Action { 48 | Company.find(id) match { 49 | case Some(company) => 50 | company.destroy() 51 | NoContent 52 | case _ => NotFound 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello ScalikeJDBC! 2 | 3 | This is an example app to demonstrate how to create a [Play](https://playframework.com/) application using [ScalikeJDBC](http://scalikejdbc.org/) and [play-flyway](https://github.com/flyway/flyway-play). 4 | 5 | NOTICE: Formerly, this repository was a [Lightbend Activator](http://www.lightbend.com/activator/) template for ScalikeJDBC beginners. But Activator was EOL-ed on May 24, 2017. Thus, the repository is not an Activator template but an example app now 6 | 7 | ![screenshot](https://raw.github.com/scalikejdbc/hello-scalikejdbc/master/screenshot.png) 8 | 9 | ## ScalikeJDBC 10 | 11 | https://github.com/scalikejdbc/scalikejdbc 12 | 13 | A tidy SQL-based DB access library for Scala developers. This library naturally wraps JDBC APIs and provides you easy-to-use APIs. 14 | 15 | ## Play Framework 2.x 16 | 17 | https://www.playframework.com/ 18 | 19 | Play Framework makes it easy to build web applications with Java & Scala. Play is based on a lightweight, stateless, web-friendly architecture. 20 | 21 | Built on Akka, Play provides predictable and minimal resource consumption (CPU, memory, threads) for highly-scalable applications. 22 | 23 | ### play-flyway 24 | 25 | https://github.com/flyway/flyway-play 26 | 27 | Flyway plugin for Play2. It aims to be a substitute for play-evolutions. 28 | 29 | ### play-json4s 30 | 31 | https://github.com/tototoshi/play-json4s 32 | 33 | This module allows you to use json4s in your Play2 apps. 34 | 35 | ## Backbone.js 36 | 37 | http://backbonejs.org/ 38 | 39 | Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface. 40 | 41 | ## CoffeeScript 42 | 43 | http://coffeescript.org/ 44 | 45 | CoffeeScript is a little language that compiles into JavaScript. Underneath that awkward Java-esque patina, JavaScript has always had a gorgeous heart. CoffeeScript is an attempt to expose the good parts of JavaScript in a simple way. 46 | 47 | -------------------------------------------------------------------------------- /test/models/CompanySpec.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import scalikejdbc.specs2.mutable.AutoRollback 4 | import org.specs2.mutable._ 5 | import scalikejdbc._ 6 | 7 | class CompanySpec extends Specification with settings.DBSettings { 8 | 9 | trait AutoRollbackWithFixture extends AutoRollback { 10 | override def fixture(implicit session: DBSession) { 11 | applyUpdate(delete from Company) 12 | Company.create("Typesafe", Some("http://typesafe.com")) 13 | Company.create("Oracle") 14 | Company.create("Amazon") 15 | } 16 | } 17 | 18 | "Company" should { 19 | "find by primary keys" in new AutoRollbackWithFixture { 20 | val id = Company.findAll().head.id 21 | val maybeFound = Company.find(id) 22 | maybeFound.isDefined should beTrue 23 | } 24 | "find all records" in new AutoRollbackWithFixture { 25 | val allResults = Company.findAll() 26 | allResults.size should_== (3) 27 | } 28 | "count all records" in new AutoRollbackWithFixture { 29 | val count = Company.countAll() 30 | count should_== (3L) 31 | } 32 | "find by where clauses" in new AutoRollbackWithFixture { 33 | val results = Company.findAllBy(sqls.isNull(Company.column.url)) 34 | results.size should_== (2) 35 | } 36 | "count by where clauses" in new AutoRollbackWithFixture { 37 | val count = Company.countBy(sqls.isNotNull(Company.column.url)) 38 | count should_== (1L) 39 | } 40 | "create new record" in new AutoRollbackWithFixture { 41 | val newCompany = Company.create("Microsoft") 42 | newCompany.id should not beNull 43 | } 44 | "save a record" in new AutoRollbackWithFixture { 45 | val entity = Company.findAll().head 46 | entity.copy(name = "Google").save() 47 | val updated = Company.find(entity.id).get 48 | updated.name should_== ("Google") 49 | } 50 | "destroy a record" in new AutoRollbackWithFixture { 51 | val entity = Company.findAll().head 52 | entity.destroy() 53 | val shouldBeNone = Company.find(entity.id) 54 | shouldBeNone.isDefined should beFalse 55 | Company.countAll should_== (2L) 56 | } 57 | } 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /test/models/SkillSpec.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import scalikejdbc.specs2.mutable.AutoRollback 4 | import org.specs2.mutable._ 5 | import scalikejdbc._ 6 | 7 | class SkillSpec extends Specification with settings.DBSettings { 8 | 9 | trait AutoRollbackWithFixture extends AutoRollback { 10 | override def fixture(implicit session: DBSession) { 11 | applyUpdate(delete from Skill) 12 | Skill.create("Scala") 13 | Skill.create("Java") 14 | Skill.create("Ruby") 15 | Skill.create("Perl") 16 | Skill.create("Python") 17 | } 18 | } 19 | 20 | "Skill" should { 21 | "find by primary keys" in new AutoRollbackWithFixture { 22 | val id = Skill.findAll().head.id 23 | val maybeFound = Skill.find(id) 24 | maybeFound.isDefined should beTrue 25 | } 26 | "find all records" in new AutoRollbackWithFixture { 27 | val allResults = Skill.findAll() 28 | allResults.size should_== (5) 29 | } 30 | "count all records" in new AutoRollbackWithFixture { 31 | val count = Skill.countAll() 32 | count should_== (5L) 33 | } 34 | "find by where clauses" in new AutoRollbackWithFixture { 35 | val results = Skill.findAllBy(sqls.in(Skill.column.name, Seq("Python", "Perl"))) 36 | results.size should_== (2) 37 | } 38 | "count by where clauses" in new AutoRollbackWithFixture { 39 | val count = Skill.countBy(sqls"${Skill.column.name} like ${"P%"}") 40 | count should_== (2L) 41 | } 42 | "create new record" in new AutoRollbackWithFixture { 43 | val newSkill = Skill.create(name = "ScalikeJDBC") 44 | newSkill.id should not beNull 45 | } 46 | "save a record" in new AutoRollbackWithFixture { 47 | val entity = Skill.findAll().head 48 | entity.copy(name = "Erlang").save() 49 | val updated = Skill.find(entity.id).get 50 | updated.name should_== ("Erlang") 51 | } 52 | "destroy a record" in new AutoRollbackWithFixture { 53 | val entity = Skill.findAll().head 54 | entity.destroy() 55 | val shouldBeNone = Skill.find(entity.id) 56 | shouldBeNone.isDefined should beFalse 57 | Skill.countAll() should_== (4L) 58 | } 59 | } 60 | 61 | } 62 | 63 | -------------------------------------------------------------------------------- /conf/db/migration/default/V1__create_tables.sql: -------------------------------------------------------------------------------- 1 | drop table if exists programmer; 2 | create sequence programmer_id_seq start with 1; 3 | create table programmer ( 4 | id bigint not null default nextval('programmer_id_seq') primary key, 5 | name varchar(255) not null, 6 | company_id bigint, 7 | created_timestamp timestamp not null, 8 | deleted_timestamp timestamp 9 | ); 10 | 11 | drop table if exists company; 12 | create sequence company_id_seq start with 1; 13 | create table company ( 14 | id bigint not null default nextval('company_id_seq') primary key, 15 | name varchar(255) not null, 16 | url varchar(255), 17 | created_at timestamp not null, 18 | deleted_at timestamp 19 | ); 20 | 21 | drop table if exists skill; 22 | create sequence skill_id_seq start with 1; 23 | create table skill ( 24 | id bigint not null default nextval('skill_id_seq') primary key, 25 | name varchar(255) not null, 26 | created_at timestamp not null, 27 | deleted_at timestamp 28 | ); 29 | 30 | drop table if exists programmer_skill; 31 | create table programmer_skill ( 32 | programmer_id bigint not null, 33 | skill_id bigint not null, 34 | primary key(programmer_id, skill_id) 35 | ); 36 | 37 | insert into company (name, url, created_at) values ('Typesafe', 'http://typesafe.com/', current_timestamp); 38 | insert into company (name, url, created_at) values ('Oracle', 'http://www.oracle.com/', current_timestamp); 39 | insert into company (name, url, created_at) values ('Google', 'http://www.google.com/', current_timestamp); 40 | insert into company (name, url, created_at) values ('Microsoft', 'http://www.microsoft.com/', current_timestamp); 41 | 42 | insert into skill (name, created_at) values ('Scala', current_timestamp); 43 | insert into skill (name, created_at) values ('Java', current_timestamp); 44 | insert into skill (name, created_at) values ('Ruby', current_timestamp); 45 | insert into skill (name, created_at) values ('MySQL', current_timestamp); 46 | insert into skill (name, created_at) values ('PostgreSQL', current_timestamp); 47 | 48 | 49 | insert into programmer (name, company_id, created_timestamp) values ('Alice', 1, current_timestamp); 50 | insert into programmer (name, company_id, created_timestamp) values ('Bob', 2, current_timestamp); 51 | insert into programmer (name, company_id, created_timestamp) values ('Chris', 1, current_timestamp); 52 | 53 | insert into programmer_skill values (1, 1); 54 | insert into programmer_skill values (1, 2); 55 | insert into programmer_skill values (2, 2); 56 | 57 | -------------------------------------------------------------------------------- /conf/application.conf: -------------------------------------------------------------------------------- 1 | # This is the main configuration file for the application. 2 | # ~~~~~ 3 | 4 | # Secret key 5 | # ~~~~~ 6 | # The secret key is used to secure cryptographics functions. 7 | # 8 | # This must be changed for production, but we recommend not changing it in this file. 9 | # 10 | # See http://www.playframework.com/documentation/latest/ApplicationSecret for more details. 11 | play.crypto.secret = "ChgqH0oo_CxMGVbi4Ej5u/:QVVD37[BIs=2T:]A<:d92DuoHWs>_VNyEiJDb1]n/" 12 | 13 | # The application languages 14 | # ~~~~~ 15 | play.i18n.langs = [ "en" ] 16 | 17 | # Router 18 | # ~~~~~ 19 | # Define the Router object to use for this application. 20 | # This router will be looked up first when the application is starting up, 21 | # so make sure this is the entry point. 22 | # Furthermore, it's assumed your route file is named properly. 23 | # So for an application router like `my.application.Router`, 24 | # you may need to define a router file `conf/my.application.routes`. 25 | # Default to Routes in the root package (and conf/routes) 26 | # play.http.router = my.application.Routes 27 | 28 | # Database configuration 29 | # ~~~~~ 30 | # You can declare as many datasources as you want. 31 | # By convention, the default datasource is named `default` 32 | # 33 | # db.default.driver=org.h2.Driver 34 | # db.default.url="jdbc:h2:mem:play" 35 | # db.default.user=sa 36 | # db.default.password="" 37 | 38 | # JDBC settings 39 | db.default.driver="org.h2.Driver" 40 | db.default.url="jdbc:h2:file:./db/playapp" 41 | db.default.username="sa" 42 | db.default.password="" 43 | 44 | # Connection Pool settings 45 | db.default.poolInitialSize=10 46 | db.default.poolMaxSize=20 47 | db.default.poolConnectionTimeoutMillis=1000 48 | 49 | # Global settings 50 | scalikejdbc.global.loggingSQLAndTime.enabled=true 51 | scalikejdbc.global.loggingSQLAndTime.logLevel=info 52 | scalikejdbc.global.loggingSQLAndTime.warningEnabled=true 53 | scalikejdbc.global.loggingSQLAndTime.warningThresholdMillis=1000 54 | scalikejdbc.global.loggingSQLAndTime.warningLogLevel=warn 55 | 56 | # Evolutions 57 | # ~~~~~ 58 | # You can disable evolutions if needed 59 | play.evolutions.enabled=false 60 | 61 | # You can disable evolutions for a specific datasource if necessary 62 | # play.evolutions.db.default.enabled=false 63 | 64 | play.modules.enabled += "org.flywaydb.play.PlayModule" 65 | play.modules.enabled += "scalikejdbc.PlayModule" 66 | play.modules.enabled += "scalikejdbc.PlayFixtureModule" 67 | play.modules.enabled += "modules.GlobalModule" 68 | 69 | play.filters.disabled += "play.filters.csrf.CSRFFilter" 70 | play.filters.disabled += "play.filters.headers.SecurityHeadersFilter" -------------------------------------------------------------------------------- /app/utils/DBInitializer.scala: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import scalikejdbc._ 4 | 5 | object DBInitializer { 6 | 7 | def run() { 8 | DB readOnly { implicit s => 9 | try { 10 | sql"select 1 from programmer limit 1".map(_.long(1)).single.apply() 11 | } catch { 12 | case e: java.sql.SQLException => 13 | DB autoCommit { implicit s => 14 | sql""" 15 | create sequence programmer_id_seq start with 1; 16 | create table programmer ( 17 | id bigint not null default nextval('programmer_id_seq') primary key, 18 | name varchar(255) not null, 19 | company_id bigint, 20 | created_timestamp timestamp not null, 21 | deleted_timestamp timestamp 22 | ); 23 | 24 | create sequence company_id_seq start with 1; 25 | create table company ( 26 | id bigint not null default nextval('company_id_seq') primary key, 27 | name varchar(255) not null, 28 | url varchar(255), 29 | created_at timestamp not null, 30 | deleted_at timestamp 31 | ); 32 | 33 | create sequence skill_id_seq start with 1; 34 | create table skill ( 35 | id bigint not null default nextval('skill_id_seq') primary key, 36 | name varchar(255) not null, 37 | created_at timestamp not null, 38 | deleted_at timestamp 39 | ); 40 | 41 | create table programmer_skill ( 42 | programmer_id bigint not null, 43 | skill_id bigint not null, 44 | primary key(programmer_id, skill_id) 45 | ); 46 | 47 | insert into company (name, url, created_at) values ('Typesafe', 'http://typesafe.com/', current_timestamp); 48 | insert into company (name, url, created_at) values ('Oracle', 'http://www.oracle.com/', current_timestamp); 49 | insert into company (name, url, created_at) values ('Google', 'http://www.google.com/', current_timestamp); 50 | insert into company (name, url, created_at) values ('Microsoft', 'http://www.microsoft.com/', current_timestamp); 51 | 52 | insert into skill (name, created_at) values ('Scala', current_timestamp); 53 | insert into skill (name, created_at) values ('Java', current_timestamp); 54 | insert into skill (name, created_at) values ('Ruby', current_timestamp); 55 | insert into skill (name, created_at) values ('MySQL', current_timestamp); 56 | insert into skill (name, created_at) values ('PostgreSQL', current_timestamp); 57 | 58 | insert into programmer (name, company_id, created_timestamp) values ('Alice', 1, current_timestamp); 59 | insert into programmer (name, company_id, created_timestamp) values ('Bob', 2, current_timestamp); 60 | insert into programmer (name, company_id, created_timestamp) values ('Chris', 1, current_timestamp); 61 | 62 | insert into programmer_skill values (1, 1); 63 | insert into programmer_skill values (1, 2); 64 | insert into programmer_skill values (2, 2); 65 | """.execute.apply() 66 | } 67 | } 68 | } 69 | } 70 | 71 | } 72 | 73 | -------------------------------------------------------------------------------- /app/models/Skill.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import java.time.ZonedDateTime 4 | 5 | import scalikejdbc._ 6 | 7 | case class Skill( 8 | id: Long, 9 | name: String, 10 | createdAt: ZonedDateTime, 11 | deletedAt: Option[ZonedDateTime] = None) { 12 | 13 | def save()(implicit session: DBSession = Skill.autoSession): Skill = Skill.save(this)(session) 14 | def destroy()(implicit session: DBSession = Skill.autoSession): Unit = Skill.destroy(id)(session) 15 | } 16 | 17 | object Skill extends SQLSyntaxSupport[Skill] { 18 | 19 | def apply(s: SyntaxProvider[Skill])(rs: WrappedResultSet): Skill = apply(s.resultName)(rs) 20 | def apply(s: ResultName[Skill])(rs: WrappedResultSet): Skill = new Skill( 21 | id = rs.get(s.id), 22 | name = rs.get(s.name), 23 | createdAt = rs.get(s.createdAt), 24 | deletedAt = rs.get(s.deletedAt)) 25 | 26 | def opt(s: SyntaxProvider[Skill])(rs: WrappedResultSet): Option[Skill] = rs.longOpt(s.resultName.id).map(_ => apply(s.resultName)(rs)) 27 | 28 | val s = Skill.syntax("s") 29 | 30 | private val isNotDeleted = sqls.isNull(s.deletedAt) 31 | 32 | def find(id: Long)(implicit session: DBSession = autoSession): Option[Skill] = withSQL { 33 | select.from(Skill as s).where.eq(s.id, id).and.append(isNotDeleted) 34 | }.map(Skill(s)).single.apply() 35 | 36 | def findAll()(implicit session: DBSession = autoSession): List[Skill] = withSQL { 37 | select.from(Skill as s) 38 | .where.append(isNotDeleted) 39 | .orderBy(s.id) 40 | }.map(Skill(s)).list.apply() 41 | 42 | def countAll()(implicit session: DBSession = autoSession): Long = withSQL { 43 | select(sqls.count).from(Skill as s).where.append(isNotDeleted) 44 | }.map(rs => rs.long(1)).single.apply().get 45 | 46 | def findAllBy(where: SQLSyntax)(implicit session: DBSession = autoSession): List[Skill] = withSQL { 47 | select.from(Skill as s) 48 | .where.append(isNotDeleted).and.append(sqls"${where}") 49 | .orderBy(s.id) 50 | }.map(Skill(s)).list.apply() 51 | 52 | def countBy(where: SQLSyntax)(implicit session: DBSession = autoSession): Long = withSQL { 53 | select(sqls.count).from(Skill as s).where.append(isNotDeleted).and.append(sqls"${where}") 54 | }.map(_.long(1)).single.apply().get 55 | 56 | def create(name: String, createdAt: ZonedDateTime = ZonedDateTime.now)(implicit session: DBSession = autoSession): Skill = { 57 | val id = withSQL { 58 | insert.into(Skill).namedValues(column.name -> name, column.createdAt -> createdAt) 59 | }.updateAndReturnGeneratedKey.apply() 60 | 61 | Skill(id = id, name = name, createdAt = createdAt) 62 | } 63 | 64 | def save(m: Skill)(implicit session: DBSession = autoSession): Skill = { 65 | withSQL { 66 | update(Skill).set(column.name -> m.name).where.eq(column.id, m.id).and.isNull(column.deletedAt) 67 | }.update.apply() 68 | m 69 | } 70 | 71 | def destroy(id: Long)(implicit session: DBSession = autoSession): Unit = withSQL { 72 | update(Skill).set(column.deletedAt -> ZonedDateTime.now).where.eq(column.id, id) 73 | }.update.apply() 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/controllers/Programmers.scala: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import javax.inject.{ Inject, Singleton } 4 | import com.github.tototoshi.play2.json4s.native._ 5 | import models._ 6 | import org.json4s._ 7 | import org.json4s.ext.JodaTimeSerializers 8 | import play.api.data.Forms._ 9 | import play.api.data._ 10 | import play.api.data.validation.Constraints._ 11 | import play.api.mvc._ 12 | 13 | @Singleton 14 | class Programmers @Inject() (json4s: Json4s) extends Controller { 15 | 16 | import json4s.implicits._ 17 | implicit val formats = DefaultFormats ++ JodaTimeSerializers.all 18 | 19 | def all = Action { 20 | Ok(Extraction.decompose(Programmer.findAll)) 21 | } 22 | 23 | def show(id: Long) = Action { 24 | Programmer.find(id) match { 25 | case Some(programmer) => Ok(Extraction.decompose(programmer)) 26 | case _ => NotFound 27 | } 28 | } 29 | 30 | case class ProgrammerForm(name: String, companyId: Option[Long] = None) 31 | 32 | private val programmerForm = Form( 33 | mapping( 34 | "name" -> text.verifying(nonEmpty), 35 | "companyId" -> optional(longNumber))(ProgrammerForm.apply)(ProgrammerForm.unapply)) 36 | 37 | def create = Action { implicit req => 38 | programmerForm.bindFromRequest.fold( 39 | formWithErrors => BadRequest("invalid parameters"), 40 | form => { 41 | val programmer = Programmer.create(name = form.name, companyId = form.companyId) 42 | Created.withHeaders(LOCATION -> s"/programmers/${programmer.id}") 43 | NoContent 44 | }) 45 | } 46 | 47 | def addSkill(programmerId: Long, skillId: Long) = Action { 48 | Programmer.find(programmerId) match { 49 | case Some(programmer) => 50 | try { 51 | Skill.find(skillId).foreach(programmer.addSkill) 52 | Ok 53 | } catch { case scala.util.control.NonFatal(e) => Conflict } 54 | case _ => NotFound 55 | } 56 | } 57 | 58 | def deleteSkill(programmerId: Long, skillId: Long) = Action { 59 | Programmer.find(programmerId) match { 60 | case Some(programmer) => 61 | Skill.find(skillId).foreach(programmer.deleteSkill) 62 | Ok 63 | case _ => NotFound 64 | } 65 | } 66 | 67 | def joinCompany(programmerId: Long, companyId: Long) = Action { 68 | Company.find(companyId) match { 69 | case Some(company) => 70 | Programmer.find(programmerId) match { 71 | case Some(programmer) => 72 | programmer.copy(companyId = Some(company.id)).save() 73 | Ok 74 | case _ => BadRequest("Programmer not found!") 75 | } 76 | case _ => BadRequest("Company not found!") 77 | } 78 | } 79 | 80 | def leaveCompany(programmerId: Long) = Action { 81 | Programmer.find(programmerId) match { 82 | case Some(programmer) => 83 | programmer.copy(companyId = None).save() 84 | Ok 85 | case _ => BadRequest("Programmer not found!") 86 | } 87 | } 88 | 89 | def delete(id: Long) = Action { 90 | Programmer.find(id) match { 91 | case Some(programmer) => 92 | programmer.destroy() 93 | NoContent 94 | case _ => NotFound 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /app/models/Company.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import java.time.ZonedDateTime 4 | 5 | import scalikejdbc._ 6 | 7 | case class Company( 8 | id: Long, 9 | name: String, 10 | url: Option[String] = None, 11 | createdAt: ZonedDateTime, 12 | deletedAt: Option[ZonedDateTime] = None) { 13 | 14 | def save()(implicit session: DBSession = Company.autoSession): Company = Company.save(this)(session) 15 | def destroy()(implicit session: DBSession = Company.autoSession): Unit = Company.destroy(id)(session) 16 | } 17 | 18 | object Company extends SQLSyntaxSupport[Company] { 19 | 20 | def apply(c: SyntaxProvider[Company])(rs: WrappedResultSet): Company = apply(c.resultName)(rs) 21 | def apply(c: ResultName[Company])(rs: WrappedResultSet): Company = new Company( 22 | id = rs.get(c.id), 23 | name = rs.get(c.name), 24 | url = rs.get(c.url), 25 | createdAt = rs.get(c.createdAt), 26 | deletedAt = rs.get(c.deletedAt)) 27 | 28 | val c = Company.syntax("c") 29 | private val isNotDeleted = sqls.isNull(c.deletedAt) 30 | 31 | def find(id: Long)(implicit session: DBSession = autoSession): Option[Company] = withSQL { 32 | select.from(Company as c).where.eq(c.id, id).and.append(isNotDeleted) 33 | }.map(Company(c)).single.apply() 34 | 35 | def findAll()(implicit session: DBSession = autoSession): List[Company] = withSQL { 36 | select.from(Company as c) 37 | .where.append(isNotDeleted) 38 | .orderBy(c.id) 39 | }.map(Company(c)).list.apply() 40 | 41 | def countAll()(implicit session: DBSession = autoSession): Long = withSQL { 42 | select(sqls.count).from(Company as c).where.append(isNotDeleted) 43 | }.map(rs => rs.long(1)).single.apply().get 44 | 45 | def findAllBy(where: SQLSyntax)(implicit session: DBSession = autoSession): List[Company] = withSQL { 46 | select.from(Company as c) 47 | .where.append(isNotDeleted).and.append(sqls"${where}") 48 | .orderBy(c.id) 49 | }.map(Company(c)).list.apply() 50 | 51 | def countBy(where: SQLSyntax)(implicit session: DBSession = autoSession): Long = withSQL { 52 | select(sqls.count).from(Company as c).where.append(isNotDeleted).and.append(sqls"${where}") 53 | }.map(_.long(1)).single.apply().get 54 | 55 | def create(name: String, url: Option[String] = None, createdAt: ZonedDateTime = ZonedDateTime.now)(implicit session: DBSession = autoSession): Company = { 56 | val id = withSQL { 57 | insert.into(Company).namedValues( 58 | column.name -> name, 59 | column.url -> url, 60 | column.createdAt -> createdAt) 61 | }.updateAndReturnGeneratedKey.apply() 62 | 63 | Company(id = id, name = name, url = url, createdAt = createdAt) 64 | } 65 | 66 | def save(m: Company)(implicit session: DBSession = autoSession): Company = { 67 | withSQL { 68 | update(Company).set( 69 | column.name -> m.name, 70 | column.url -> m.url).where.eq(column.id, m.id).and.isNull(column.deletedAt) 71 | }.update.apply() 72 | m 73 | } 74 | 75 | def destroy(id: Long)(implicit session: DBSession = autoSession): Unit = withSQL { 76 | update(Company).set(column.deletedAt -> ZonedDateTime.now).where.eq(column.id, id) 77 | }.update.apply() 78 | 79 | } 80 | -------------------------------------------------------------------------------- /test/models/ProgrammerSpec.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import scalikejdbc.specs2.mutable.AutoRollback 4 | import org.specs2.mutable._ 5 | import scalikejdbc._ 6 | 7 | class ProgrammerSpec extends Specification with settings.DBSettings { 8 | 9 | val _p = Programmer.p 10 | 11 | trait AutoRollbackWithFixture extends AutoRollback { 12 | override def fixture(implicit session: DBSession) { 13 | applyUpdate(delete from ProgrammerSkill) 14 | applyUpdate(delete from Programmer) 15 | applyUpdate(delete from Skill) 16 | applyUpdate(delete from Company) 17 | 18 | val scala = Skill.create("Scala") 19 | val java = Skill.create("Java") 20 | val ruby = Skill.create("Ruby") 21 | 22 | val programmer = Programmer.create("seratch") 23 | programmer.addSkill(scala) 24 | programmer.addSkill(java) 25 | programmer.addSkill(ruby) 26 | 27 | val company = Company.create("M3") 28 | programmer.copy(companyId = Some(company.id)).save() 29 | 30 | Programmer.create("no_skill_programmer") 31 | 32 | val anon = Programmer.create("anonymous") 33 | anon.addSkill(java) 34 | 35 | Programmer.create("should_be_deteled").destroy() 36 | } 37 | } 38 | 39 | "Programmer" should { 40 | "find with skills" in new AutoRollbackWithFixture { 41 | val seratch = Programmer.findAllBy(sqls.eq(_p.name, "seratch")).head 42 | seratch.skills.size should_== (3) 43 | } 44 | "find no skill programmers" in new AutoRollbackWithFixture { 45 | val noSkillProgrammers = Programmer.findNoSkillProgrammers() 46 | noSkillProgrammers.size should_== (1) 47 | } 48 | "find by primary keys" in new AutoRollbackWithFixture { 49 | val id = Programmer.findAll().head.id 50 | val maybeFound = Programmer.find(id) 51 | maybeFound.isDefined should beTrue 52 | } 53 | "find all records" in new AutoRollbackWithFixture { 54 | val allResults = Programmer.findAll() 55 | allResults.size should_== (3) 56 | } 57 | "count all records" in new AutoRollbackWithFixture { 58 | val count = Programmer.countAll() 59 | count should_== (3L) 60 | } 61 | "find by where clauses" in new AutoRollbackWithFixture { 62 | val results = Programmer.findAllBy(sqls.isNotNull(_p.companyId)) 63 | results.head.name should_== ("seratch") 64 | } 65 | "count by where clauses" in new AutoRollbackWithFixture { 66 | val count = Programmer.countBy(sqls.isNull(_p.companyId)) 67 | count should_== (2L) 68 | } 69 | "create new record" in new AutoRollbackWithFixture { 70 | val martin = Programmer.create("Martin") 71 | martin.id should not beNull 72 | } 73 | "save a record" in new AutoRollbackWithFixture { 74 | val entity = Programmer.findAll().head 75 | entity.copy(name = "Bob").save() 76 | val updated = Programmer.find(entity.id).get 77 | updated.name should_== ("Bob") 78 | } 79 | "destroy a record" in new AutoRollbackWithFixture { 80 | val entity = Programmer.findAll().head 81 | entity.destroy() 82 | val shouldBeNone = Programmer.find(entity.id) 83 | shouldBeNone.isDefined should beFalse 84 | Programmer.countAll should_== (2L) 85 | } 86 | } 87 | 88 | } 89 | 90 | -------------------------------------------------------------------------------- /test/settings/DBSettings.scala: -------------------------------------------------------------------------------- 1 | package settings 2 | 3 | import scalikejdbc._, config._ 4 | 5 | trait DBSettings { 6 | DBSettings.initialize() 7 | } 8 | 9 | object DBSettings { 10 | 11 | private var isInitialized = false 12 | 13 | def initialize(): Unit = this.synchronized { 14 | if (isInitialized) return 15 | DBs.setupAll() 16 | GlobalSettings.loggingSQLErrors = false 17 | GlobalSettings.sqlFormatter = SQLFormatterSettings("utils.HibernateSQLFormatter") 18 | DBInitializer.run() 19 | isInitialized = true 20 | } 21 | 22 | } 23 | 24 | object DBInitializer { 25 | 26 | def run() { 27 | DB readOnly { implicit s => 28 | try { 29 | sql"select 1 from programmer limit 1".map(_.long(1)).single.apply() 30 | } catch { 31 | case e: java.sql.SQLException => 32 | DB autoCommit { implicit s => 33 | sql""" 34 | create sequence programmer_id_seq start with 1; 35 | create table programmer ( 36 | id bigint not null default nextval('programmer_id_seq') primary key, 37 | name varchar(255) not null, 38 | company_id bigint, 39 | created_timestamp timestamp not null, 40 | deleted_timestamp timestamp 41 | ); 42 | 43 | create sequence company_id_seq start with 1; 44 | create table company ( 45 | id bigint not null default nextval('company_id_seq') primary key, 46 | name varchar(255) not null, 47 | url varchar(255), 48 | created_at timestamp not null, 49 | deleted_at timestamp 50 | ); 51 | 52 | create sequence skill_id_seq start with 1; 53 | create table skill ( 54 | id bigint not null default nextval('skill_id_seq') primary key, 55 | name varchar(255) not null, 56 | created_at timestamp not null, 57 | deleted_at timestamp 58 | ); 59 | 60 | create table programmer_skill ( 61 | programmer_id bigint not null, 62 | skill_id bigint not null, 63 | primary key(programmer_id, skill_id) 64 | ); 65 | 66 | insert into company (name, url, created_at) values ('Typesafe', 'http://typesafe.com/', current_timestamp); 67 | insert into company (name, url, created_at) values ('Oracle', 'http://www.oracle.com/', current_timestamp); 68 | insert into company (name, url, created_at) values ('Google', 'http://www.google.com/', current_timestamp); 69 | insert into company (name, url, created_at) values ('Microsoft', 'http://www.microsoft.com/', current_timestamp); 70 | 71 | insert into skill (name, created_at) values ('Scala', current_timestamp); 72 | insert into skill (name, created_at) values ('Java', current_timestamp); 73 | insert into skill (name, created_at) values ('Ruby', current_timestamp); 74 | insert into skill (name, created_at) values ('MySQL', current_timestamp); 75 | insert into skill (name, created_at) values ('PostgreSQL', current_timestamp); 76 | 77 | insert into programmer (name, company_id, created_timestamp) values ('Alice', 1, current_timestamp); 78 | insert into programmer (name, company_id, created_timestamp) values ('Bob', 2, current_timestamp); 79 | insert into programmer (name, company_id, created_timestamp) values ('Chris', 1, current_timestamp); 80 | 81 | insert into programmer_skill values (1, 1); 82 | insert into programmer_skill values (1, 2); 83 | insert into programmer_skill values (2, 2); 84 | """.execute.apply() 85 | } 86 | } 87 | } 88 | } 89 | 90 | } 91 | 92 | -------------------------------------------------------------------------------- /app/views/index.scala.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello ScalikeJDBC! 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 |
29 |

This is a ScalikeJDBC sample app which is built with Play2, Backbone.js, CoffeeScript and Bootstrap.

30 |
31 | Now Loading... 32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 109 | 110 | 141 | 142 | 175 | 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /app/assets/javascripts/main.coffee: -------------------------------------------------------------------------------- 1 | # 2 | # Backbone main.js 3 | # 4 | 5 | # --- Router --- 6 | 7 | AppRouter = Backbone.Router.extend 8 | routes: 9 | "" : "showProgrammers" 10 | "programmers" : "showProgrammers" 11 | "companies" : "showCompanies" 12 | "skills" : "showSkills" 13 | 14 | showProgrammers: -> new ProgrammersView().renew() 15 | showCompanies: -> new CompaniesView().renew() 16 | showSkills: -> new SkillsView().renew() 17 | 18 | # --- Programmers --- 19 | 20 | Programmer = Backbone.Model.extend 21 | initialize: -> @on('sync', -> new ProgrammersView().renew()) 22 | urlRoot: '/programmers' 23 | validate: (attrs, options) -> 24 | 'name is required' unless attrs.name 25 | 26 | addSkill: (skillId) -> 27 | route = jsRoutes.controllers.Programmers.addSkill(@id, skillId) 28 | $.ajax 29 | url: route.url, type: route.method, 30 | success: (response) => new ProgrammersView().renew() 31 | error: (response) => console.log "POST /programmers/#{@id}/skills/#{skillId} failure (status: #{response.statusText})" 32 | 33 | deleteSkill: (skillId) -> 34 | route = jsRoutes.controllers.Programmers.deleteSkill(@id, skillId) 35 | $.ajax 36 | url: route.url, type: route.method, 37 | success: (response) => new ProgrammersView().renew() 38 | error: (response) => console.log "DELETE /programmers/#{@id}/skills/#{skillId} failure (status: #{response.statusText})" 39 | 40 | changeCompany: (companyId) -> 41 | if companyId then route = jsRoutes.controllers.Programmers.joinCompany(@id, companyId) 42 | else route = jsRoutes.controllers.Programmers.leaveCompany(@id) 43 | $.ajax 44 | url: route.url, type: route.method, 45 | success: (response) => $("#changeCompanyHolder#{@id}").append($('')) 46 | error: (response) => console.log "/programmers/#{@id}/company failure (status: #{response.statusText})" 47 | 48 | Programmers = Backbone.Collection.extend 49 | model: Programmer 50 | url: '/programmers' 51 | 52 | ProgrammerView = Backbone.View.extend 53 | render: (param) -> $('#main').html(@$el.html(_.template($('#main_programmer').html(), param))) 54 | 55 | ProgrammersView = Backbone.View.extend 56 | events: 57 | "submit .addProgrammer" : "addProgrammer" 58 | "blur .addSkill" : "addSkill" 59 | "click .deleteSkill" : "deleteSkill" 60 | "blur .changeCompany" : "changeCompany" 61 | "click .deleteProgrammer" : "deleteProgrammer" 62 | 63 | addProgrammer: (event) -> 64 | event.preventDefault() 65 | model = new Programmer(name: $('#newName').val(), companyId: $('#newCompanyId').val()) 66 | if model.isValid() then model.save() 67 | else window.alert model.validationError 68 | 69 | addSkill: (event) -> 70 | event.preventDefault() 71 | id = $(event.currentTarget).data('id') 72 | skillId = $(event.currentTarget).val() 73 | programmers.get(id).addSkill(skillId) 74 | 75 | deleteSkill: (event) -> 76 | event.preventDefault() 77 | id = $(event.currentTarget).data('programmer-id') 78 | skillId = $(event.currentTarget).data('skill-id') 79 | programmers.get(id).deleteSkill(skillId) 80 | 81 | changeCompany: (event) -> 82 | event.preventDefault() 83 | id = $(event.currentTarget).data('id') 84 | companyId = $(event.currentTarget).val() 85 | programmers.get(id).changeCompany(companyId) 86 | 87 | deleteProgrammer: (event) -> 88 | if window.confirm("Are you sure?") 89 | event.preventDefault() 90 | id = $(event.currentTarget).data('id') 91 | programmers.get(id).destroy() 92 | 93 | render: (param) -> 94 | @$el.html(_.template($('#main_programmers').html(), param)) 95 | 96 | renew: -> 97 | $.when( 98 | programmers.fetch(), companies.fetch(), skills.fetch() 99 | ).done( -> 100 | $('#main').html(new ProgrammersView().render( 101 | programmers: programmers 102 | companies: companies 103 | skills: skills 104 | )) 105 | ) 106 | 107 | # --- Companies --- 108 | 109 | Company = Backbone.Model.extend 110 | initialize: -> @on('sync', -> new CompaniesView().renew()) 111 | urlRoot: '/companies' 112 | validate: (attrs, options) -> 113 | 'name is required' unless attrs.name 114 | 115 | Companies = Backbone.Collection.extend 116 | model: Company 117 | url: '/companies' 118 | 119 | CompaniesView = Backbone.View.extend 120 | events: 121 | "submit .addCompany" : "addCompany" 122 | "click .deleteCompany" : "deleteCompany" 123 | 124 | addCompany: (event) -> 125 | event.preventDefault() 126 | model = new Company(name: $('#newName').val()) 127 | if model.isValid() then model.save() 128 | else window.alert(model.validationError) 129 | 130 | deleteCompany: (event) -> 131 | if window.confirm("Are you sure?") 132 | event.preventDefault() 133 | id = $(event.currentTarget).data('id') 134 | companies.get(id).destroy() 135 | 136 | render: (param) -> 137 | $('#main').html(@$el.html(_.template($('#main_companies').html(), param))) 138 | 139 | renew: -> 140 | companies.fetch 141 | success: (companies) -> new CompaniesView().render({companies: companies}) 142 | error: (response) -> console.log "GET /companies failure (status: #{response.statusText})" 143 | 144 | # --- Skills --- 145 | 146 | Skill = Backbone.Model.extend 147 | initialize: -> @on('sync', -> new SkillsView().renew()) 148 | urlRoot: '/skills' 149 | validate: (attrs, options) -> 150 | 'name is required' unless attrs.name 151 | 152 | Skills = Backbone.Collection.extend 153 | model: Skill 154 | url: '/skills' 155 | 156 | SkillsView = Backbone.View.extend 157 | events: 158 | "submit .addSkill" : "addSkill" 159 | "click .deleteSkill" : "deleteSkill" 160 | 161 | addSkill: (event) -> 162 | event.preventDefault() 163 | model = new Skill(name: $('#newName').val()) 164 | if model.isValid() then model.save() 165 | else window.alert model.validationError 166 | 167 | deleteSkill: (event) -> 168 | if window.confirm("Are you sure?") 169 | event.preventDefault() 170 | id = $(event.currentTarget).data('id') 171 | skills.get(id).destroy() 172 | 173 | render: (param) -> 174 | $('#main').html(@$el.html(_.template($('#main_skills').html(), param))) 175 | 176 | renew: -> 177 | skills.fetch 178 | success: (skills) -> new SkillsView().render({skills: skills}) 179 | error: (response) -> console.log "GET /skills failure (status: #{response.statusText})" 180 | 181 | 182 | # --- Initialize --- 183 | 184 | programmers = new Programmers() 185 | companies = new Companies() 186 | skills = new Skills() 187 | 188 | $ -> 189 | appRouter = new AppRouter() 190 | Backbone.history.start 191 | pushHistory: true 192 | 193 | -------------------------------------------------------------------------------- /app/models/Programmer.scala: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import java.time.ZonedDateTime 4 | 5 | import scalikejdbc._ 6 | 7 | case class Programmer( 8 | id: Long, 9 | name: String, 10 | companyId: Option[Long] = None, 11 | company: Option[Company] = None, 12 | skills: Seq[Skill] = Nil, 13 | createdAt: ZonedDateTime, 14 | deletedAt: Option[ZonedDateTime] = None) { 15 | 16 | def save()(implicit session: DBSession = Programmer.autoSession): Programmer = Programmer.save(this)(session) 17 | def destroy()(implicit session: DBSession = Programmer.autoSession): Unit = Programmer.destroy(id)(session) 18 | 19 | private val (ps, p, s) = (ProgrammerSkill.ps, Programmer.p, Skill.s) 20 | private val column = ProgrammerSkill.column 21 | 22 | def addSkill(skill: Skill)(implicit session: DBSession = Programmer.autoSession): Unit = withSQL { 23 | insert.into(ProgrammerSkill) 24 | .namedValues(column.programmerId -> id, column.skillId -> skill.id) 25 | }.update.apply() 26 | 27 | def deleteSkill(skill: Skill)(implicit session: DBSession = Programmer.autoSession): Unit = withSQL { 28 | delete.from(ProgrammerSkill) 29 | .where.eq(column.programmerId, id).and.eq(column.skillId, skill.id) 30 | }.update.apply() 31 | 32 | } 33 | 34 | object Programmer extends SQLSyntaxSupport[Programmer] { 35 | 36 | // If the table name is same as snake_case'd name of this companion object, you don't need to specify tableName explicitly. 37 | override val tableName = "programmer" 38 | // By default, column names will be cached from meta data automatically when accessing this table for the first time. 39 | override val columns = Seq("id", "name", "company_id", "created_timestamp", "deleted_timestamp") 40 | // If you need mapping between fields and columns, use nameConverters. 41 | override val nameConverters = Map("At$" -> "_timestamp") 42 | 43 | // simple extractor 44 | def apply(p: SyntaxProvider[Programmer])(rs: WrappedResultSet): Programmer = apply(p.resultName)(rs) 45 | def apply(p: ResultName[Programmer])(rs: WrappedResultSet): Programmer = new Programmer( 46 | id = rs.get(p.id), 47 | name = rs.get(p.name), 48 | companyId = rs.get(p.companyId), 49 | createdAt = rs.get(p.createdAt), 50 | deletedAt = rs.get(p.deletedAt)) 51 | 52 | // join query with company table 53 | def apply(p: SyntaxProvider[Programmer], c: SyntaxProvider[Company])(rs: WrappedResultSet): Programmer = { 54 | apply(p.resultName)(rs).copy(company = rs.longOpt(c.resultName.id).flatMap { _ => 55 | if (rs.timestampOpt(c.resultName.deletedAt).isEmpty) Some(Company(c)(rs)) else None 56 | }) 57 | } 58 | 59 | // SyntaxProvider objects 60 | val p = Programmer.syntax("p") 61 | 62 | private val (c, s, ps) = (Company.c, Skill.s, ProgrammerSkill.ps) 63 | 64 | // reusable part of SQL 65 | private val isNotDeleted = sqls.isNull(p.deletedAt) 66 | 67 | // find by primary key 68 | def find(id: Long)(implicit session: DBSession = autoSession): Option[Programmer] = withSQL { 69 | select 70 | .from(Programmer as p) 71 | .leftJoin(Company as c).on(p.companyId, c.id) 72 | .leftJoin(ProgrammerSkill as ps).on(ps.programmerId, p.id) 73 | .leftJoin(Skill as s).on(sqls.eq(ps.skillId, s.id).and.isNull(s.deletedAt)) 74 | .where.eq(p.id, id).and.append(isNotDeleted) 75 | }.one(Programmer(p, c)) 76 | .toMany(Skill.opt(s)) 77 | .map { (programmer, skills) => programmer.copy(skills = skills) } 78 | .single.apply() 79 | 80 | // programmer with company(optional) with skills(many) 81 | def findAll()(implicit session: DBSession = autoSession): List[Programmer] = withSQL { 82 | select 83 | .from(Programmer as p) 84 | .leftJoin(Company as c).on(p.companyId, c.id) 85 | .leftJoin(ProgrammerSkill as ps).on(ps.programmerId, p.id) 86 | .leftJoin(Skill as s).on(sqls.eq(ps.skillId, s.id).and.isNull(s.deletedAt)) 87 | .where.append(isNotDeleted) 88 | .orderBy(p.id) 89 | }.one(Programmer(p, c)) 90 | .toMany(Skill.opt(s)) 91 | .map { (programmer, skills) => programmer.copy(skills = skills) } 92 | .list.apply() 93 | 94 | def findNoSkillProgrammers()(implicit session: DBSession = autoSession): List[Programmer] = withSQL { 95 | select 96 | .from(Programmer as p) 97 | .leftJoin(Company as c).on(p.companyId, c.id) 98 | .where.notIn(p.id, select(sqls.distinct(ps.programmerId)).from(ProgrammerSkill as ps)) 99 | .and.append(isNotDeleted) 100 | .orderBy(p.id) 101 | }.map(Programmer(p, c)).list.apply() 102 | 103 | def countAll()(implicit session: DBSession = autoSession): Long = withSQL { 104 | select(sqls.count).from(Programmer as p).where.append(isNotDeleted) 105 | }.map(rs => rs.long(1)).single.apply().get 106 | 107 | def findAllBy(where: SQLSyntax, withCompany: Boolean = true)(implicit session: DBSession = autoSession): List[Programmer] = withSQL { 108 | select 109 | .from[Programmer](Programmer as p) 110 | .map(sql => if (withCompany) sql.leftJoin(Company as c).on(p.companyId, c.id) else sql) // dynamic 111 | .leftJoin(ProgrammerSkill as ps).on(ps.programmerId, p.id) 112 | .leftJoin(Skill as s).on(sqls.eq(ps.skillId, s.id).and.isNull(s.deletedAt)) 113 | .where.append(isNotDeleted).and.append(sqls"${where}") 114 | }.one(rs => if (withCompany) Programmer(p, c)(rs) else Programmer(p)(rs)) 115 | .toMany(Skill.opt(s)) 116 | .map { (programmer, skills) => programmer.copy(skills = skills) } 117 | .list.apply() 118 | 119 | def countBy(where: SQLSyntax)(implicit session: DBSession = autoSession): Long = withSQL { 120 | select(sqls.count).from(Programmer as p).where.append(isNotDeleted).and.append(sqls"${where}") 121 | }.map(_.long(1)).single.apply().get 122 | 123 | def create(name: String, companyId: Option[Long] = None, createdAt: ZonedDateTime = ZonedDateTime.now)(implicit session: DBSession = autoSession): Programmer = { 124 | if (companyId.isDefined && Company.find(companyId.get).isEmpty) { 125 | throw new IllegalArgumentException(s"Company is not found! (companyId: ${companyId})") 126 | } 127 | val id = withSQL { 128 | insert.into(Programmer).namedValues( 129 | column.name -> name, 130 | column.companyId -> companyId, 131 | column.createdAt -> createdAt) 132 | }.updateAndReturnGeneratedKey.apply() 133 | 134 | Programmer( 135 | id = id, 136 | name = name, 137 | companyId = companyId, 138 | company = companyId.flatMap(id => Company.find(id)), 139 | createdAt = createdAt) 140 | } 141 | 142 | def save(m: Programmer)(implicit session: DBSession = autoSession): Programmer = { 143 | withSQL { 144 | update(Programmer).set( 145 | column.name -> m.name, 146 | column.companyId -> m.companyId).where.eq(column.id, m.id).and.isNull(column.deletedAt) 147 | }.update.apply() 148 | m 149 | } 150 | 151 | def destroy(id: Long)(implicit session: DBSession = autoSession): Unit = withSQL { 152 | update(Programmer).set(column.deletedAt -> ZonedDateTime.now).where.eq(column.id, id) 153 | }.update.apply() 154 | 155 | } 156 | --------------------------------------------------------------------------------