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

Protocol with CRUD operations for structs #12

Closed
chiliec opened this issue Dec 2, 2015 · 17 comments
Closed

Protocol with CRUD operations for structs #12

chiliec opened this issue Dec 2, 2015 · 17 comments

Comments

@chiliec
Copy link

chiliec commented Dec 2, 2015

All my models is a structs and I can't use Record. RowConvertible protocol only allow to fetch models, but I want to give CRUD operations, at least.

@groue
Copy link
Owner

groue commented Dec 2, 2015

Hello @chiliec,

Are you asking for a protocol that would automagically add CRUD operations to your structs?

@chiliec
Copy link
Author

chiliec commented Dec 2, 2015

@groue yes, exactly )

@groue
Copy link
Owner

groue commented Dec 2, 2015

I did not chose this path, because :

  1. A protocol prevents changes tracking.
  2. A protocol prevents the save method from being final and reliably delegate its job to the customizable update and insert methods.
  3. Protocols don't provide super.

You may not care about the point 2 and 3, and I may not convince you with them - they are API design issue. See https://github.com/groue/GRDB.swift/blob/master/DESIGN.md for more information.

Yet, did you consider change tracking seriously? Even if you don't use it know, I mean. Change tracking avoids a lot of useless update requests, and has a real performance advantage (see https://gist.github.com/groue/dcdd3784461747874f41)

@groue
Copy link
Owner

groue commented Dec 2, 2015

BTW, what difficulty do you have adding your own insert(), update() and delete() methods to your structs?

@groue
Copy link
Owner

groue commented Dec 2, 2015

I've been experimenting a little.

First, of course, such a protocol can be written, and that's a good news because it means that you can write it yourself.

Now I'm not happy with the result, and that's not good for your issue to be solved by GRDB itself.

Particularly, since protocols don't provide super, there is no way for Record to inherit the default implementation provided by the protocol (the one you are asking for), and add change tracking on top of it. This leads to a lot of code duplication that can't be alleviated, as far as I can see.

If you have ideas on how to resolve that, I would be happy.

@groue
Copy link
Owner

groue commented Dec 3, 2015

@chiliec

Here is some sample code for adding your own CRUD functions to your structs:

// Setup database

var configuration = Configuration()
configuration.trace = { print($0) }
let dbQueue = DatabaseQueue(configuration: configuration)
try dbQueue.inDatabase { db in
    try db.execute(
        "CREATE TABLE persons (" +
            "id INTEGER PRIMARY KEY, " +
            "name TEXT, " +
            "url TEXT " +
        ")")
}


// A Struct with CRUD operations

struct Person : RowConvertible {
    var id: Int64?
    var name: String?
    var url: NSURL?

    init(id: Int64?, name: String?, url: NSURL?) {
        self.id = id
        self.name = name
        self.url = url
    }

    init(row: Row) {
        id = row.value(named: "id")
        name = row.value(named: "name")
        url = row.value(named: "url")
    }

    mutating func insert(db: Database) throws {
        let changes = try db.execute("INSERT INTO persons (name, url) VALUES (?, ?)", arguments: [name, url])
        id = changes.insertedRowID
    }

    func update(db: Database) throws {
        try db.execute("UPDATE persons SET name = ?, url = ? WHERE id = ?", arguments: [name, url, id])
    }

    func delete(db: Database) throws {
        try db.execute("DELETE FROM persons WHERE id = ?", arguments: [id])
    }
}

var arthur = Person(id: nil, name: "Arthur", url: nil)
var barbara = Person(id: nil, name: "Barbara", url: NSURL(string: "http://example.com"))

try dbQueue.inDatabase { db in

    // Insert
    try arthur.insert(db)
    try barbara.insert(db)

    // Update
    arthur.url = NSURL(string: "http://arthur.me")
    try arthur.update(db)

    // Delete
    try barbara.delete(db)

    // Select
    for person in Person.fetch(db, "SELECT * FROM persons") {
        print(person)
    }
}

The output is:

CREATE TABLE persons (id INTEGER PRIMARY KEY, name TEXT, url TEXT )
INSERT INTO persons (name, url) VALUES ('Arthur', NULL)
INSERT INTO persons (name, url) VALUES ('Barbara', 'http://example.com')
UPDATE persons SET name = 'Arthur', url = 'http://arthur.me' WHERE id = 1
DELETE FROM persons WHERE id = 2
SELECT * FROM persons
Person(id: Optional(1), name: Optional("Arthur"), url: Optional(http://arthur.me))

@groue groue changed the title Scarce protocol for structs Protocol with CRUD operations for structs Dec 3, 2015
@groue
Copy link
Owner

groue commented Dec 6, 2015

What solution did you pick, @chiliec ?

@chiliec
Copy link
Author

chiliec commented Dec 7, 2015

I'm still thinking :)

@groue
Copy link
Owner

groue commented Dec 7, 2015

Take your time! It wasn't an easy decision to leave structs out of the CRUD game...

@groue groue added the question label Dec 8, 2015
@groue
Copy link
Owner

groue commented Dec 8, 2015

Hi @chiliec, I've got some news.

I've tried again to write the protocol you look after, after realizing that functions declared in a protocol extension can be statically dispatched, which allows Record to reuse those methods in its own implementation, and avoid the code duplication. I was pretty happy, until...

Until I stumbled upon a nasty bug in Swift.

The insert, save and reload methods must be declared mutating (the first two may change the the primary key). And Swift oddly requires Record variables to be declared var:

class Person : Record { ... }
let person = Person(...)

// error: cannot use mutating member on immutable value: 'person' is a 'let' constant
person.save(db)

This is a wrong error, since this kind of mutability does not apply to class instances: there is no reason for person to be declared as var.

So, again, I'm afraid I still can not expose this protocol yet. But I have enough matter for a Swift bug report.

@groue
Copy link
Owner

groue commented Dec 8, 2015

Relevant Swift issue: https://bugs.swift.org/browse/SR-142

@groue
Copy link
Owner

groue commented Dec 9, 2015

@chiliec

Another update. Could you please check the DatabasePersistable branch?

It includes a DatabasePersistable protocol that may interest you.

Will you tell me what you think?

groue added a commit that referenced this issue Dec 10, 2015
… dispatched. The protocol exposes default implementation for CRUD methods as statically dispatched method that are available for adopting types in their own implementations of CRUD methods. Introduce DatabasePersistable.save() (#12)
groue added a commit that referenced this issue Dec 10, 2015
groue added a commit that referenced this issue Dec 10, 2015
…hen there is no INTEGER PRIMARY KEY column. DatabaseTableMapping no longer extends RowConvertible. (#12)
@groue groue removed the question label Dec 10, 2015
@groue
Copy link
Owner

groue commented Dec 10, 2015

I think we are on the way. Hold on, it's gonna ship soon.

@groue
Copy link
Owner

groue commented Dec 10, 2015

OK @chiliec.

The DatabasePersistable protocol has landed in the master branch. It just misses a tests before a new version of the library is shipped.

Documentation: https://github.com/groue/GRDB.swift#databasepersistable-protocol

Let me close this issue: you will open another if the protocol leaves something to be desired.

@groue groue closed this as completed Dec 10, 2015
@groue
Copy link
Owner

groue commented Dec 10, 2015

Thanks for your feature request, BTW 😄!

@groue
Copy link
Owner

groue commented Dec 11, 2015

@chiliec version v0.33.0 has just shipped with your protocol :bowtie:!

Er... there are two protocols, actually: check the updated documentation https://github.com/groue/GRDB.swift#databasepersistable-protocol

@chiliec
Copy link
Author

chiliec commented Dec 11, 2015

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants