I've been trying to link up my PostgreSQL database to a Swift Vapor project so I create routes to it. The first table I want to access is a table in my_database calls users. It has the properties user_id (primary integer key) and created_on (timestamp with time zone).
I've linked my Vapor project to my_database so that I can create new models. However, what if I want access to the pre-existing table users. Here's what I've cobbled together from the documentation and a few tutorials:
My initial model:
final class Users: Model {
static let name = "users"
typealias ID = Int
typealias Database = PostgreSQLDatabase
static let idKey: WritableKeyPath<Users, Int?> = \.user_id
var user_id: Int?
var created_on: Date
}
extension Users: Content { }
My migration (which I think is just a 'blank' migration, just to hook the project to the table?):
struct FirstMigration: PostgreSQLMigration {
static func prepare(on conn: PostgreSQLConnection) -> EventLoopFuture<Void> {
return conn.future()
}
static func revert(on conn: PostgreSQLConnection) -> EventLoopFuture<Void> {
return Future<Void>.done(on: conn)
}
}
And my configuration:
var migrations = MigrationConfig()
migrations.add(migration: FirstMigration.self, database: .psql)
services.register(migrations)
Any guidance much appreciated!
You don't need the migration. You either need to make your model confirm to Migration and add the model as a migration or set the static defaultDatabase property. Then you should be good to go.
PS - I think you want entity not name to tell it what the table name is.
Related
I have a Fluent (Postgres backed) model that requires type Decimal, but I'm only allowed to store .float/.double.
// Model
final class Stat: Model, Content, Equatable {
static let schema: String = "stats"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
#Field(key: "earned_run_average")
var era: Decimal // <-- CAN'T DO THIS
}
// Migration
struct CreateStatsTable: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
return database.schema(Stats.schema)
.id()
.field("name", .string, .required)
.field("earned_run_average", .decimal/*.decimal TYPE DOES NOT EXIST */, .required)
.create()
}
func revert(on database: Database) -> EventLoopFuture<Void> {
return database.schema(Stats.schema).delete()
}
}
The above model with migration will fail. If this is not easily achievable (as easy as .string or .double) is there a way to get a behavior similar to type Decimal in Fluent?
Proper Decimal support for Postgres was added recently.
So you should be able to use Decimal in the Model for encoding/decoding to and from a Decimal value in the Postgres DB without any issues.
As for the Migration, there still isn't a .decimal DataType so you can simply use a custom field for the Postgres Database like numeric as so:
.field("earned_run_average", .sql(raw: "NUMERIC(7,2)"), .required)
This should allow you to use Decimal without any issues.
I'm writing a web service in Swift using Vapor framework.
I have model named Item. Intially it has only name and id properties.
typealias VaporModel = Content & PostgreSQLModel & Parameter
final class Item: VaporModel {
var id: Int?
var name: String
}
After I configure a controller for the model and add the routes, when I hit the post Item request, I get the error as Model.defaultDatabase is required to use as DatabaseConnectable. I think the error is because I have not added Item to Migrations in configure.swift and I do the same after conforming Item to PostgreSQLMigration.
var migrations = MigrationConfig()
migrations.add(model: Item.self, database: .psql)
services.register(migrations)
Now, I am able to hit the post request and create items in the database.
So I understand that Migration protocol creates the default schema for a model and adds a new table to the database with the model's properties as columns.
Now I want to add a property such as price to my Item class. Now when I hit the post request, I get the error as column "price" of relation "Item" does not exist.
I assume the Migration protocol will be able to identify the schema changes and the column to my table (that's what I was used to in while using Realm for my iOS apps). But I am wrong and I read through the Migration docs and implement the prepare and revert methods in migration like below.
extension Item: PostgreSQLMigration {
static func prepare(on conn: PostgreSQLConnection) -> Future<Void> {
return Database.create(self, on: conn) { creator in
creator.field(for: \.price)
}
}
static func revert(on connection: PostgreSQLConnection) -> EventLoopFuture<Void> {
return Future.map(on: connection) { }
}
}
I'm still struck with the same error column "price" of relation "Item" does not exist. What am I missing here? Is my migration code correct?
Also, I understand that if am not making any changes to the Model, I can comment out the migration config, because they need not run every time I run the service. Is that correct?
With your code you haven't added a new migration. You have implemented a manual initial migration, but the initial migration has run already as requested (migrations.add(model: Item.self, database: .psql). To create a new migration you would need sth like:
struct ItemAddPriceMigration: Migration {
typealias Database = PostgreSQLDatabase
static func prepare(on conn: PostgreSQLConnection) -> EventLoopFuture<Void> {
return Database.update(Item.self, on: conn) { builder in
builder.field(for: \.price)
}
}
static func revert(on conn: PostgreSQLConnection) -> EventLoopFuture<Void> {
return conn.future()
}
}
And then you need to add it in configure:
migrations.add(migration: ItemAddPriceMigration.self, database: .psql)
I making chat backend and I need messages history table which will contain two users.
Is it some way to do something like that right way?
static func prepare(_ database: Database) throws {
try database.create("historys") { history in
history.id()
history.parent(User.self, optional: false)
history.parent(User.self, optional: false)
}
}
Now I'm getting an error of multiple user_id fields.
It should really be possible to set the field name in your preparation; this would be a useful enhancement.
In the meantime, though, you can get the same effect by creating an int field.
static func prepare(_ database: Database) throws {
try database.create("historys") { history in
history.id()
history.int("sender_user_id", optional: false)
history.int("recipient_user_id", optional: false)
}
}
In your model, you'll have properties senderUserId: Node and recipientUserId: Node, and you'll initialise them as e.g. senderUserId = try Node.extract("sender_user_id").
You can then fetch each relation using the following convenience methods on the model:
func sender() throws -> Parent<User> {
return try parent(senderUserId)
}
func recipient() throws -> Parent<User> {
return try parent(recipientUserId)
}
I am learning to parse JSON in Swift, coming from Android/Java, and I am using Unbox by John Sundell to help me with this, which reminds me of GSON.
Reference: Unbox pod
I use Realm as a database to store data locally.
Reference: Realm.io
It would be great to find a workflow to parse a class with JSON and save it to Realm. I don't want to have a struct that implements Unboxable AND a class that implements Object (Realm), because then I have to reflect the two. That isn't too much work for my current project, but it is kinda ugly...
Did any of you try a similar workflow?
I don't think you need two separate types. My suggestion is to create your objects as Swift classes that inherit from Realm's Object class, and then also conform them to the Unboxable protocol that Unbox offers. (Although the examples on Unbox's page use struct models, there's nothing in the code or documentation that indicates that classes wouldn't work.)
Realm model objects work just like any other classes: in addition to defining whatever properties on the objects you'd like stored in the database, you can also define methods and initializers, and even specify properties that you want Realm to ignore. This allows you to create an object that both serves as a Realm model and also a JSON model compatible with Unbox.
A more concise approach that doesn't require to override required initialisers (based on a tweet by Marin Todorov):
class Car: Object, Unboxable {
dynamic var vendor: String = ""
dynamic var modelName: String = ""
dynamic var electric: Bool = false
required convenience init(unboxer: Unboxer) throws {
self.init()
self.vendor = try unboxer.unbox(key: "vendor")
self.modelName = try unboxer.unbox(key: "modelName")
self.electric = try unboxer.unbox(key: "electric")
}
}
Here is an example that works perfectly for me:
class ProviderRealm: Object, Unboxable {
dynamic var identifier: String = "demo"
dynamic var name: String?
dynamic var logo: String?
/// Initializer used for unboxing of JSON string
required init(unboxer: Unboxer) throws {
self.identifier = (try? unboxer.unbox(key: "identifier")) ?? "demo"
self.name = try? unboxer.unbox(key: "name")
self.logo = try? unboxer.unbox(key: "logo")
super.init()
}
required init(realm: RLMRealm, schema: RLMObjectSchema) {
super.init(realm: realm, schema: schema)
}
required init() {
super.init()
}
required init(value: Any, schema: RLMSchema) {
super.init(value: value, schema: schema)
}
override static func primaryKey() -> String? {
return "identifier"
}
}
I'm currently working on using Realm together with the Swift framework Oatmeal. Now, both of those require model classes (Oatmeal can do automatic serialisation). My structure for a given Oatmeal model and Realm model are as follows:
class OatmealModel: SerializableObject {
var id: Int = 0
var someOtherProperty: String?
var anotherUnknownProperty: SomeType?
var store: RealmModel?
}
class RealmModel: Object {
dynamic var id: Int = 0
dynamic var someOtherProperty: String?
dynamic var anotherUnknownProperty: SomeType?
override static func primaryKey() -> String? {
return "id"
}
}
As should be clear, they both hold the same properties, but the OatmealModel is always the one I interact with. Aside from this, the OatmealModel also implements a protocol to retrieve data:
protocol PersistentStore {
func save()
func findStore()
func all() -> [PersistentStore]
func filter(predicate: NSPredicate) -> [PersistentStore]
}
These methods allow us to fetch from the Realm database and then hydrate the oatmeal model, and get only oatmeal models back. And then calling save will write back to realm.
So essentially, what I want to do is:
When retrieving entities from the Realm DB and hydrating Oatmeal models, this should be done automatically. Currently, every oatmeal model has to set the all and filter (and save) methods separately just to replace the class name, and the properties currently have to be set manually. My plan is to somehow use reflection on both the oatmeal model and the realm model to see which properties have the same name and type, and then write them from one to the other.
Currently, I have no idea how to go about this, so any hints on how I could use reflection for this would be helpful. I assume somehow use the Mirror to extract the properties and then loop through them and see if one of the same name exists in the other?
Also, to achieve this in the end, since it'll be the models doing this to return instances of itself, is it somehow possible for a model to create an array of its own type, without explicitly writing the type? E.g [Self] (which doesn't work) rather than [OatmealModel]?
I'm fairly new to Swift, but am familiar with a few other languages and concepts, any help is greatly appreciated!