├── 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 |
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 | 
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 |
--------------------------------------------------------------------------------