Code Monkey home page Code Monkey logo

fluent's Introduction

Fluent

Documentation Team Chat MIT License Continuous Integration Swift 5.7+


The Fluent package makes it easy to use FluentKit in Vapor applications by tying the FluentKit database abstraction layer into the Vapor application lifecycle. It also provides helpers for mapping FluentKit's models to Vapor's authentication APIs.

For more information, see the Fluent documentation.

fluent's People

Contributors

0xtim avatar artkay avatar brettrtoomey avatar calebkleveter avatar cardoso avatar chriscombs avatar cuva avatar evertt avatar gwynne avatar jdmcd avatar jjaybrown avatar joannis avatar loganwright avatar luizzak avatar mihaelisaev avatar mozharovsky avatar mrmage avatar mz2 avatar natebird avatar prince2k3 avatar proggeramlug avatar rafiki270 avatar raulriera avatar rausnitz avatar samnung avatar siemensikkema avatar susandoggie avatar tanner0101 avatar timominous avatar vzsg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fluent's Issues

and/or chaining ambiguity

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.

Make the Migration entity "__fluent" instead of "fluent"

Name of Feature

Introduction

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

Motivation

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.

Proposed solution

I propose to give the table the name __fluent instead of fluent.

Code snippets

N/A

Impact

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.

Alternatives considered

It could also possibly be called something like meta_fluent.

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.

Model associations?

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.

Cassandra Driver

Would be great to have a Cassandra driver, if anyone is willing to work on it.

Best regards.

redis driver

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.

Objects don't update

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 = ?

filter operators

#32

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. :-)

use id from model in modify

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()
    }

Database property on Pivot is order dependent

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.

In Memory Storage

In Memory Storage

Introduction

An in memory storage mechanism for use with fluent would assist greatly during development.

Motivation

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.

Fluent does not set database in models if already 'prepared'

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.

UUID support for IDs

I would like to see support for ids 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.

Fluent/Sibling filter doesn't match field value

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.

Ability to get item ID after save

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.

default implementation for serialize

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.

Static Value objects

Static Value objects

Introduction

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:

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.

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.

Custom Character Sets

Feature, Enhancement, or Optimization

Name of Feature

Introduction

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.)

Motivation

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.

Proposed solution

Add custom character sets and collations to tables and columns.

Code snippets

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

Impact

This will not have any impact on existing code.

Alternatives considered

The alternative it to let users continue altering tables after being created with raw queries.

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.

entity collections

Create an immutable EntityCollection<T: Entity>

Introduction & Motivation

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.

Proposed solution

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.

Code snippets

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)
    }
}

Another interesting thought

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.

Impact

This is strictly additive.

Alternatives considered

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.

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.

.delete() doesn't trigger .willDelete() or .didDelete() in Models

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.
    }

    [...]

}

Many To Many Relationships Return Join Table Row ID

  1. Setup a many to many relationship.
  2. Fetch siblings for one of the entities.
  3. Iterate through their IDs.

Expected result
IDs should be of those associated object.

Actual result.
IDs are the rows inside of the join table.

Using multiple filters() results in SQL error

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.

Conflict on Entity.find and Schema.Creator.id

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 !

[Question] Postgres ETA

Hey, trying to run Vapor on Heroku, and sqlite isn't supported. Any sort of eta on postgres?

Pivot database not being prepared

Overview

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
}

Workaround

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.

Comments

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.

Extensible Schema Creation

Name of Feature

Introduction

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.

Proposed solution

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.

Impact

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.

Alternatives considered

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.

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.

Failable Initializer?

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 ๐Ÿ˜„

๐Ÿ›  Roadmap

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

  • Improved Testing
  • Improved Unions
    • Work with modifying and deleting.
    • Pre-load relational models

Fluent 2.0

  • Timestamps
  • Siblings relation needs Left and Right entities (as well as other relations)
  • Allow custom fields on Pivots.
  • Relation attach and detach
  • Fetch subset of data

Comment below with any ideas you have or things you would change or add to this list.

REST driver

Move the RESTDriver from Vapor University to Fluent.

Schema builder for relations

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)
    }
}

unbox

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

data fetching in fluent

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.

Schema support for Timestamp database type

Enhancement

Type Timestamp in Schema

Introduction

Schema support for SQL Timestamp types is needed to allow for efficient database indexing on timestamp types

Motivation

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.

Proposed solution

Add in Fluent the support for a .timestamp() Schema entry

Impact

This will need to be implemented by appropriate Providers to complete the implementation of this feature

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.

Preparations trigger table "already exists" error with SQLite

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")

Pagination

Like the collection, just with options for page=2 query

Add a pagination object to the response with

  • next page url
  • previous page url
  • current page
  • total pages
  • total count
  • count per page

Timestamps

Option to set have fluent set timestamps for updated_at and created_at

Like with entity I suggest having some static vars for

  • enable / disable the feature
  • override names for the fields

Important to have both support for int and datatime (unix and date time)

Vulnerable to SQL injection

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.

Versioning in preparation

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.

Introduction

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.

Proposed solution

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.

Model does not conform with JSONRepresentable

After updating to Vapor v0.12 I couldn't build my app due to:

Model does not conform with JSONRepresentable

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 ๐Ÿ˜ƒ

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.