vapor / fluent Goto Github PK
View Code? Open in Web Editor NEWVapor ORM (queries, models, and relations) for NoSQL and SQL databases
Home Page: https://docs.vapor.codes/4.0/fluent/overview/
License: MIT License
Vapor ORM (queries, models, and relations) for NoSQL and SQL databases
Home Page: https://docs.vapor.codes/4.0/fluent/overview/
License: MIT License
Should be:
public func modify(_ id: Node, _ serialized: Node?) throws {
let query = try makeQuery()
query.action = .modify
query.data = serialized
let idKey = query.database.driver.idKey
_ = try filter(idKey, id)
try query.run()
}
Table and rows have many essential database-specific constraints and row types. Right now, to access these features, one has to do manually alter tables after creation like try mysql.driver.raw("ALTER TABLE \(entity) CHANGE text text VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
to get a custom character set.
It would be much better if there was a way that each database package could extend the schema creation library to allow for custom constraints and row types. For example, the new extended format for schema creation would look like this:
try database.create(entity) { users in
users.charset("utf8_mb4")
users.row("id").id()
users.row("name").string(191).charset("utf8_mb4").unique() // Assumes `varchar` type in Postgres; could also say `users.row("name").varchar(191)`
users.row("ip").cidr()
}
Notice the use of charset()
and cidr()
. These would be extensions from the PostgreSQL driver on Fluent allowing access to database-specific row types and constrains.
By calling row(name)
, this creates a Row
object which can be extended to add custom properties like cidr()
(which changes the row type) and charset()
(which would add a charset constraint to the row).
Notice the Schema
itself can also have constraints added to it just like rows, e.g. users.charset("utf8_mb4")
.
This would make issues like #74 quick fixes in the driver itself instead of alterations to Fluent itself.
This would require a lot of the Fluent library itself to be refactored to work with extensions. This would also break any existing schema code.
The alternative is to continue statically adding more and more data types that may or may not be supported by databases. Any database-specific features would have to be accessed using raw SQL ALTER
queries.
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.
I think it would be better if find returned an optional instead of throwing an exception when nothing is found.
Deleting a model object with .delete()
will not trigger willDelete()
or didDelete()
, but the model's data is still deleted from the database.
Example:
final class Thing: Model {
[...]
func willDelete() {
print("willDelete") // doesn't get printed on deletion.
}
func didDelete() {
print("didDelete") // doesn't get printed on deletion.
}
[...]
}
I was thinking it might be nice if the initializer was failable, or maybe throwable.
I know it's probably a rare case, but it's possible that you could get passed some invalid data. With the current implementation, the only thing we can do is come up with default values for it. That might not be the worst thing in the world -- most times the initializer will be called is when there is an actual record in the database, so something needs to be done!
Just a thought ๐
Would be great to have a Cassandra driver, if anyone is willing to work on it.
Best regards.
Schema support for SQL Timestamp types is needed to allow for efficient database indexing on timestamp types
Fluent currently supports a small number of database types (INT, DOUBLE, STRING, ID, DATA, and BOOL). I am working on an application that needs to keep a table sorted by date/time of events so queries can efficiently be processed. The current design does not allow me to use native swift date values and store them in the database in a format that the database can efficiently process.
Add in Fluent the support for a .timestamp() Schema entry
This will need to be implemented by appropriate Providers to complete the implementation of this feature
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 schema builder should offer some helper methods for building relationship foreign keys. Maybe something like this:
static func prepare(database: Database) throws {
try database.create("posts") { posts in
...
posts.parent(User.self)
}
}
Will need to think how this works for Mongo.
Option to set have fluent set timestamps for updated_at and created_at
Like with entity I suggest having some static vars for
Important to have both support for int and datatime (unix and date time)
Hey guys, when I was working on the Query object, I was using the Laravel Query Builder as an inspiration to make a nice and readable API.
And a few hours ago I was reading the MongoKitten docs and felt very inspired by its Cursor
object, which you can iterate over to get documents from it and secretly behind the scenes it fetches the results in batches.
And now I'm thinking, wat is the best way to do all this? I would like to build Fluent in such a way that it encourages developers to design their apps well, with SOLID principles in mind.
That's why I personally prefer data-mapper-ORMs over active-record-ORMs, because they encourage developers to better separate concerns in their apps.
Anyhow, so I would like to have a discussion about designing Fluent. For example, do we want to have an EntityCollection
class which holds an array of entities and has some convenience methods to again filter them down? And that then the Query
returns entity-collections instead of arrays? Do we also want some Iterator
that retrieves the results in batches like the Cursor
object in MongoKitten?
Etcetera... I honestly have no idea of all the different possible ways to do this, but I want to research it. And I'll happily make a proposal myself, but right now I have visitors so I'll come back to this later. In the meanwhile I'd love to hear your thoughts about this.
I'm upgrading a project to Vapor 1.1 and VaporSQLite 1.1. I've added a Config/sqlite.json
file and I'm initializing with...
let drop.Droplet()
try drop.addProvider(VaporSQLite.Provider.self)
drop.preparations = [Person.self]
My project compiles, but when I run it...
Failed to prepare Person
prepare("table `persons` already exists")
While compiling nicely with DEVELOPMENT-SNAPSHOT-2016-03-24-a
it does not with DEVELOPMENT-SNAPSHOT-2016-05-09-a
.
I am not entirely sure I follow the logic behind the AND/OR chaining of filters, when building queries.
Using the example
Query<User>().filter("name", .Equals, "John").or { query in
query.filter("phone", .NotEquals, "2234567890").and { query in
query.filter("address", .Equals, "100 Apt Ln").filter("other", 1)
}
}
I read this as name = ... OR (phone = ... AND (address = ... AND other = ...))
But the resulting sql statement is name = ... AND (phone != ... OR (address = ... AND other = ...))
I guess the underlying intention is that OR/AND is applied between the array of filters in the closure, with the result being AND-ed to the previous node in the filter chain. But the code reads as if it is OR between the previous node and the filters in the closure.
The only reason I'm suggesting this is because I believe we can implement this functionality ourselves, without requiring the creator of any Entity
to implement it. I already wrote some code to do this.
func serialize(entity: Entity, fields: [String]) -> [String:Value?] {
let entityMirror = Mirror(reflecting: entity)
var serialized = [String:Value?]()
for child in entityMirror.children {
// Match the property if it's in the provided fields array
if let property = child.label where fields.contains(property) {
switch unwrap(child.value) {
// If it's another entity, then it must be a belongsTo relationship
case let entity as Entity:
if let id = entity.id {
serialized[property] = id
}
// Otherwise it must just be some value
case let value as Value?:
serialized[property] = value
// If we get here then something is off
default: fatalError("Fields must be either a `Value` or an `Entity`")
}
}
}
return serialized
}
// Don't mind this function.
// It just unwraps a value that's potentially secretly an optional to a real optional.
func unwrap(any: Any) -> Any? {
let mirror = Mirror(reflecting: any)
if mirror.displayStyle != .Optional {
return any
}
if mirror.children.count == 0 {
return nil
}
let (_, some) = mirror.children.first!
return some
}
Now we just need some method of determining which columns need to be extracted. But I think someone was already working on a SchemaBuilder and such right? #18
What do you guys think? My preference is to require as little as possible in the Entity
protocol. If I could also implement a func unserialize(serialized: [String:Value?]) -> Entity
then I would, but unfortunately that's not possible without turning every Entity into an NSObject.
Are there any plans/ideas about having associations between models? Maybe like has_many, belongs_to, etc?
I can start working on this with some guidance on how do we want it to look like.
It would be nice to also create a Redis driver for Fluent. I use Redis for most of my services as both the cache and the db.
https://github.com/vapor/fluent/blob/master/Sources/Fluent/Query/Query.swift#L165
I think it should be possible to insert entries with a forced id (this is used when seeding data fx)
But at least it should fail, right now you think it got stored, but nothing is to be found in the db
(MySQL)
I would like to see support for id
s of different types (e.g. UUID
). Currently fluent assumes the id
is not set when saving a non-existing entity and takes the id
from the created query as an Int
. It would be nice if it is possible to have UUID
support, where the UUID
can be set in the init
method of the entity.
People need custom encodings and character sets in their databases to do things such as supporting unicode in MySQL. (Let's be honest here, those very emojis are important.)
Fluent need the ability to add custom encodings. I have already added support for reading and writing with custom encodings to all three MySQL libraries.
Add custom character sets and collations to tables and columns.
A MySQL table with a utf8mb4
encoding on the text
column would loo like this:
CREATE TABLE `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`text` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`date` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
This will not have any impact on existing code.
The alternative it to let users continue altering tables after being created with raw queries.
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.
Move the RESTDriver
from Vapor University to Fluent.
The Migration entity mainly stores metadata about migrations in Fluent. Therefor, the table name should not be fluent
but something that denotes metadata like __fluent
Someone may one day com along and try and make a table called fluent
and be very disappointed. However, that is quite unlikely. Just for clarity, it would be better to give the Migration entity a table name that sets it apart from normal tables. While this is fairly nit-picky, it's just something I feel should be done.
I propose to give the table the name __fluent
instead of fluent
.
N/A
This will make Fluent re-prepare every model once the table name has been changed, since Fluent won't have a way to know if the entities are already prepared. This will also leave a redundant, unused table called fluent
in everyone's database.
It could also possibly be called something like meta_fluent
.
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.
Like the collection, just with options for page=2 query
Add a pagination object to the response with
So right now the only major filter that hasn't been implemented yet is between
, I think. And I'm doubting how to do it.
Right now if I would write:
let filter: Filter = "id" ~= 1...10
It generates id in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
. But maybe it should actually generate id between 1 and 10
, because a range never increments more than 1 right?
But for some reason, in the case of between
, to me ><
would look like a more obvious operator than ~=
. When I see ><
I almost read it as saying between
:
let filter: Filter = "id" >< 1...10
Then again, the ~=
operator already exists and the ><
does not AFAIK. What do you guys think? And if we would go for ><
then what would be the most logical operator for not between
?
Would it be <>
or !><
?
edit Okay right now I implemented it with the ~=
operator, but I would still like to get some opinions from you guys. :-)
After watching the video and looking at the readme in the repository I'm convinced that Unbox could be a welcome addition to Fluent.
Unbox is a little library that unboxes any object from dictionary data with very simply syntax:
let user = try Unbox(dictionary) as User
So instead of requiring Entity
objects to implement an init(serialized: [String: Value])
, we would just inherit from the Unboxable
protocol and require Entity
objects to implement init(unboxer: Unboxer)
.
The code just looks so much cleaner. It would turn this:
struct User: Entity {
let id: Int?
let age: Int
let name: String
let gender: Gender // This is an enum
let birthday: NSDate
init(serialized: [String:Value]) throws {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
id = serialized["id"]?.int // id is optional so that's easy
age = serialized["age"]?.int ?? 18 // age is not optional, but let's provide a fallback value for ease
if let name = serialized["name"]?.string,
let gender = Gender(rawValue: serialized["gender"]?.string ?? ""),
let birthday = dateFormatter.dateFromString(serialized["birthday"]?.string ?? "") {
self.name = name
self.gender = gender
self.birthday = birthday
} else {
// Name, gender and birthday are required,
// so if they are not present, throw an error
throw SomeError()
}
}
}
Into this:
struct User: Entity {
let id: Int?
let age: Int
let name: String
let gender: Gender
let birthday: NSDate
init(unboxer: Unboxer) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
id = unboxer.unbox("id") // Since id is optional it will not fail when id is not present
age = unboxer.unbox("age") ?? 18 // Age is required, but I provided a fallback value so it won't fail
name = unboxer.unbox("name") // As you can see it automatically returns the correct type
gender = unboxer.unbox("gender") // And it even works for more complex types like enums
birthday = unboxer.unbox("birthday", formatter: dateFormatter) // and dates and even structs and classes
}
}
I think it is 100x nicer to read. So, what do you guys think?
The same guy also made a Wrap library which basically does the inverse of Unbox. If we would implement Wrap too then that would satisfy the other issue I opened #30
There should be a way to modify foreign keys for relations in case the naming is non-standard.
https://github.com/qutheory/fluent/blob/master/Sources/Fluent/Relations/Children.swift#L23
Value
objectsThis 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.
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.
Here is an example model that implements my proposed method:
class Person: Model {
let id = Value<Int?>()
let age = Value<Int>(validators: Validators<Int>.greaterThanEqual(other: 18))
let name = Value<String>(sqlType: .string(20), unique: true, validators: Validators<String>.length(10...20)) { print("Name changed \($0)") }
let gender = Value<Gender>()
let birthday = Value<Date>(nullable: true)
let friends = Value<[Person]>(relationship: Relationship.manyToMany)
init(age: Int, name: String, gender: Gender, birthday: Date) {
self.age.value = age
self.name.value = name
self.gender.value = gender
self.birthday.value = birthday
self.birthday.changeCallback = birthdayChanged
}
func values() -> [String: Value<Any>] {
return ["id": id, "age": age]
}
class var table: String {
return "users"
}
func birthdayChanged(value: Value<Date>) {
print("Hmm... That's odd.")
}
}
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 any Value<MutableCollection>
objects to have these methods that can selectively load items into the array:
extension Value where T: MutableCollection, T.Iterator.Element: Model {
init(relation: Relation, sqlType: SQLType? = nil, unique: Bool = false, nullable: Bool = false, validators: Validators<T>.ValueValidator..., changeCallback: ChangeCallback? = nil)
func all() throws -> [T.Iterator.Element]
func find(_ id: Value) throws -> T.Iterator.Element?
static var query: Fluent.Query<T.Iterator.Element>
}
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 current Model
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 in Value<>
and highly modular, as seen in a couple example validators below:
struct Validators<Value> {
typealias ValueValidator = (inout value: Value) throws -> Void
}
extension Validators where Value: String {
static func length(isIn: Range<Int>) -> ValueValidator {
return {
guard $0.characters.count > 0 else {
throw ValidatorError()
}
}
}
}
extension Validators where Value: Comparable {
static func lessThan(other: Value) -> ValueValidator {
return { guard $0 < other else { throw ValidatorError() } }
}
static func greaterThan(other: Value) -> ValueValidator {
return { guard $0 > other else { throw ValidatorError() } }
}
static func lessThanEqual(other: Value) -> ValueValidator {
return { guard $0 <= other else { throw ValidatorError() } }
}
static func greaterThanEqual(other: Value) -> ValueValidator {
return { guard $0 >= other else { throw ValidatorError() } }
}
static func between(values range: Range<Value>) -> ValueValidator {
return { guard range.contains($0) else { throw ValidatorError() } }
}
}
You might recognize that the value passed into the ValueValidator
is flagged as inout
. 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.
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.
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.
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.
I don't know if it is my lack of knowledge of the environment (I am just starting with Vapor), but I was not able ti find a method to make a preparation on a preexistent table. So my following proposal.
At the moment there is no versioning of the schema. It would be fantastic to add the possibility to associate a version with a model, so the implementer can keep trace of different model versions.
This is essential when we maintain a database modifying tables by just adding fields, for example.
The fluent
table must be modified adding an integer field that stores the model version. It is responsibility of the model manager to know the meaning of the value that will be stored here.
The model prepare
method should return the new version of the model and must have the current model version as parameter:
static func prepare(_ database: Database, currentVersion version: Int?) -> Int throws
If the model is not existent in the database, the optional version
parameter will be nil
. The value returned is the stored version of the model and must be stored in the fluent
table.
Similar solution to the revert method
:
static func revert(_ database: Database, currentVersion version: Int) -> Int? throws
The version
parameter contains the version stored in the fluent
table. After the revert succeeded, it returns the new version stored or nil
if the model has been deleted.
Hey, trying to run Vapor on Heroku, and sqlite isn't supported. Any sort of eta on postgres?
var foo = Foo(name: "a")
try foo.save()
foo.name = "b"
// something here, save, or anything else (that I found and tried)
print(foo.name) // always a
In logging commands, there appears to possibly be a missing WHERE
clause where the statement cuts off after WHERE
in a place one might expect WHERE id = ?
After updating to Vapor v0.12
I couldn't build my app due to:
Model
does not conform withJSONRepresentable
After diving a bit into the source it looks to be a simple type-error. In Fluent's Model
it has a method named MakeJSON
where the JSONRepresentable
protocol expects a MakeJson
method.
I would have made a pull-request, but I'm not sure how you wish to spell it. Either with caps or just a capitalized J. So figured it was best, to let you guys figure it out ๐
When using a Pivot
, the entity will be prepared and everything will work the first time the app is ran, but any time after the database will fail to be set. This also happens if trying to save more than once in a single program execution.
My Droplet
initialization looks like this
let drop = Droplet(preparations: [User.self, Appointment.self, Pivot<User, Appointment>.self], providers: [VaporMySQL.Provider])
And the simple testing code I'm using is this
drop.post("appt")
{ req in
guard
let userId = req.data["user_id"].int,
let user = try User.find(userId)
else
{
throw Abort.badRequest
}
guard let appointment = try Appointment.find(1) else
{
throw Abort.serverError
}
var pivot = Pivot<User, Appointment>(user, appointment)
try pivot.save()
return appointment
}
If this helps at all, manually calling Pivot<User, Appointment>.database = drop.database
seems to fix all of the issues, but I was under the impression that adding the pivot as a preparation should do this for me.
I talked with Tanner about this a little and he suspects that it may be due to the Droplet
preparation code not casting the pivot as a Model
.
An in memory storage mechanism for use with fluent would assist greatly during development.
While developing an application or experimenting with vapor it would be nice to have all of the niceness of fluent without the commitment of setting up a full persistence stack. In memory storage would make this possible.
In addition, in memory storage could also be helpful for an extremely basic cache layer.
Expected result
IDs should be of those associated object.
Actual result.
IDs are the rows inside of the join table.
Attempting to setup a sibling between 2 models through a pivot table returns []
due to a filter within the sibling function.
Sibling.swift line: 30 try query.filter(pivot, "\(E.name)_\(query.idKey)", ident)
Value of indent
using "\(E.name)_\(query.idKey)"
as field name doesn't yield true
when using .equals
comparator.
Allow some way to enable global query logging
I'm making a very basic API with the basic ResourceController
using vapor.
Since there is already an id
on the Model
protocol, which is "nil when not saved yet", it would be cool if there was some way to get the id
after saving.
Maybe save
could populate the id
field, or return an id
, or maybe return a newly instantiated model with the id
filled in.
That way, when a user makes a POST request, they can actually know the ID of the object to fetch it again.
Let's get a roadmap of what features contributors and users think are important for Fluent 1.1 and Fluent 2.0.
Fluent 1.1
Fluent 2.0
attach
and detach
Comment below with any ideas you have or things you would change or add to this list.
I am assuming the Pivot
entity should work independent on the order of the generic entities (which is why there is the left
and right
property. However, setting the database uses the name
of the Pivot
, which is dependent on the order.
Here is a pull request with a test that shows the issue: #114.
When I run Vapor with an existing database, I get an error when I try to fetch entries from the database (with a basic REST controller) but this works when the table is created in the database when the server is run.
I believe that there is a missing action in the hasPrepared() logic.
I have added in the logic and will send a pull request.
Hi !
I found a bug with Fluent :
inside a Model, I create the table with an id but with a custom name :
static func prepare(_ database: Database) throws {
try database.create("rooms") { rooms in
rooms.id("roomNumber")
}
}
In my init, I have :
self.id = try node.extract("roomNumber")
The error is when I try to use Room.find().
It doesn't work because inside the implementation of this method, It use
database?.driver.idKey
this variable is in sqlite and mySql 'id', so It doesn't work with my custom idKey...
Is this a bug, or a normal behavior ?
Thanks !
When using the FluentMySQL
driver and using multiple filters:
guard let user = try User.filter("email", .equals, request.data["email"].string ?? "").filter("password", .equals, request.data["password"].string ?? "").first() else {
throw Abort.custom(status: .unauthorized, message: "Invalid login credentials")
}
It results in a SQL error:
Server Error: prepare("You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'?AND`password` = ? LIMIT 1\' at line 1")
Basically there's missing a space between ?
and AND
.
A fix could be to add a space after the ?
in the GeneralSQLSerializer.swift
file at line 140
.
Allow default values to be set for columns in preparations.
Support ordering
EntityCollection<T: Entity>
I understand that most frameworks use an Active-Record style of ORM, because it's just the easiest to understand and get started with. The Data-Mapper ORM, while being very useful for separating concerns in bigger projects, is also more complicated to learn and more work to get started with. So I propose a middle way, using EntityCollections.
As far as the developer is concerned, the EntityCollection<T: Entity>
is precisely a collection of a certain type of Entity
which they can iterate over and which has some convenient functions to further filter-down the collection based on some conditions.
Underneath however, the collection can be smart about optimizing when it uses the Query
class to get the entities it needs or whether it works with the entities it has already loaded in memory.
Also I recommend we make the EntityCollection
completely immutable, so that every time you add a new filter, it just returns a new instance of Self
with the new filter applied.
class UsersController {
let app: Application
let users: EntityCollection<User>
init(app: Application, users: EntityCollection<User>) {
self.app = app
self.users = users
}
func someAction(request: Request) -> ResponseRepresentable {
let adultUsers = users.filtered(by: "age" > 18)
// `adultUsers` is now a new instance of EntityCollection<User>,
// and `users` itself is unchanged.
// And the database has not been queried yet.
for user in adultUsers {
// Only now that we started iterating over adultUsers,
// will the class start querying the database.
// The collection can optimize how often to query the DB, what batch-sizes to use,
// and the developer does not need to know about any of that.
user.active = false
app.email(user).with(template: "kids-hour.html", data: ["name": user.name])
}
// Any DB-related methods can only be called on the collection,
// not on the entities themselves.
adultUsers.save()
return Json(["status": "ok"])
}
}
Of course the developer is free to extend EntityCollection
to add their own convenience methods:
class UserCollection: EntityCollection<User> {
func getAdultUsers() -> Self {
return filtered(by: "age" > 18)
}
}
Although I'm not sure yet whether to do it like that, or to use a protocol:
protocol EntityCollection: Collection {
associatedtype T: Entity
internal var entities: [T] { get set }
internal var query: Query<T> { get }
init(_ query: Query<T>)
}
extension EntityCollection {
func filtered(by filter: Filter) -> Self {
return Self(query.filter(by: filter))
}
func sorted(by column: String, _ direction: Direction) -> Self {
return Self(query.sort(by: column, direction))
}
// Here we could add all the code necessary for conforming
// to the Collection protocol, like subscripts and whatnot.
}
class CollectionOf<T: Entity>: EntityCollection {
let query: Query<T>
init(_ query: Query<T>) {
self.query = query
}
}
class UserCollection: EntityCollection {
let query: Query<User>
init(_ query: Query<User>) {
self.query = query
}
func getAdultUsers() -> Self {
return filtered(by: "age" > 18)
}
}
Doctrine's EntityManager
has a flush()
method which basically means that all of the creating and updating of entities is queued and won't be executed until you call flush()
. That way EntityManager
can figure out how to persist all changes with as few queries as possible.
I like that and yet I hate having to call flush()
all the time. But right now I'm doing more or less the same thing when I call adultUsers.save()
. But what if we would use the deinit {}
method for that? If EntityCollection
automatically persists when it's deallocated then you can truly use it as any other collection without having to worry about how and when to persist to the DB.
And of course we could add a method or variable with which you can prevent this behavior like users.persistChanges = false
or whatever. The point is that I think it will probably be much more common that you do want to persist changes than that you don't want to persist changes. But I'm not 100% sure about that, so maybe we'd need to try it out in an example project.
This is strictly additive.
Either only supporting Active-Record style ORM, which I really don't prefer. Or building a full-fledged Doctrine-like DataMapper style ORM which I think costs a lot of time and energy and I'm not sure if the pay-off is that much better than this proposed solution.
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.
protocol Model { var id: String? { get } }
vs
extension Model { func find(id: Int) -> Self }
This database framework is vulnerable to SQL injection. I have created an example project showing this behavior here. You unfortunately can't build SQL statements with normal string manipulations. Instead, each SQL statement must be complete, with possible parameters. This is known as parameterized queries. For SQLite, this is a good resource to understand how they work, using sqlite3_prepare()
.
The impact of this is that any user can control the contents of the SQL statements being executed, which can allow them to dump databases, modify data, log in without a password, or drop tables.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.