-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add h2 db activemodel tests, where, find by id (#11)
* Add h2 db activemodel tesdts * add find by id, DRY * code cleanup * moar tests * Add show view * Update the readme
- Loading branch information
Showing
11 changed files
with
211 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,32 +1,85 @@ | ||
package kales | ||
|
||
import org.jdbi.v3.core.Handle | ||
import org.jdbi.v3.core.Jdbi | ||
import org.jdbi.v3.core.h2.H2DatabasePlugin | ||
import org.jdbi.v3.core.kotlin.KotlinPlugin | ||
import org.jdbi.v3.core.kotlin.mapTo | ||
import org.jdbi.v3.postgres.PostgresPlugin | ||
import org.yaml.snakeyaml.Yaml | ||
|
||
abstract class ApplicationRecord { | ||
companion object { | ||
val JDBI: Jdbi = Jdbi.create(dbConnectionString()).installPlugins() | ||
val JDBI: Jdbi = Jdbi.create(dbConnectionString()) | ||
.installPlugin(PostgresPlugin()) | ||
.installPlugin(H2DatabasePlugin()) | ||
.installPlugin(KotlinPlugin()) | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
private fun dbConnectionString(): String { | ||
val yaml = Yaml() | ||
val stream = ApplicationRecord::class.java.classLoader.getResourceAsStream("database.yml") | ||
val data = yaml.load<Map<String, Any>>(stream) | ||
val devData = data["development"] as Map<String, String> | ||
val host = devData["host"] | ||
val adapter = devData["adapter"] | ||
val database = devData["database"] | ||
val username = devData["username"] | ||
val password = devData["password"] | ||
return "jdbc:$adapter://$host/$database?user=$username&password=$password" | ||
// TODO handle muliple environments | ||
val devData = data["development"] as? Map<String, String> ?: throwMissingField("development") | ||
val adapter = devData["adapter"] ?: throwMissingField("adapter") | ||
val host = devData["host"] ?: throwMissingField("host") | ||
val database = devData["database"] ?: throwMissingField("database") | ||
return if (adapter == "h2") { | ||
"jdbc:$adapter:$host:$database" | ||
} else { | ||
val username = devData["username"] ?: "" | ||
val password = devData["password"] ?: "" | ||
"jdbc:$adapter://$host/$database?user=$username&password=$password" | ||
} | ||
} | ||
|
||
private fun throwMissingField(name: String): Nothing = | ||
throw IllegalArgumentException( | ||
"Please set a value for the field '$name' in the file database.yml") | ||
|
||
inline fun <reified T : ApplicationRecord> allRecords(): List<T> { | ||
JDBI.open().use { | ||
val tableName = T::class.simpleName!!.toLowerCase() | ||
return it.createQuery("select * from ${tableName}s").mapTo<T>().list() | ||
useJdbi { | ||
val tableName = toTableName<T>() | ||
return it.createQuery("select * from $tableName").mapTo<T>().list() | ||
} | ||
} | ||
|
||
inline fun <reified T : ApplicationRecord> whereRecords(clause: Map<String, Any>): List<T> { | ||
useJdbi { | ||
val tableName = toTableName<T>() | ||
val whereClause = clause.keys.joinToString(" and ") { k -> "$k = :$k" } | ||
val query = it.createQuery("select * from $tableName where $whereClause") | ||
clause.forEach { k, v -> query.bind(k, v) } | ||
return query.mapTo<T>().list() | ||
} | ||
} | ||
|
||
inline fun <reified T : ApplicationRecord> createRecord(values: Map<String, Any>): Int { | ||
useJdbi { | ||
val tableName = toTableName<T>() | ||
val cols = values.keys.joinToString(prefix = "(", postfix = ")") | ||
val refs = values.keys.joinToString(prefix = "(", postfix = ")") { k -> ":$k" } | ||
val update = it.createUpdate("insert into $tableName $cols values $refs") | ||
values.forEach { k, v -> update.bind(k, v) } | ||
return update.execute() | ||
} | ||
} | ||
|
||
/** TODO I think Rails raises RecordNotFound in this case instead of returning null. Should we do the same? */ | ||
inline fun <reified T : ApplicationRecord> findRecord(id: Int): T? { | ||
useJdbi { | ||
val tableName = toTableName<T>() | ||
return it.createQuery("select * from $tableName where id = :id") | ||
.bind("id", id) | ||
.mapTo<T>() | ||
.findFirst() | ||
.orElse(null) | ||
} | ||
} | ||
|
||
inline fun <T> useJdbi(block: (Handle) -> T) = JDBI.open().use { block(it) } | ||
|
||
inline fun <reified T> toTableName() = "${T::class.simpleName!!.toLowerCase()}s" | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
activemodel/src/test/kotlin/kales/ApplicationRecordTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package kales | ||
|
||
import com.google.common.truth.Truth.assertThat | ||
import org.junit.Test | ||
|
||
class ApplicationRecordTest { | ||
@Test fun `test no records`() { | ||
withTestDb { | ||
assertThat(TestModel.all()).isEmpty() | ||
assertThat(TestModel.where("id" to 1)).isEmpty() | ||
assertThat(TestModel.find(1)).isNull() | ||
assertThat(TestModel.find(2)).isNull() | ||
} | ||
} | ||
|
||
@Test fun `test all`() { | ||
withTestDb { | ||
TestModel.create("name" to "Hello World") | ||
TestModel.create("name" to "Ping Pong") | ||
val expectedModel1 = TestModel(1, "Hello World") | ||
val expectedModel2 = TestModel(2, "Ping Pong") | ||
assertThat(TestModel.all()).containsExactly(expectedModel1, expectedModel2) | ||
} | ||
} | ||
|
||
@Test fun `test where`() { | ||
withTestDb { | ||
TestModel.create("name" to "Hello World") | ||
TestModel.create("name" to "Ping Pong") | ||
val expectedModel1 = TestModel(1, "Hello World") | ||
val expectedModel2 = TestModel(2, "Ping Pong") | ||
assertThat(TestModel.where("name" to "Hello World")).containsExactly(expectedModel1) | ||
assertThat(TestModel.where("id" to 1, "name" to "Hello World")).containsExactly(expectedModel1) | ||
assertThat(TestModel.where("id" to 1)).containsExactly(expectedModel1) | ||
assertThat(TestModel.where("id" to 2)).containsExactly(expectedModel2) | ||
assertThat(TestModel.where("id" to 3)).isEmpty() | ||
} | ||
} | ||
|
||
@Test fun `test find`() { | ||
withTestDb { | ||
TestModel.create("name" to "Hello World") | ||
TestModel.create("name" to "Ping Pong") | ||
assertThat(TestModel.find(1)).isEqualTo(TestModel(1, "Hello World")) | ||
assertThat(TestModel.find(2)).isEqualTo(TestModel(2, "Ping Pong")) | ||
assertThat(TestModel.find(3)).isNull() | ||
} | ||
} | ||
|
||
private fun withTestDb(block: () -> Unit) { | ||
ApplicationRecord.JDBI.withHandle<Any, RuntimeException> { | ||
it.execute("CREATE TABLE testmodels (id INTEGER PRIMARY KEY AUTO_INCREMENT, name VARCHAR)") | ||
block() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package kales | ||
|
||
data class TestModel( | ||
val id: Int, | ||
val name: String | ||
) : ApplicationRecord() { | ||
companion object { | ||
fun all() = allRecords<TestModel>() | ||
|
||
fun where(vararg clause: Pair<String, Any>) = whereRecords<TestModel>(clause.toMap()) | ||
|
||
fun create(vararg values: Pair<String, Any>) = createRecord<TestModel>(values.toMap()) | ||
|
||
fun find(id: Int) = findRecord<TestModel>(id) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
development: | ||
adapter: h2 | ||
host: mem | ||
database: test | ||
username: | ||
password: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
sampleapp/src/main/kotlin/kales/sample/app/views/example/ShowView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package kales.sample.app.views.example | ||
|
||
import kales.actionview.ActionView | ||
import kotlinx.html.FlowContent | ||
import kotlinx.html.h2 | ||
import kotlinx.html.h3 | ||
|
||
class ShowView( | ||
bindings: ShowViewModel? = ShowViewModel() | ||
) : ActionView<ShowViewModel>(bindings) { | ||
override fun render(content: FlowContent) { | ||
content.apply { | ||
h2 { +"Details" } | ||
h3 { | ||
+"Video ${bindings?.video}" | ||
} | ||
} | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
sampleapp/src/main/kotlin/kales/sample/app/views/example/ShowViewModel.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package kales.sample.app.views.example | ||
|
||
import kales.actionview.ViewModel | ||
import kales.sample.app.models.Video | ||
|
||
data class ShowViewModel( | ||
val video: Video? = null | ||
) : ViewModel |