Skip to content
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

Schemabuilder #305

Closed
Evertt opened this issue May 24, 2016 · 6 comments
Closed

Schemabuilder #305

Evertt opened this issue May 24, 2016 · 6 comments
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@Evertt
Copy link
Contributor

Evertt commented May 24, 2016

Schemabuilder

Introduction

I want to build a class which builds database-schemas from entities / models, but I noticed it takes quite some time to build. So before I put any more hours in it I want to see if the community agrees that it's valuable.

Motivation

Doctrine has this feature where it builds up the entire database based on your entities. Since I've started using this feature I haven't looked at my database once. And I really enjoy that. I don't need to think anymore whether or not the database schema matches my entities, because they're built automatically. And when I change anything about the infrastructure of my entities then doctrine automatically rebuilds the database for me without me having to do anything. I think that's awesome.

Proposed solution & Code snippets

So I've already written a bit of code with which I can take these models:

class Post: Model {
    id: Value?
    body: String
    author: User

    // initializers etc
}

class User: Model {
    id: Value?
    posts: [Post]
    birthday: NSDate

    // initializers etc
}

And do this with them:

let postSchema: Table = Table(from: Post.self)
let userSchema: Table = Table(from: User.self)

/*
Which would basically turns into this:

Table(columns: [
    "id": .value(.integer),
    "body": .value(.string),
    "author": .relationship(.belongsTo, User.self)
])

Table(columns: [
    "id": .value(.integer),
    "posts": .relationship(.hasMany, Post.self),
    "birthday": .value(.date)
])

The value types of the ids are undefined in the models,
so I just decided them to be ints for now.
That should be configurable though.
*/

Which in turn you can convert to a CREATE TABLE SQL statement. And maybe also something similar for MongoDB, but I don't know enough about MongoDB to say anything about that.

Impact

This should be strictly additive I think. Developers should be able to choose whether to let Vapor generate the tables or to design their own tables.

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.

@Evertt Evertt added the enhancement New feature or request label May 24, 2016
@Evertt Evertt self-assigned this May 24, 2016
@NathanFlurry
Copy link
Contributor

NathanFlurry commented May 25, 2016

Wouldn't this belong under Fluent? Also, I can't tell if the first code block is supposed to be Swift or a generic notation for defining data structures.

That said, I disagree with this approach, I feel that even though Swift is static language which sometimes requires one to write excess code to achieve certain tasks, we should refrain from using code generators. After all, we do have code snippets.

I propose creating a protocol Generated and utilizing a structure Column in order to define how the database is created, like so:

protocol Generated {
    func columns() -> [String: Column]
}
enum ColumnOption {
    case unique, manyToOne, oneToMany, custom(String) // Add more options
}

struct Column {
    init(type: Value.Type, options: ColumnOption...) {
        // Generate column
    }
}

In doing this, our final model would look something like this:

class User: Model, Generated {
    var id: Value?
    var name: String
    var email: String

    init(name: String, email: String) {
        self.name = name
        self.email = email
    }

    required init(serialized: [String: Value]) {
        id = serialized["id"]
        name = serialized["name"]?.string ?? ""
        email = serialized["email"]?.string ?? ""
    }

    func serialize() -> [String: Value?] {
        return [
                   "name": name,
                   "email": email
        ]
    }

    class var table: String {
        return "users"
    }

    class func columns() -> [String: Column] {
        return [
                   "name": Column(type: String.self),
                   "email": Column(type: String.self, options: .unique)
        ]
    }
}

At the beginning of the program, this line of code would be executed to generate the database:

Database.default.create(tables: .model(Post.self), .model(User.self), .columns(["value": Column(type: Int.self)]))

We should also consider how this works with migrating databases.

@Evertt
Copy link
Contributor Author

Evertt commented May 25, 2016

Wouldn't this belong under Fluent?

Yes it would. I made a mistake putting it here. Is it possible to move a thread to another repository?

Also, I can't tell if the first code block is supposed to be Swift or a generic notation for defining data structures.

It was pseudo-code, not real code. I tried to make it more clear, is it more clear now?

That said, I disagree with this approach, I feel that even though Swift is static language which sometimes requires one to write excess code to achieve certain tasks, we should refrain from using code generators.

Yeah I understand the confusion. Again I didn't intend to generate swift code.

enum ColumnOption {
    case unique, manyToOne, oneToMany, custom(String) // Add more options
}

Oh right I didn't think about the unique option. Honestly for every other option we wouldn't need the user to explicitly configure it. The type of the column, the name of the column, whether it should be nullable and whether it's a relationship to another model is all already clear from how people define the properties of their models.

Except for whether the column should be unique unfortunately, that is not clear from the Model itself. Which I mourn, because I really hoped I could keep the Model requirements as little as possible. But yes, now I'm leaning towards your solution as well.

@tanner0101
Copy link
Member

tanner0101 commented May 25, 2016

As I said in vapor/fluent#43 I'd like to avoid coupling the database and entities too much this early in Fluent.

Things that couple the database and models can easily be added into Fluent, but it's much harder to take them away.

I definitely support a schema builder of some sort–Fluent will require this. But I think it would be just as powerful and even more flexible with syntax that looks more like this:

Schema.create("users") { table in 
   table.int("id")
   table.string("first_name")
   table.string("last_name", maxLength: 128)
   ...
}

@NathanFlurry
Copy link
Contributor

@tannernelson I completely agree, but the idea is that with something like qutheroy/fluent#43 implemented, a implementation for automatic generating schemas for models could be build on top of this mechanism.

@tanner0101
Copy link
Member

tanner0101 commented May 25, 2016

Then we both agree 👍. Let's start with the manual schema builder first since I assume the entity-based automatic schema builder would be based upon the manual one.

@tanner0101 tanner0101 added this to the 0.12 milestone Jun 14, 2016
@tanner0101 tanner0101 assigned tanner0101 and unassigned Evertt Jun 14, 2016
@tanner0101
Copy link
Member

This is solved by #405

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants