How do we compose queries in Kotlin Exposed? - kotlin

I would like to create something similar to Ruby's ActiveRecord Scopes using Kotlin Exposed.
For example I would like to break the following query up so that the first part acts like a scope.
This query returns what I want.
val m1 = Measurement.wrapRows(Measurements.innerJoin(Runs).select {
((exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})) and Measurements.name.eq("someName")
I would like to use this part as a scope:
val q1 = Measurements.innerJoin(Runs).select {
((exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
}))
}
and then be able to refine the query using the q1 "scope"
so something like this:
val q2 = q1.having { Measurements.name.eq("someName") } // which does not work
Ultimately I would like to push this down into either the Measurements object or the Measurement class so I can do something like this
Measurement.withDefaultRegion.where( Measurements.name.eq("someName")

I was able to get what I wanted by adding a couple of functions to the model's companion object.
The first one provides the "scope"
fun defaultRegion() :Op<Boolean> {
return Op.build {(exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})}
}
The second function does the query using the scope and any refinements passed in and returns a "collection" of objects.
fun withDefaultRegionAnd( refinedBy: (SqlExpressionBuilder.()->Op<Boolean>)) : SizedIterable<Measurement> {
return Measurement.wrapRows(Measurements.innerJoin(Runs).select(Measurement.defaultRegion() and SqlExpressionBuilder.refinedBy() ))
}
At the client level I can simply do this:
val measurements = Measurement.withDefaultRegionAnd { Measurements.name.eq("someName") }
Here are the nearly table object and entity classes:
object Measurements : IntIdTable("measurements") {
val sequelId = integer("id").primaryKey()
val run = reference("run_id", Runs)
// more properties
}
class Measurement(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<Measurement>(Measurements) {
fun defaultRegion() :Op<Boolean> {
return Op.build {(exists(Tags.select { Tags.run.eq(Runs.sequelId) and Tags.name.eq("region") and Tags.value.eq("default") })) or notExists(Tags.select {
Tags.run.eq(Runs.sequelId) and Tags.name.eq("region")
})}
}
fun withDefaultRegionAnd( refinedBy: (SqlExpressionBuilder.()->Op<Boolean>)) : SizedIterable<Measurement> {
return Measurement.wrapRows(Measurements.innerJoin(Runs).select(Measurement.defaultRegion() and SqlExpressionBuilder.refinedBy() ))
}
}
var run by Run referencedOn Measurements.run
var name by Measurements.name
// more properties
}

Related

Should I get rid of big switch case?

I have a factory which includes many HTML attribute generators which returns one of them based on the type of attribute, so I wanted to see if there is a better way of doing this.
class AttributeHtmlGeneratorFactory {
fun create(property: String): AttributeHtmlGenerator {
when (property) {
"animation" -> {
return AnimationHtmlGenerator()
}
...
"left", "top" -> {
return PositionHtmlGenerator()
}
...
"scaleX" , "scaleY", ... , "direction" -> {
return UnusedAttributesHtmlGenerator()
}
this when switch has like 20 switch cases in it.
this is the interface which all these classes are using
interface AttributeHtmlGenerator {
fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel ): String
}
and this is where and how I'm using all of these:
var result = ""
HtmlComponentDataModel::class.memberProperties.forEach { member ->
val generator = AttributeHtmlGeneratorFactory().create(member.name)
result = result.plus(generator.generateHtml(member, component))
}
return result
also, this is a simple implementation of the interface:
class ButtonFillHtmlGenerator : AttributeHtmlGenerator {
override fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel): String {
var result = ""
member.get(component)?.let {
result = result.plus("background-color:${it};")
}
return result
}
}
is there anyway to make this better?
If you just want to reformat the when statement, I suggest you you do like this:
fun create(property: String): AttributeHtmlGenerator = when (property)
{
"animation" -> AnimationHtmlGenerator()
"left", "top" -> PositionHtmlGenerator()
"scaleX", "scaleY", "direction" -> UnusedAttributesHtmlGenerator()
else -> error("No generator found for property $property")
}
If you want to split this logic across modules, you would use a Map.
class AttributeHtmlGeneratorFactory {
private val generatorMap = mutableMapOf<String, () -> AttributeHtmlGenerator>()
init {
assignGeneratorToProperties("animation") { AnimationHtmlGenerator() }
assignGeneratorToProperties("left", "top") { PositionHtmlGenerator() }
}
fun create(property: String): AttributeHtmlGenerator {
return generatorMap[property]?.invoke() ?: error("No generator found for property $property")
}
fun assignGeneratorToProperties(vararg properties: String, provider: () -> AttributeHtmlGenerator) {
properties.forEach {
generatorMap[it] = provider
}
}
}
This way you can call assignGeneratorToProperties in parts of the code and thus split the initialization logic.
Performance-wise, when/if-else statements are really performant when you have a few cases but a HashMap outperforms them for a lot of elements. You decide what to use depending on your case.

Change source Flow for LiveData

I try to to use Flow instead of LiveData in repos.
In viewModel:
val state: LiveData<StateModel> = stateRepo
.getStateFlow("euro")
.catch {}
.asLiveData()
Repository:
override fun getStateFlow(currencyCode: String): Flow<StateModel> {
return serieDao.getStateFlow(currencyCode).map {with(stateMapper) { it.fromEntityToDomain() } }
}
It works fine if currCode if always the same during viewModel's lifetime, for example euro
but what to do if currCode is changed to dollar?
How to make state to show a Flow for another param?
You need to switchMap your repository call.
I imagine you could dosomething like this:
class SomeViewModel : ViewModel() {
private val currencyFlow = MutableStateFlow("euro");
val state = currencyFlow.switchMap { currentCurrency ->
// In case they return different types
when (currentCurrency) {
// Assuming all of these database calls return a Flow
"euro" -> someDao.euroCall()
"dollar" -> someDao.dollarCall()
else -> someDao.elseCall()
}
// OR in your case just call
serieDao.getStateFlow(currencyCode).map {
with(stateMapper) { it.fromEntityToDomain() }
}
}
.asLiveData(Dispatchers.IO); //Runs on IO coroutines
fun setCurrency(newCurrency: String) {
// Whenever the currency changes, then the state will emit
// a new value and call the database with the new operation
// based on the neww currency you have selected
currencyFlow.value = newCurrency
}
}

Create a callback function with another callback inside

My apologies for the bad title, I'm fairly new to callbacks and I'm not sure how to explain what I'm trying to achieve.
I have a class called MyClass that has a function connectToService inside of it.
The function connectToService does some calculations and then calls a function with a callback, like this:
fun connectToService() {
//Whatever calculations
val a = 7
var b = 3
var c = a + b
val token = MyToken()
token.actionCallback = object: SuperSecretObject {
override fun onSuccess(asyncActionToken: MyToken) {
c++
}
override fun onFailure(asyncActionToken: MyToken) {
c--
}
}
}
I want to create another class, YourClass which creates an object of MyClass and then calls the connectToService function. When the connectToService function finishes either the onSuccess or onFailurefunctions, I want to do something depending on which one was triggered (something different each time, thats why I can't put it inside the onSuccess or onFailure blocks of code).
Something like this:
//Inside `yourClass`
private fun myFunc() {
val yourClassObj = YourClass()
youClassObj.connectToService {
if(onSuccess)
reinventTheWheel()
else
squareIt()
}
youClassObj.connectToService {
combAWatermelon()
}
youClassObj.connectToService {
sharpenMyHammer()
}
}
Is this possible? If so, how can I achieve it? If it's not, what would be the closest solution to this requirement?
EDIT:
More detailed information has been requested, so while I can't provide exact details, I'll do my best to explain what's going on.
I'm basically working on a library to simplify petitions. For example, MQTT petitions. This is something tht resembles what I want to achieve:
/**
* Subscribes to a list of topics and handles the results
*/
fun subscribe(client: MqttAndroidClient, list: MutableList<String>, onMsg: ((String, MqttMessage)->Unit)?=null, conLost: ((Throwable)->Unit)?=null, delComp: ((IMqttDeliveryToken)->Unit)?=null) {
if (client.isConnected) { //Assert connection
for(x in list.iterator()) { //Subscribe to events
client.subscribe(x, 0)
}
client.setCallback(object : MqttCallback {
override fun connectionLost(cause: Throwable) { //Lost connection
Log.i("TAG", "Connection lost")
conLost?.let { it(cause) }
}
#Throws(java.lang.Exception::class)
override fun messageArrived(topic: String, message: MqttMessage) { //Arrived message
Log.i("TAG", "Message arrived: topic => $topic, message => $message")
onMsg?.let { it(topic, message) }
}
override fun deliveryComplete(token: IMqttDeliveryToken) { //Delivery complete
Log.i("TAG", "Delivery complete")
delComp?.let { it(token) }
}
})
}
}
The messageArrived function must have a behaviour that can be customized depending on the app it's being used on.
For example, on one app I want the onMsg() function to be like this:
when(topic) {
"firstTopic" -> {
localVariable++
}
"secondTopic" -> {
localMethod()
}
"thirdTopic" -> {
localClass.variable.method()
}
}
If I'm using it on an Android device, I'd like to be able to update the interface, doing Android API calls, etc.
I'm not sure I got your question correctly. I think what you are looking for is passing lambdas.
fun connectToService(onSucc: ()->Unit, onFail: ()->Unit) {
//Whatever calculations
MyToken().actionCallback = object: SuperSecretObject {
override fun onSuccess(asyncActionToken: MyToken) {
onSucc()
}
override fun onFailure(asyncActionToken: MyToken) {
onFail()
}
}
}
Then you can call the function like this:
connectToService({ /* Something */ }, { /* Something else */ })

How to inject scopeId into Koin to get the dependency?

In https://github.com/InsertKoinIO/koin/blob/master/koin-projects/docs/reference/koin-android/scope.md#sharing-instances-between-components-with-scopes it is shown the below example
module {
// Shared user session data
scope(named("session")) {
scoped { UserSession() }
}
// Inject UserSession instance from "session" Scope
factory { (scopeId : ScopeID) -> Presenter(getScope(scopeId).get())}
}
But I don't even know how to get presenter?
I try
val nameScope = getKoin().createScope("SomeName", named("session"))
val presenter = get<Presenter>(nameScope.id)
but it's not the correct. How to get my presenter?
After tracing the code, the way to do it is to use parameter to pass over the scopeId
For the above example, it will be
val nameScope = getKoin().createScope("SomeName", named("session"))
val presenter = get<Presenter>(parameters = { parametersOf(nameScope.id) )
If there's qualifier, we just need to send through them as well
One Example as below where we need a parameter of the lambda to send through scopeId and name of the qualifier. (the argument is self definable through the parameters of any type).
module {
scope(named("AScopeName")) {
scoped(qualifier = named("scopedName")) { Dependency() }
factory(qualifier = named("factoryName")) { Dependency() }
}
factory { (scopeId: ScopeID, name: String) ->
Environment(getScope(scopeId).get(qualifier = named(name)))
}
}
Then the calling is as simple as below
val nameScope = getKoin().createScope("SomeName", named("AScopeName"))
val environment = get<Environment>(parameters = { parametersOf(nameScope.id, "scopedName") })
Or we could also
val nameScope = getKoin().createScope("SomeName", named("AScopeName"))
val environment = get<Environment>(parameters = { parametersOf("SomeName", "scopedName") })

How to improve my code to show a data array in a table, with TornadoFX?

This is my way to display an array of data:
private val data = observableArrayList(
arrayOf("AAA", "111"),
arrayOf("BBB", "222"),
arrayOf("CCC", "333")
)
class HelloWorld : View() {
override val root = tableview<Array<String>>(data) {
column("name") { cellDataFeatures: TableColumn.CellDataFeatures<Array<String>, String> ->
SimpleStringProperty(cellDataFeatures.value[0])
}
column("value") { cellDataFeatures: TableColumn.CellDataFeatures<Array<String>, String> ->
SimpleStringProperty(cellDataFeatures.value[1])
}
}
}
It works but the code is quite complex. Is there any better way to do it?
(Maybe define a class to hold the data will make it much simpler, but I just want to test some uncommon cases)
Update:
A complete demo project for this: https://github.com/javafx-demos/tornadofx-tableview-array-data-demo
Here is a simpler way of defining your columns:
class HelloWorld : View() {
override val root = tableview(data) {
column<Array<String>, String>("name", { it.value[0].toProperty() })
column<Array<String>, String>("value", { it.value[1].toProperty() })
}
}
That said, using a specialized data structure would yield less headache :)
An alternative approach would be to configure just the cell item type and then a value factory:
column("name", String::class) {
value { it.value[0] }
}
column("value", String::class) {
value { it.value[1] }
}