Additional fields in many-to-many references - kotlin

I need to create many-to-many reference that will contain additional fields.
Eg I have
object Servers : UUIDTable("servers") {
val name = text("name")
val description = text("description").nullable()
val link = text("link").nullable()
val privacy = enumeration<ServerPrivacy>("privacy").default(ServerPrivacy.PUBLIC)
val owner = reference("owner", Users, onDelete = ReferenceOption.CASCADE)
}
class Server(id: EntityID<UUID>) : UUIDEntity(id, Servers) {
companion object : UUIDEntityClass<Server>(Servers)
var name by Servers.name
var description by Servers.description
var link by Servers.link
var privacy by Servers.privacy
var owner by User referencedOn Servers.owner
val categories by Category referrersOn Categories.server
val members by Members.server
}
object Users : UUIDTable("users") {
val login = text("login")
val password = text("password")
val username = text("username")
val link = text("link").nullable()
val locale = enumeration<LanguageRegion>("locale")
val flags = integer("flags").default(0)
val isBanned = bool("is_banned").default(false)
}
class User(id: EntityID<UUID>) : UUIDEntity(id, Users) {
companion object : UUIDEntityClass<User>(Users)
var login by Users.login
var password by Users.password
var username by Users.username
var link by Users.link
var locale by Users.locale
var flags by Users.flags
var isBanned by Users.isBanned
}
and I need to link Users on Servers with many-to many reference. I created table like
object Members : UUIDTable("members") {
val user = reference("user", Users, onDelete = ReferenceOption.CASCADE)
val server = reference("server", Servers, onDelete = ReferenceOption.CASCADE)
}
and understood that I need to contain another information in the Members and also will be helpful if I'll create entity like Member for easier data manipulation

I know I already answered you on GitHub, but will post it here as well for those that are looking to represent many-to-many relationship with Exposed DAO.
What you're missing is the use of via function.
It's documented, but maybe we could do a better job.
object Servers : UUIDTable("servers") {
val name = text("name").default("dummy server")
val description = text("description").nullable()
val link = text("link").nullable()
val privacy = enumeration<ServerPrivacy>("privacy").default(ServerPrivacy.PUBLIC)
val owner = reference("owner", Users, onDelete = ReferenceOption.CASCADE)
}
enum class ServerPrivacy {
PUBLIC
}
class Server(id: EntityID<UUID>) : UUIDEntity(id) {
companion object : UUIDEntityClass<Server>(Servers)
var name by Servers.name
var description by Servers.description
var link by Servers.link
var privacy by Servers.privacy
var owner by User referencedOn Servers.owner
val members by User.via(Members.server, Members.user)
}
object Users : UUIDTable("users") {
val username = text("username").default("dummy")
val link = text("link").nullable()
val flags = integer("flags").default(0)
val isBanned = bool("is_banned").default(false)
}
class User(id: EntityID<UUID>) : UUIDEntity(id) {
companion object : UUIDEntityClass<User>(Users)
var username by Users.username
var link by Users.link
var flags by Users.flags
var isBanned by Users.isBanned
val servers by Server.via(Members.user, Members.server)
}
object Members : UUIDTable("members") {
val user = reference("user", Users, onDelete = ReferenceOption.CASCADE)
val server = reference("server", Servers, onDelete = ReferenceOption.CASCADE)
}

Related

Get HashMap<Model1, List<Model2>> in an MVVM+Repostiory+LiveData setting?

So I was working on this silly little app for practicing MVVM and Repository Pattern. I have two model classes at the moment. They are Category and SubCategory for which I have defined the following data classes:
#Entity(tableName = "categories")
data class Category(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
val id: Int,
#ColumnInfo(name = "name")
val name: String
) {
}
And
/**
* One to many Relationship from Category to SubCategory
*/
#Entity(
tableName = "sub_categories", foreignKeys = arrayOf(
ForeignKey(
entity = Category::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("category_id")
)
)
)
data class SubCategory(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
val id: Int,
#ColumnInfo(name = "name")
val name: String,
#ColumnInfo(name = "category_id")
val categoryId: Int
) {
}
As you can see, I have modeled the resources such that we will need categoryId to be passed to get SubCategories related to a Category.
Now I am pretty new with this MVVM and LiveData and Repository Pattern.
My Problem is that I am using an ExpandableListView to populate SubCategories under Categories and the Adapter for it requires a HashMap<Category, List<SubCategory> for it to display the expandable listview.
So my question is how do I get a HashMap<Category, List<SubCategory> from my database using an approach of db->dao->repository->viewmodel and wherever the adpater goes.
I suppose creating a separate repository like CategorySubCategoryRespository whereby I can do something like following is not going to help?:
class CategorySubCategoryRepository(
private val categoryDao: CategoryDao,
private val subCategoryDao: SubCategoryDao
) {
val allCategoriesSubCategories: LiveData<HashMap<Category, List<SubCategory>>>
get() {
val hashMap: HashMap<Category, List<SubCategory>> = hashMapOf()
for (category in categoryDao.getList()) {
hashMap[category] = subCategoryDao.getSubCategoriesListForCategory(category.id)
}
return hashMap
}
}
}
PS: I think I want to use LiveData wherever possible
So what I ended up doing was, in my CategorySubcategoryRepository I constructed the Hashmap from the CategoryDao and SubcategoryDao as follows:
class CategorySubCategoryRepository(
private val categoryDao: CategoryDao,
private val subCategoryDao: SubCategoryDao
) {
fun getHashMap(): LiveData<HashMap<Category, List<SubCategory>>> {
val data = MutableLiveData<HashMap<Category, List<SubCategory>>>()
val hashMap: HashMap<Category, List<SubCategory>> = hashMapOf()
Executors.newSingleThreadExecutor().execute {
for (category in categoryDao.getList()) {
hashMap[category] = subCategoryDao.getSubCategoriesListForCategory(category.id)
}
}
data.value = hashMap
return data
}
}
Then I used this in my viewmodel's init{} block like:
hashMap = categorySubCategoryRepository.getHashMap()
Then I observed it in my Fragment's onCreateView as:
myViewModel.hashMap.observe(this, Observer {
adapter.setCategoryList(it.keys.toList())
adapter.setCategorySubCategoriesMap(it)
elv_categories.setAdapter(adapter)
adapter.notifyDataSetChanged()
})
Do Comment if this is not the right thing to do. I am doing this only to increase my skills and would love to hear if there's a better way to go about things or if my approach is completely absurd.
Edit:
As per #SanlokLee's comment. The getHashMap function has been changed to:
fun getHashMap(): LiveData<HashMap<Category, List<SubCategory>>> {
val data = MutableLiveData<HashMap<Category, List<SubCategory>>>()
Executors.newSingleThreadExecutor().execute {
val hashMap: HashMap<Category, List<SubCategory>> = hashMapOf()
for (category in categoryDao.getList()) {
hashMap[category] = subCategoryDao.getSubCategoriesListForCategory(category.id)
}
data.postValue(hashMap)
}
return data
}

Kotlin Exposed Many-to-Many Jackson Infinite recursion (StackOverflowError)

I have done the many-to-many reference in ExposedBD (kotlin) as follows in the wiki:
https://github.com/JetBrains/Exposed/wiki/DAO#many-to-many-reference
However, there is a problem of Infinite Recursion (Jackson) when I am trying to return a list of objects in my API (Javalin).
So, I would like to know how to put the annotation #jsonIgnore or if there are other alternative solutions in this case. Here is the mapping:
// many-to-many Actor--StarWarsFilms
// Actor Entity
object Actors: IntIdTable() {
val firstname = varchar("firstname", 50)
val lastname = varchar("lastname", 50)
}
class Actor(id: EntityID<Int>): IntEntity(id) {
companion object : IntEntityClass<Actor>(Actors)
var firstname by Actors.firstname
var lastname by Actors.lastname
}
// StarWarFilm Entity
object StarWarsFilms : IntIdTable() {
val sequelId = integer("sequel_id").uniqueIndex()
val name = varchar("name", 50)
val director = varchar("director", 50)
}
class StarWarsFilm(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<StarWarsFilm>(StarWarsFilms)
var sequelId by StarWarsFilms.sequelId
var name by StarWarsFilms.name
var director by StarWarsFilms.director
var actors by Actor via StarWarsFilmActors
}
// Intermediate table
object StarWarsFilmActors : Table() {
val starWarsFilm = reference("starWarsFilm", StarWarsFilms).primaryKey(0)
val actor = reference("actor", Actors).primaryKey(1)
}

Simple casting in Kotlin/Java

I have an object User defined as below
class User(){
var id: Int? = null
var name: String? = null}
For certain reasons, I have to create new object User of same parameters and I have to copy data from old to new type.
class UserNew(){
var id: Int? = null
var name: String? = null}
I was looking for easiest way to convert from old type to a new one. I want to do simply
var user = User()
var userNew = user as UserNew
But obviously, I am getting This cast can never succeed. Creating a new UserNew object and set every parameter is not feasible if I have a User object with lots of parameters. Any suggestions?
as is kotlin's cast operator. But User is not a UserNew. Therefore the cast fails.
Use an extension function to convert between the types:
fun User.toUserNew(): UserNew {
val userNew = UserNew()
userNew.id = id
userNew.name = name
return userNew
}
And use it like so
fun usingScenario(user: User) {
val userNew = user.toUserNew()
If you don't want to write a boilerplate code, you can use some libraries that will copy values via reflection (for example http://mapstruct.org/), but it's not the best idea.
To achieve you can Simply use Gson and avoid boilerplate code:
var user = User(....)
val json = Gson().toJson(user)
val userNew:UserNew =Gson().fromJson(json, UserNew::class.java)
you should follow this logic for this case.
note: #Frank Neblung answer i implemented
fun main(args: Array<String>) {
val user = User()
user.id = 10
user.name = "test"
var userNew = user.toUserNew()
println(userNew.id) // output is 10
println(userNew.name)// output is test
}
class User()
{
var id: Int? = null
var name: String? = null
fun toUserNew(): UserNew {
val userNew = UserNew()
userNew.id = id
userNew.name = name
return userNew
}
}
class UserNew() {
var id: Int? = null
var name: String? = null
}
You have two options. Either create interface and implement it in both classes. then you can use this interface in both places (User,UserNew) If this is not what you want, i would use copy constructor in UserNew taking User as parameter, You can create new
NewUser nu = new UserNew(userOld)
if you have lots of properties answer from ppressives is way to go
To achieve that you can use the concept of inheritance:
https://www.programiz.com/kotlin-programming/inheritance
Example:
open class Person(age: Int) {
// code for eating, talking, walking
}
class MathTeacher(age: Int): Person(age) {
// other features of math teacher
}

What is the benefit of using primarykey and references method in class jooq

I'm start the learn jooq. I have mssql server. I create some class the represent table on my server. But I don't understand what is the benefit when I was using getPrimaryKey and getReferences methods in my table class?
class User : TableImpl<Record>("users") {
companion object {
val USER = User()
}
val id: TableField<Record, Int> = createField("id", SQLDataType.INTEGER)
val name: TableField<Record, String> = createField("name", SQLDataType.NVARCHAR(50))
val countryId: TableField<Record, Short> = createField("country_id", SQLDataType.SMALLINT)
override fun getPrimaryKey(): UniqueKey<Record> = Internal.createUniqueKey(this, id)
override fun getReferences(): MutableList<ForeignKey<Record, *>> =
mutableListOf(Internal.createForeignKey(primaryKey, COUNTRY, COUNTRY.id))
}
class Country : TableImpl<Record>("country") {
companion object {
val COUNTRY = Country()
}
val id: TableField<Record, Short> = createField("id", SQLDataType.SMALLINT)
val name: TableField<Record, String> = createField("name", SQLDataType.NVARCHAR(100))
override fun getPrimaryKey(): UniqueKey<Record> =
Internal.createUniqueKey(this, id)
}
The generated meta data is a mix of stuff that's useful...
to you, the API user
to jOOQ, which can reflect on that meta data for a few internal features
For instance, in the case of getPrimaryKey(), that method helps with all sorts of CRUD related operations as you can see in the manual:
https://www.jooq.org/doc/latest/manual/sql-execution/crud-with-updatablerecords/simple-crud
If you're not using the code generator (which would generate all of these methods for you), then there is no need to add them to your classes. You could shorten them to this:
class User : TableImpl<Record>("users") {
companion object {
val USER = User()
}
val id: Field<Int> = createField("id", SQLDataType.INTEGER)
val name: Field<String> = createField("name", SQLDataType.NVARCHAR(50))
val countryId: Field<Short> = createField("country_id", SQLDataType.SMALLINT)
}
However, using the code generator is strongly recommended for a variety of advanced jOOQ features which you might not get, otherwise.

Ignore setter and set property directly

I have a class that writes a user to SharedPreferences every time it is set:
class UserManager #Inject constructor(
val prefs: SharedPreferences,
val jsonAdapter: JsonAdapter<User>
) {
companion object {
val USER = "user"
}
var user: User = User()
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
init {
val userString = prefs.getString(USER, null)
if (userString != null) {
user = jsonAdapter.fromJson(userString)
}
}
}
Problem: If the user is set in the init block, it calls the setter and writes the user that we just got from the shared prefs... to the shared prefs.
Question 1: How can I directly set the property from the init block?
Question 2: Why do I have to initialize the User when I define a custom setter, but can omit the initialization when the default setter is used?
You need to directily initiliaze the property with the correct value. You can do this using the run function from the stdlib:
class UserManager #Inject constructor(
val prefs: SharedPreferences,
val jsonAdapter: JsonAdapter<User>
) {
companion object {
val USER = "user"
}
var user: User = run {
val userString = prefs.getString(USER, null)
if (userString != null) {
jsonAdapter.fromJson(userString)
} else {
User()
}
}
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
}
Shorter syntax proposed by Ilya Ryzhenkov on the Kotlin Slack:
var user: User = prefs.getString(USER, null)?.let { jsonAdapter.fromJson(it) } ?: User()
set(value) {
field = value
prefs.edit().putString(USER, jsonAdapter.toJson(user)).apply()
}
I believe the best solution is to use the 'backing property' concept described here: https://kotlinlang.org/docs/reference/properties.html#backing-properties
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null)
_table = HashMap() // Type parameters are inferred
return _table ?: throw AssertionError("Set to null by another thread")
}
Then initialize the backing property in the constructor and do <backingproperty> = value instead of field = value as well as point the getter to the backing property.
Take a look at by map delegate, seems like this is the pattern you want:
class User(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
https://kotlinlang.org/docs/reference/delegated-properties.html#storing-properties-in-a-map