Skip to content

Instantly share code, notes, and snippets.

@dennislysenko
Created October 20, 2015 15:54
Show Gist options
  • Save dennislysenko/ebc5092e85f5727eafa4 to your computer and use it in GitHub Desktop.
Save dennislysenko/ebc5092e85f5727eafa4 to your computer and use it in GitHub Desktop.
Genome+MappableCoreDataObject Example
//
// SocialAccount.swift
// Riff
//
// Created by Dennis Lysenko on 10/19/15.
// Copyright © 2015 Riff Digital. All rights reserved.
//
import Foundation
import CoreData
import Flex
import MagicalRecord
import SwiftyJSON
import Genome
// MARK: Genome+MappableCoreDataObject
typealias MappableCoreDataInitializer = (map: Map, context: NSManagedObjectContext) throws -> MappableObject
protocol MappableCoreDataObject: MappableObject {
static func initializer() -> MappableCoreDataInitializer
}
extension MappableCoreDataObject {
static func initializer() -> Initializer {
// return try initializer(context: NSManagedObjectContext.MR_defaultContext())
fatalError("no soup for you")
// Not sure how to resolve this
}
static func mappedInstance(js: Genome.JSON, context: NSManagedObjectContext) throws -> Self {
let map = Map(json: js, context: [:])
let initialization = initializer() // auto maps to the MappableCoreDataInitializer()
var instance = try initialization(map: map, context: context)
assert(instance is Self,
"Unfortunately, in order to support flexible subclassing, while also not enforcing a specific initializer, I need to be lenient with my type specifications in MappableObject protocol. Otherwise, an error is thrown 'You are unable to 'use Self in a non-parameter, non-result type position`. This means that although `Initializer` is of type `Map -> MappableObject`, it should really be considered `Map -> Self`!"
)
try instance.sequence(map)
return instance as! Self
}
}
// MARK: - Example Implementation
class SocialAccount: NSManagedObject, MappableCoreDataObject {
static func initializer() -> MappableCoreDataInitializer {
return findOrCreateInstance
}
private static func findOrCreateInstance(map: Map, context: NSManagedObjectContext) throws -> MappableObject {
let id: NSNumber = try <~map["id"]
if let existingAccount = SocialAccount.MR_findFirstByAttribute("id", withValue: id, inContext: context) {
return existingAccount
}
let account = SocialAccount.MR_createEntityInContext(context)
account.id = id.longLongValue
return account
}
func sequence(map: Map) throws {
try self.accountID <~> map["account_id"]
try self.accountName <~> map["account_name"]
try self.socialNetwork <~> map["social_network"].transformFromJson{ (input: String) -> SocialNetwork! in
if let network = SocialNetwork(rawValue: input) {
return network
}
throw RequestProcessingError.ParseError(message: "Invalid social network \(input)")
}.transformToJson { $0.rawValue }
}
// MARK: - Non-Genome stuff
var socialNetwork: SocialNetwork! {
get {
return SocialNetwork(rawValue: self.rawSocialNetwork)
}
set {
self.rawSocialNetwork = newValue.rawValue
}
}
class func loadAllFromServer(completion: (SimpleResult) -> ()) {
guard ActiveUser.loggedIn else {
return
}
RiffNetworking.getAsync("/v1/users/me/social_accounts.json", body: DictionaryBody(["access_token": ActiveUser.accessToken])) { (result) -> () in
switch result {
case .Success(let response) where response.status == 200:
guard let accounts = response.asJSON?["social_accounts"].array else {
return completion(.Failure(RequestProcessingError.ParseError(message: "No social_accounts array on response: \(response)")))
}
for accountJSON in accounts {
VinylRecord.saveWithBlock({ (context) -> () in
try SocialAccount.mappedInstance(accountJSON, context: context)
}, completion: { (result) -> () in
if case .Failure(let error) = result {
Rlog.e(self, error)
}
})
}
completion(.Success)
case .Success(let response): // unsuccessful response status
Rlog.e(self, "failed result \(result)")
completion(.Failure(RequestProcessingError.UnsuccessfulResponse(response: response)))
case .Failure(let error):
completion(.Failure(error))
}
}
}
static func create(socialNetwork socialNetwork: SocialNetwork, accountID: String, accountName: String, accessToken: String, completion: (Result<SocialAccount>) -> ()) {
RiffNetworking.postAsync("/v1/users/me/social_accounts.json", body: JSON(["access_token": ActiveUser.accessToken, "social_account": ["account_id": accountID, "account_name": accountName, "social_network": socialNetwork.rawValue]])) { (result) -> () in
switch result {
case .Success(let response) where response.status == 200 && response.asJSON != nil:
let json = response.asJSON!["social_account"]
VinylRecord.saveWithBlock { context, handler in
do {
let account = try SocialAccount.mappedInstance(json, context: context)
handler.onCompletion { result in completion(.Success(account)) }
} catch let error {
handler.onCompletion { _ in completion(.Failure(error)) }
}
}
case .Success(let response) where response.asJSON != nil:
completion(.Failure(RequestProcessingError.UnsuccessfulResponse(response: response)))
case .Success(let response):
completion(.Failure(RequestProcessingError.ParseError(message: "Response was not JSON \(response)")))
case .Failure(let error): completion(.Failure(error))
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment