New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Static Value objects #43
Comments
I'd like to get @loganwright's thoughts on this since he implemented a very similar Validation library in Vapor. My concern here would be forcing too much coupling between the database and the model. Users of Fluent should be able to choose whether or not they want the Model to closely resemble the database schema. As @Evertt mentioned, it could be possible to create a layer between this called For example, say my database has 2 columns: struct Name {
var first: String
var last: String
var full: String { first + " " + last }
} This simple functionality would be a headache to implement if the database and |
@tannernelson That sounds like a good idea. To clarify, the class Person could be written as either: class Person: Model {
let id: Value?
let age: Valid<Count<Int>>
let first: String
let last: String
init(id: Value? = nil, age: Int, first: String, last: String) throws {
self.id = id
self.age = try Valid(age, by: Count<Int>.min(18))
self.first = first
self.last = last
}
convenience required init?(serialized: [String : Value]) throws {
try self.init(id: serialized["id"], age: serialized["age"]?.int ?? 18, first: serialized["first"]?.string ?? "", last: serialized["last"]?.string ?? "")
}
func serialize() -> [String : Value?] {
return [
"age": age.value,
"first": first,
"last": last
]
}
class var table: String {
return "people"
}
} or class Person: Entity {
let id = Field<Int?>()
let age = Field<Int>(validators: Validators<Int>.greaterThanEqual(other: 18))
let first = Field<String>(sqlType: .string(20), unique: true, validators: Validators<String>.length(10...20)) { print("Name changed \($0)") }
let first = Field<String>(sqlType: .string(20), unique: true, validators: Validators<String>.length(10...20)) { print("Name changed \($0)") }
init(age: Int, name: String) {
self.age.value = age
self.name.value = name
}
func values() -> [String: Value<Any>] {
return ["id": id, "age": age, "name": name]
}
class var table: String {
return "users"
}
} |
This should be achievable with Node + Vapor validators now. |
Static
Value
objectsIntroduction
This change would allow for easier serialization and deserialization, provide better implementation of validators, enable a more eloquent relational mapping method, and allow for easier database generation.
Motivation
As it stands now, reading and writing to objects in Fluent isn't as fluent as it could be. Developers have to manually read the object's variables from a dictionary when deserializing data and then put all of that data back into a dictionary when serializing it again. That's a lot of unnecessary work for the user.
In addition, the possibility of automatically generation tables with the way the framework is currently designed isn't very feasible without even more code from the user of the framework. (See #305 on qutheory/vapor)
Finally, validating data should not be done by the user of the framework every time they change a variable; the validators should be executed automatically when changing the value of an object, and Fluent currently does not provide an easy way of doing that.
Proposed solution
Here is an example model that implements my proposed method:
As you can see, the
Value<>
objects are stored in constants, so they can not be replaced. This allows for them to be referenced and modified anywhere. That means it's specifically good for relationships where the database needs to be able to load more items into the array on an as-needed basis and can't just give the developer all the data at once and not retain a reference to it. To implement this functionality, we'd simply have to extend anyValue<MutableCollection>
objects to have these methods that can selectively load items into the array:In addition, instead of requiring a
serialize() -> [String: Value?]
method and an initializer,init?(serialized: [String : Value])
, there's one method,values() -> [String: Value<Any>]
. When the data from the database is deserialized into the object, the database simply calls this method to get a reference to all the values in the object and reads in the data appropriately. Ideally, this method for serialization and deserialization would be built on top of the currentModel
serialization requirements, so if a developer needs more control of the serialization for some reason, they can simply use those methods instead.This method of storing values would also allow for validators that check the validity of a value any time it's changed. That way, the developer does not need to continuously check the values for validity over and over again every time they're changed; the
Value<>
object itself will check them for you. Validators are available for used based on the type of value stored inValue<>
and highly modular, as seen in a couple example validators below:You might recognize that the value passed into the
ValueValidator
is flagged asinout
. This allows for values to be modified by the validator in order to fix salvageable inputs.Finally, generating an empty database would be very easy with this, since everything is structured and validated rigorously. In addition, as seen in the declaration for a couple of the fields, the developer would be able to declare special SQL properties for values like the specific type of column, nullability, uniqueness, etc.
Impact
This will break all current Fluent projects. There's not much we can do to retain functionality of old codebases. Better sooner than later, if we're going to do it.
Alternatives considered
The alternative is to continue using Fluent as it works today, yet I feel that not adopting this change would require a lot more workarounds and cause many caveats in the future.
Decision (For Moderator Use)
On [Date], the community decided to (TBD) this proposal. When the community makes a decision regarding this proposal, their rationale for the decision will be written here.
The text was updated successfully, but these errors were encountered: