├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── CoreDataCodable.swift ├── CoreDataExtensions.swift ├── Decoder.swift ├── Encoder.swift └── SwiftExtensions.swift └── Tests ├── CoreDataCodableTests ├── CoreDataCodableTests.swift ├── Summit22.json ├── Summit6.json ├── Summit7.json ├── SummitModel.xcdatamodeld │ └── Model.xcdatamodel │ │ └── contents ├── TestAttributes.swift ├── TestChild.swift ├── TestFullfilledParent.swift ├── TestModel.xcdatamodeld │ └── Model.xcdatamodel │ │ └── contents ├── TestNested.swift ├── TestParent.swift ├── TestSummitJSON.swift ├── TestSummitManagedObject.swift └── TestSummitModel.swift └── LinuxMain.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | .build/ 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | 69 | # Carthage 70 | Carthage 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Alsey Coleman Miller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "CoreDataCodable", 8 | products: [ 9 | // Products define the executables and libraries produced by a package, and make them visible to other packages. 10 | .library( 11 | name: "CoreDataCodable", 12 | targets: ["CoreDataCodable"]), 13 | ], 14 | dependencies: [ 15 | // Dependencies declare other packages that this package depends on. 16 | // .package(url: /* package url */, from: "1.0.0"), 17 | ], 18 | targets: [ 19 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 20 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 21 | .target( 22 | name: "CoreDataCodable", 23 | dependencies: [], 24 | path: "./Sources" 25 | ), 26 | .testTarget( 27 | name: "CoreDataCodableTests", 28 | dependencies: ["CoreDataCodable"] 29 | ) 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CoreDataCodable 2 | 3 | ## ⚠️ Deprecated, instead use [CoreModel](https://github.com/PureSwift/CoreModel) 4 | 5 | `CoreDataCodable` framework provides a `CoreDataEncoder` and `CoreDataDecoder` to encode and decode Swift `Codable` types to CoreData `NSManagedObject`. 6 | 7 | # How to use 8 | 9 | For `Codable` types you will need to implement a couple protocols to provide the necesary information for CoreData serialization. 10 | 11 | - `CoreDataIdentifier`: In order for the encoder and decoder to fetch `NSManagedObject` for a to-one or to-many relationship, create a custom value type that conforms to `CoreDataIdentifier` and implement its methods. An identifier can be ay attribute value except `Bool`. 12 | - `CoreDataCodable`: The value type (e.g. `struct`) that represents your entity should implement this protocol, as well as any to-one or to-many relationships that come as nested values. 13 | 14 | # Properties 15 | 16 | The `Encoder` and `Decoder` supports to-one and to-many relationships, as well as all supported CoreData attribute types (except `Transformable`). 17 | 18 | ``` 19 | struct Entity: Codable, CoreDataCodable { 20 | 21 | struct Identifier: Codable, RawRepresentable, CoreDataIdentifier { 22 | 23 | var rawValue: String 24 | 25 | init(rawValue: String) { 26 | 27 | self.rawValue = rawValue 28 | } 29 | } 30 | 31 | enum Device: String, Codable { 32 | 33 | case iPhone, iPad, Mac 34 | } 35 | 36 | var identifier: Identifier 37 | 38 | var boolean: Bool 39 | 40 | var data: Data 41 | 42 | var date: Date 43 | 44 | var decimal: Decimal 45 | 46 | var double: Double 47 | 48 | var float: Float 49 | 50 | var int16: Int16 51 | 52 | var int32: Int32 53 | 54 | var int64: Int64 55 | 56 | var string: String 57 | 58 | var uri: URL 59 | 60 | var uuid: UUID 61 | 62 | var device: Device 63 | 64 | var optional: String? 65 | 66 | var toOneIdentifier: OtherEntity.Identifier? 67 | 68 | var toManyIdentifiers: [OtherEntity.Identifier] 69 | 70 | var toOneNested: OtherEntity? 71 | 72 | var toManyNested: [OtherEntity] 73 | } 74 | 75 | struct OtherEntity: Codable, CoreDataCodable { 76 | 77 | struct Identifier: Codable, RawRepresentable, CoreDataIdentifier { 78 | 79 | var rawValue: Int64 80 | 81 | init(rawValue: Int64) { 82 | 83 | self.rawValue = rawValue 84 | } 85 | } 86 | 87 | var identifier: Identifier 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /Sources/CoreDataCodable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataCodable.swift 3 | // ColemanCDA 4 | // 5 | // Created by Alsey Coleman Miller on 11/1/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | // MARK: - CoreDataCodable 13 | 14 | /// Specifies how a type can be encoded to be stored with Core Data. 15 | public protocol CoreDataCodable: Codable { 16 | 17 | static var identifierKey: CodingKey { get } 18 | 19 | var coreDataIdentifier: CoreDataIdentifier { get } 20 | } 21 | 22 | extension CoreDataCodable { 23 | 24 | func encode(to managedObjectContext: NSManagedObjectContext) throws -> NSManagedObject { 25 | 26 | let encoder = CoreDataEncoder(managedObjectContext: managedObjectContext) 27 | 28 | let managedObject = try encoder.encode(self) 29 | 30 | return managedObject 31 | } 32 | } 33 | 34 | public extension Collection where Iterator.Element: CoreDataCodable { 35 | 36 | func save(_ context: NSManagedObjectContext) throws -> [NSManagedObject] { 37 | 38 | var managedObjects = ContiguousArray() 39 | managedObjects.reserveCapacity(numericCast(self.count)) 40 | 41 | for element in self { 42 | 43 | let managedObject = try element.encode(to: context) 44 | 45 | managedObjects.append(managedObject) 46 | } 47 | 48 | return Array(managedObjects) 49 | } 50 | } 51 | 52 | // MARK: - CoreDataIdentifier 53 | 54 | public protocol CoreDataIdentifier: Codable { 55 | 56 | /// Find or create managed object from identifier in the specified managed object. 57 | func findOrCreate(in context: NSManagedObjectContext) throws -> NSManagedObject 58 | 59 | /// Failible initializer to create identifier from managed object. 60 | init?(managedObject: NSManagedObject) 61 | } 62 | 63 | public protocol DecodableManagedObject: class { 64 | 65 | var decodable: CoreDataCodable.Type { get } 66 | 67 | var decodedIdentifier: Any { get } 68 | } 69 | 70 | public extension Sequence where Iterator.Element: CoreDataIdentifier { 71 | 72 | @inline(__always) 73 | func findOrCreate(in context: NSManagedObjectContext) throws -> [NSManagedObject] { 74 | 75 | return try map { try $0.findOrCreate(in: context) } 76 | } 77 | } 78 | 79 | internal extension Sequence where Iterator.Element: CodingKey { 80 | 81 | /// Convert CodingKey sequence into key path string. 82 | var keyPath: String { 83 | 84 | return self.reduce("", { $0 + "\($0.isEmpty ? "" : ".")" + $1.stringValue }) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Sources/CoreDataExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataExtensions.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/3/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | extension NSEntityDescription { 13 | 14 | func contains (key: Key) -> Bool { 15 | 16 | return self.allKeys.contains(key.stringValue) 17 | } 18 | 19 | var allKeys: [String] { 20 | 21 | // all properties plus 22 | return self.properties.map { $0.name } + (self.superentity?.allKeys ?? []) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/Encoder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Encoder.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/2/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | 12 | public final class CoreDataEncoder { 13 | 14 | // MARK: - Properties 15 | 16 | /// The managed object used to encode values. 17 | public let managedObjectContext: NSManagedObjectContext 18 | 19 | /// Any contextual information set by the user for encoding. 20 | public var userInfo = [CodingUserInfoKey : Any]() 21 | 22 | /// Logger handler 23 | public var log: Log? 24 | 25 | // MARK: - Initialization 26 | 27 | public init(managedObjectContext: NSManagedObjectContext) { 28 | 29 | self.managedObjectContext = managedObjectContext 30 | } 31 | 32 | // MARK: - Methods 33 | 34 | public func encode(_ encodable: Encodable) throws -> NSManagedObject { 35 | 36 | return try encode(encodable, codingPath: []) 37 | } 38 | 39 | fileprivate func encode(_ encodable: Encodable, codingPath: [CodingKey]) throws -> NSManagedObject { 40 | 41 | // get managed object 42 | let managedObject = try encodable.coreDataIdentifier.findOrCreate(in: managedObjectContext) 43 | 44 | // create encoder for managed object 45 | let encoder = Encoder(managedObjectContext: managedObjectContext, 46 | managedObject: managedObject, 47 | encodable: encodable, 48 | codingPath: codingPath, 49 | userInfo: userInfo, 50 | log: log) 51 | 52 | // encoder into container 53 | try encodable.encode(to: encoder) 54 | 55 | return managedObject 56 | } 57 | } 58 | 59 | // MARK: - Supporting Types 60 | 61 | public extension CoreDataEncoder { 62 | 63 | enum Error: Swift.Error { 64 | 65 | /// No key specified for container. 66 | case noKey 67 | 68 | /// Invalid selector (property doesn't exist) 69 | case invalidSelector(Selector) 70 | 71 | /// The type for the specified key does not match the type being encoded. 72 | case invalidType 73 | } 74 | 75 | typealias Log = (String) -> () 76 | } 77 | 78 | // MARK: - Encoder 79 | 80 | fileprivate extension CoreDataEncoder { 81 | 82 | class Encoder: Swift.Encoder { 83 | 84 | // MARK: - Properties 85 | 86 | /// The managed object used to encode values. 87 | public let managedObjectContext: NSManagedObjectContext 88 | 89 | /// The current managed object being encoded. 90 | public let managedObject: NSManagedObject 91 | 92 | /// The path of coding keys taken to get to this point in encoding. 93 | public fileprivate(set) var codingPath: [CodingKey] 94 | 95 | /// Any contextual information set by the user for encoding. 96 | public let userInfo: [CodingUserInfoKey : Any] 97 | 98 | /// The Swift encodable type being encoded. 99 | public let encodable: CoreDataCodable 100 | 101 | /// Logger 102 | public let log: Log? 103 | 104 | // MARK: - Initialization 105 | 106 | fileprivate init(managedObjectContext: NSManagedObjectContext, 107 | managedObject: NSManagedObject, 108 | encodable: CoreDataCodable, 109 | codingPath: [CodingKey], 110 | userInfo: [CodingUserInfoKey : Any], 111 | log: Log?) { 112 | 113 | self.managedObjectContext = managedObjectContext 114 | self.managedObject = managedObject 115 | self.encodable = encodable 116 | self.codingPath = codingPath 117 | self.userInfo = userInfo 118 | self.log = log 119 | } 120 | 121 | // MARK: - Methods 122 | 123 | public func container(keyedBy type: Key.Type) -> Swift.KeyedEncodingContainer where Key : CodingKey { 124 | 125 | let container = CoreDataEncoder.KeyedEncodingContainer(encoder: self) 126 | 127 | return Swift.KeyedEncodingContainer(container) 128 | } 129 | 130 | public func unkeyedContainer() -> Swift.UnkeyedEncodingContainer { 131 | 132 | return UnkeyedEncodingContainer(encoder: self) 133 | } 134 | 135 | public func singleValueContainer() -> Swift.SingleValueEncodingContainer { 136 | 137 | assert(self.codingPath.last != nil) 138 | 139 | return SingleValueEncodingContainer(encoder: self) 140 | } 141 | } 142 | } 143 | 144 | fileprivate extension CoreDataEncoder.Encoder { 145 | 146 | func set(_ value: NSObject?, forKey key: CodingKey) throws { 147 | 148 | // log 149 | log?("\(CoreDataEncoder.self): Will set \(value?.description ?? "nil") for key \"\(codingPath.reduce("", { $0 + "\($0.isEmpty ? "" : ".")" + $1.stringValue }))\"") 150 | 151 | // FIXME: test for valid property type 152 | 153 | let managedObject = self.managedObject 154 | 155 | // FIXME: Add option to throw or crash to improve performance 156 | 157 | let selector = Selector("set" + key.stringValue.capitalizingFirstLetter() + ":") 158 | 159 | guard managedObject.responds(to: selector) else { 160 | 161 | let context = EncodingError.Context(codingPath: codingPath, 162 | debugDescription: "No selector for the specified key.", 163 | underlyingError: CoreDataEncoder.Error.invalidSelector(selector)) 164 | 165 | let error = EncodingError.invalidValue(value as Any, context) 166 | 167 | throw error 168 | } 169 | 170 | // FIXME: call setter selector instead of `setValue:forKey` 171 | //self.container.perform(selector, with: value) 172 | 173 | // set value on object 174 | managedObject.setValue(value, forKey: key.stringValue) 175 | } 176 | } 177 | 178 | // MARK: - Concrete Value Representations 179 | 180 | private func box(_ value: Bool) -> NSObject { return NSNumber(value: value) } 181 | private func box(_ value: Int) -> NSObject { return NSNumber(value: value) } 182 | private func box(_ value: Int8) -> NSObject { return NSNumber(value: value) } 183 | private func box(_ value: Int16) -> NSObject { return NSNumber(value: value) } 184 | private func box(_ value: Int32) -> NSObject { return NSNumber(value: value) } 185 | private func box(_ value: Int64) -> NSObject { return NSNumber(value: value) } 186 | private func box(_ value: UInt) -> NSObject { return NSNumber(value: value) } 187 | private func box(_ value: UInt8) -> NSObject { return NSNumber(value: value) } 188 | private func box(_ value: UInt16) -> NSObject { return NSNumber(value: value) } 189 | private func box(_ value: UInt32) -> NSObject { return NSNumber(value: value) } 190 | private func box(_ value: UInt64) -> NSObject { return NSNumber(value: value) } 191 | private func box(_ value: Float) -> NSObject { return NSNumber(value: value) } 192 | private func box(_ value: Double) -> NSObject { return NSNumber(value: value) } 193 | private func box(_ value: String) -> NSObject { return NSString(string: value) } 194 | private func box(_ date: Date) -> NSObject { return date as NSDate } 195 | private func box(_ data: Data) -> NSObject { return data as NSData } 196 | private func box(_ uuid: UUID) -> NSObject { return uuid as NSUUID } 197 | private func box(_ url: URL) -> NSObject { return url as NSURL } 198 | private func box(_ decimal: Decimal) -> NSObject { return decimal as NSDecimalNumber } 199 | 200 | /* 201 | // MARK: - ReferenceEncoder 202 | 203 | fileprivate extension CoreDataEncoder { 204 | 205 | fileprivate final class ReferencingEncoder : Encoder { 206 | 207 | /// A reference to the encoder we're writing to. 208 | fileprivate let encoder: CoreDataEncoder.Encoder 209 | 210 | fileprivate init(referencing encoder: Encoder, at index: Int, wrapping array: NSMutableArray) { 211 | self.encoder = encoder 212 | self.reference = .array(array, index) 213 | super.init(options: encoder.options, codingPath: encoder.codingPath) 214 | 215 | self.codingPath.append(_JSONKey(index: index)) 216 | } 217 | 218 | func container(keyedBy type: Key.Type) -> KeyedEncodingContainer where Key : CodingKey { 219 | 220 | } 221 | 222 | func unkeyedContainer() -> UnkeyedEncodingContainer { 223 | 224 | } 225 | 226 | func singleValueContainer() -> SingleValueEncodingContainer { 227 | 228 | } 229 | } 230 | } 231 | */ 232 | // MARK: - KeyedEncodingContainer 233 | 234 | fileprivate extension CoreDataEncoder { 235 | 236 | struct KeyedEncodingContainer : KeyedEncodingContainerProtocol { 237 | 238 | public typealias Key = K 239 | 240 | /// A reference to the encoder we're writing to. 241 | fileprivate let encoder: CoreDataEncoder.Encoder 242 | 243 | /// A reference to the container we're writing to. 244 | private var container: NSManagedObject { 245 | 246 | get { return encoder.managedObject } 247 | } 248 | 249 | /// The path of coding keys taken to get to this point in encoding. 250 | public private(set) var codingPath: [CodingKey] { 251 | 252 | get { return encoder.codingPath } 253 | 254 | mutating set { encoder.codingPath = newValue } 255 | } 256 | 257 | // Standard primitive types 258 | public mutating func encodeNil(forKey key: Key) throws { try write(nil, forKey: key) } 259 | public mutating func encode(_ value: Bool, forKey key: Key) throws { try write(box(value), forKey: key) } 260 | public mutating func encode(_ value: Int, forKey key: Key) throws { try write(box(value), forKey: key) } 261 | public mutating func encode(_ value: Int8, forKey key: Key) throws { try write(box(value), forKey: key) } 262 | public mutating func encode(_ value: Int16, forKey key: Key) throws { try write(box(value), forKey: key) } 263 | public mutating func encode(_ value: Int32, forKey key: Key) throws { try write(box(value), forKey: key) } 264 | public mutating func encode(_ value: Int64, forKey key: Key) throws { try write(box(value), forKey: key) } 265 | public mutating func encode(_ value: UInt, forKey key: Key) throws { try write(box(value), forKey: key) } 266 | public mutating func encode(_ value: UInt8, forKey key: Key) throws { try write(box(value), forKey: key) } 267 | public mutating func encode(_ value: UInt16, forKey key: Key) throws { try write(box(value), forKey: key) } 268 | public mutating func encode(_ value: UInt32, forKey key: Key) throws { try write(box(value), forKey: key) } 269 | public mutating func encode(_ value: UInt64, forKey key: Key) throws { try write(box(value), forKey: key) } 270 | public mutating func encode(_ value: String, forKey key: Key) throws { try write(box(value), forKey: key) } 271 | public mutating func encode(_ value: Float, forKey key: Key) throws { try write(box(value), forKey: key) } 272 | public mutating func encode(_ value: Double, forKey key: Key) throws { try write(box(value), forKey: key) } 273 | 274 | // Custom 275 | private mutating func encode(_ value: Data, forKey key: Key) throws { try write(box(value), forKey: key) } 276 | private mutating func encode(_ value: Date, forKey key: Key) throws { try write(box(value), forKey: key) } 277 | private mutating func encode(_ value: Decimal, forKey key: Key) throws { try write(box(value), forKey: key) } 278 | 279 | private mutating func encode(_ value: UUID, forKey key: Key) throws { 280 | 281 | // check if attribute is string 282 | let attribute = container.entity.attributesByName[key.stringValue]?.attributeType ?? .undefinedAttributeType 283 | 284 | switch attribute { 285 | 286 | case .UUIDAttributeType: 287 | 288 | try write(box(value), forKey: key) 289 | 290 | case .stringAttributeType: 291 | 292 | try write(box(value.uuidString), forKey: key) 293 | 294 | default: 295 | 296 | // set coding key context 297 | codingPath.append(key) 298 | defer { codingPath.removeLast() } 299 | 300 | throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Invalid value type")) 301 | } 302 | } 303 | 304 | private mutating func encode(_ value: URL, forKey key: Key) throws { 305 | 306 | // check if attribute is string 307 | let attribute = container.entity.attributesByName[key.stringValue]?.attributeType ?? .undefinedAttributeType 308 | 309 | switch attribute { 310 | 311 | case .URIAttributeType: 312 | 313 | try write(box(value), forKey: key) 314 | 315 | case .stringAttributeType: 316 | 317 | try write(box(value.absoluteString), forKey: key) 318 | 319 | default: 320 | 321 | // set coding key context 322 | codingPath.append(key) 323 | defer { codingPath.removeLast() } 324 | 325 | throw EncodingError.invalidValue(value, EncodingError.Context(codingPath: codingPath, debugDescription: "Invalid value type")) 326 | } 327 | } 328 | 329 | // Encodable 330 | public mutating func encode(_ value: T, forKey key: Key) throws { 331 | 332 | // override for CoreData supported native types that also are Encodable 333 | // and don't use encodable implementation 334 | 335 | // identifier or to-one relationship 336 | if let identifier = value as? CoreDataIdentifier { 337 | 338 | let identifierKey = type(of: encoder.encodable).identifierKey.stringValue 339 | 340 | // identifier 341 | if key.stringValue == identifierKey { 342 | 343 | // skip value since we assume managed object is already faulted 344 | assert(container.value(forKey: identifierKey) != nil, "No identifier set") 345 | 346 | } else { 347 | 348 | // set relationship value 349 | try setRelationship(identifier, forKey: key) 350 | } 351 | 352 | } else if let encodable = value as? CoreDataCodable { 353 | 354 | try setRelationship(encodable, forKey: key) 355 | 356 | } else if let array = value as? [CoreDataIdentifier] { 357 | 358 | try setRelationship(array, forKey: key) 359 | 360 | } else if let set = value as? Set, 361 | let array = Array(set) as? [CoreDataIdentifier] { 362 | 363 | try setRelationship(array, forKey: key) 364 | 365 | } else if let array = value as? [CoreDataCodable] { 366 | 367 | try setRelationship(array, forKey: key) 368 | 369 | } else if let set = value as? Set, 370 | let array = Array(set) as? [CoreDataCodable] { 371 | 372 | try setRelationship(array, forKey: key) 373 | 374 | } else if let value = value as? Data { 375 | 376 | try encode(value, forKey: key) 377 | 378 | } else if let value = value as? Date { 379 | 380 | try encode(value, forKey: key) 381 | 382 | } else if let value = value as? UUID { 383 | 384 | try encode(value, forKey: key) 385 | 386 | } else if let value = value as? URL { 387 | 388 | try encode(value, forKey: key) 389 | 390 | } else if let value = value as? Decimal { 391 | 392 | try encode(value, forKey: key) 393 | 394 | } else { 395 | 396 | // set coding key context 397 | codingPath.append(key) 398 | defer { codingPath.removeLast() } 399 | 400 | // get value 401 | try value.encode(to: encoder) 402 | } 403 | } 404 | 405 | public mutating func nestedContainer(keyedBy keyType: NestedKey.Type, forKey key: K) -> Swift.KeyedEncodingContainer where NestedKey : CodingKey { 406 | 407 | fatalError() 408 | } 409 | 410 | public mutating func nestedUnkeyedContainer(forKey key: K) -> UnkeyedEncodingContainer { 411 | 412 | fatalError() 413 | } 414 | 415 | public mutating func superEncoder() -> Swift.Encoder { 416 | 417 | fatalError() 418 | } 419 | 420 | public mutating func superEncoder(forKey key: Key) -> Swift.Encoder { 421 | 422 | fatalError() 423 | } 424 | 425 | private mutating func write(_ value: NSObject?, forKey key: Key) throws { 426 | 427 | // set coding key context 428 | codingPath.append(key) 429 | defer { codingPath.removeLast() } 430 | 431 | // set value 432 | try encoder.set(value, forKey: key) 433 | } 434 | 435 | private mutating func setRelationship(_ identifiers: [CoreDataIdentifier], forKey key: Key) throws { 436 | 437 | let managedObjectContext = encoder.managedObjectContext 438 | 439 | let managedObjects = try identifiers.map { try $0.findOrCreate(in: managedObjectContext) } 440 | 441 | let isOrdered = self.encoder.managedObject.entity.relationshipsByName[key.stringValue]?.isOrdered ?? false 442 | 443 | let set: NSObject = isOrdered ? NSOrderedSet(array: managedObjects) : NSSet(array: managedObjects) 444 | 445 | // set value 446 | try write(set, forKey: key) 447 | } 448 | 449 | private mutating func setRelationship(_ encodables: [CoreDataCodable], forKey key: Key) throws { 450 | 451 | let managedObjectContext = encoder.managedObjectContext 452 | 453 | var managedObjects = [NSManagedObject]() 454 | managedObjects.reserveCapacity(encodables.count) 455 | 456 | for (index, encodable) in encodables.enumerated() { 457 | 458 | let codingPath: [CodingKey] = self.codingPath + [key, Index(intValue: index)] 459 | 460 | let managedObject = try encodable.coreDataIdentifier.findOrCreate(in: managedObjectContext) 461 | managedObjects.append(managedObject) 462 | 463 | // create encoder for managed object 464 | let encoder = Encoder(managedObjectContext: self.encoder.managedObjectContext, 465 | managedObject: managedObject, 466 | encodable: encodable, 467 | codingPath: codingPath, 468 | userInfo: self.encoder.userInfo, 469 | log: self.encoder.log) 470 | 471 | // encoder into container 472 | try encodable.encode(to: encoder) 473 | } 474 | 475 | let isOrdered = self.encoder.managedObject.entity.relationshipsByName[key.stringValue]?.isOrdered ?? false 476 | 477 | let set: NSObject = isOrdered ? NSOrderedSet(array: managedObjects) : NSSet(array: managedObjects) 478 | 479 | // set value 480 | try write(set, forKey: key) 481 | } 482 | 483 | private mutating func setRelationship(_ identifier: CoreDataIdentifier, forKey key: Key) throws { 484 | 485 | // get managed object fault 486 | let managedObject = try identifier.findOrCreate(in: encoder.managedObjectContext) 487 | 488 | // set value 489 | try write(managedObject, forKey: key) 490 | } 491 | 492 | private mutating func setRelationship(_ encodable: CoreDataCodable, forKey key: Key) throws { 493 | 494 | let managedObject = try encodable.coreDataIdentifier.findOrCreate(in: self.encoder.managedObjectContext) 495 | 496 | let codingPath: [CodingKey] = self.codingPath + [key] 497 | 498 | // create encoder for managed object 499 | let newEncoder = Encoder(managedObjectContext: self.encoder.managedObjectContext, 500 | managedObject: managedObject, 501 | encodable: encodable, 502 | codingPath: codingPath, 503 | userInfo: self.encoder.userInfo, 504 | log: self.encoder.log) 505 | 506 | // encoder into container 507 | try encodable.encode(to: newEncoder) 508 | 509 | // set value 510 | try write(managedObject, forKey: key) 511 | } 512 | } 513 | } 514 | 515 | // MARK: - SingleValueEncodingContainer 516 | 517 | fileprivate extension CoreDataEncoder.Encoder { 518 | 519 | struct SingleValueEncodingContainer: Swift.SingleValueEncodingContainer { 520 | 521 | fileprivate let encoder: CoreDataEncoder.Encoder 522 | 523 | /// A reference to the container we're writing to. 524 | private var container: NSManagedObject { 525 | 526 | get { return encoder.managedObject } 527 | } 528 | 529 | public var codingPath: [CodingKey] { 530 | 531 | get { return encoder.codingPath } 532 | } 533 | 534 | public func encodeNil() throws { 535 | 536 | try write(nil) 537 | } 538 | 539 | public func encode(_ value: Bool) throws { 540 | 541 | try write(box(value)) 542 | } 543 | 544 | public func encode(_ value: Int) throws { 545 | 546 | try write(box(value)) 547 | } 548 | 549 | public func encode(_ value: Int8) throws { 550 | 551 | try write(box(value)) 552 | } 553 | 554 | public func encode(_ value: Int16) throws { 555 | 556 | try write(box(value)) 557 | } 558 | 559 | public func encode(_ value: Int32) throws { 560 | 561 | try write(box(value)) 562 | } 563 | 564 | public func encode(_ value: Int64) throws { 565 | 566 | try write(box(value)) 567 | } 568 | 569 | public func encode(_ value: UInt) throws { 570 | 571 | try write(box(value)) 572 | } 573 | 574 | public func encode(_ value: UInt8) throws { 575 | 576 | try write(box(value)) 577 | } 578 | 579 | public func encode(_ value: UInt16) throws { 580 | 581 | try write(box(value)) 582 | } 583 | 584 | public func encode(_ value: UInt32) throws { 585 | 586 | try write(box(value)) 587 | } 588 | 589 | public func encode(_ value: UInt64) throws { 590 | 591 | try write(box(value)) 592 | } 593 | 594 | public func encode(_ value: String) throws { 595 | 596 | try write(box(value)) 597 | } 598 | 599 | public func encode(_ value: Float) throws { 600 | 601 | try write(box(value)) 602 | } 603 | 604 | public func encode(_ value: Double) throws { 605 | 606 | try write(box(value)) 607 | } 608 | 609 | public func encode(_ value: T) throws { 610 | 611 | try value.encode(to: encoder) 612 | } 613 | 614 | private func write(_ value: NSObject?) throws { 615 | 616 | guard let codingKey = self.codingPath.last else { 617 | 618 | let context = EncodingError.Context(codingPath: codingPath, 619 | debugDescription: "No key was provided for single value container.", 620 | underlyingError: CoreDataEncoder.Error.noKey) 621 | 622 | let error = EncodingError.invalidValue(value as Any, context) 623 | 624 | throw error 625 | } 626 | 627 | // set value 628 | try encoder.set(value, forKey: codingKey) 629 | } 630 | } 631 | } 632 | 633 | // MARK: - UnkeyedEncodingContainer 634 | 635 | fileprivate extension CoreDataEncoder.Encoder { 636 | 637 | struct UnkeyedEncodingContainer: Swift.UnkeyedEncodingContainer { 638 | 639 | fileprivate let encoder: CoreDataEncoder.Encoder 640 | 641 | /// A reference to the container we're writing to. 642 | private var container: NSManagedObject { 643 | 644 | get { return encoder.managedObject } 645 | } 646 | 647 | var codingPath: [CodingKey] { 648 | 649 | get { return encoder.codingPath } 650 | } 651 | 652 | var count: Int { return (try? collection().count) ?? 0 } 653 | 654 | mutating func encodeNil() throws { 655 | 656 | // do nothing 657 | // FIXME: Add option to throw error 658 | } 659 | 660 | mutating func encode(_ value: Bool) throws { 661 | 662 | throw invalidTypeError(for: value) 663 | } 664 | 665 | mutating func encode(_ value: Int) throws { 666 | 667 | throw invalidTypeError(for: value) 668 | } 669 | 670 | mutating func encode(_ value: Int8) throws { 671 | 672 | throw invalidTypeError(for: value) 673 | } 674 | 675 | mutating func encode(_ value: Int16) throws { 676 | 677 | throw invalidTypeError(for: value) 678 | } 679 | 680 | mutating func encode(_ value: Int32) throws { 681 | 682 | throw invalidTypeError(for: value) 683 | } 684 | 685 | mutating func encode(_ value: Int64) throws { 686 | 687 | throw invalidTypeError(for: value) 688 | } 689 | 690 | mutating func encode(_ value: UInt) throws { 691 | 692 | throw invalidTypeError(for: value) 693 | } 694 | 695 | mutating func encode(_ value: UInt8) throws { 696 | 697 | throw invalidTypeError(for: value) 698 | } 699 | 700 | mutating func encode(_ value: UInt16) throws { 701 | 702 | throw invalidTypeError(for: value) 703 | } 704 | 705 | mutating func encode(_ value: UInt32) throws { 706 | 707 | throw invalidTypeError(for: value) 708 | } 709 | 710 | mutating func encode(_ value: UInt64) throws { 711 | 712 | throw invalidTypeError(for: value) 713 | } 714 | 715 | mutating func encode(_ value: Float) throws { 716 | 717 | throw invalidTypeError(for: value) 718 | } 719 | 720 | mutating func encode(_ value: Double) throws { 721 | 722 | throw invalidTypeError(for: value) 723 | } 724 | 725 | mutating func encode(_ value: String) throws { 726 | 727 | throw invalidTypeError(for: value) 728 | } 729 | 730 | mutating func encode(_ value: T) throws where T : Swift.Encodable { 731 | 732 | if let identifier = value as? CoreDataIdentifier { 733 | 734 | let managedObject = try identifier.findOrCreate(in: encoder.managedObjectContext) 735 | 736 | try write(managedObject) 737 | 738 | } else { 739 | 740 | throw invalidTypeError(for: value) 741 | } 742 | } 743 | 744 | mutating func nestedContainer(keyedBy keyType: NestedKey.Type) -> Swift.KeyedEncodingContainer where NestedKey : CodingKey { 745 | 746 | fatalError() 747 | } 748 | 749 | mutating func nestedUnkeyedContainer() -> Swift.UnkeyedEncodingContainer { 750 | fatalError() 751 | } 752 | 753 | mutating func superEncoder() -> Swift.Encoder { 754 | 755 | return encoder 756 | } 757 | 758 | private func codingKey() throws -> CodingKey { 759 | 760 | guard let codingKey = self.codingPath.last 761 | else { throw CoreDataEncoder.Error.noKey } 762 | 763 | return codingKey 764 | } 765 | 766 | /// Get array of to-many relationship 767 | private func collection() throws -> [NSManagedObject] { 768 | 769 | let key = try codingKey() 770 | 771 | if let value = container.value(forKey: key.stringValue) as! NSObject? { 772 | 773 | if let set = value as? Set { 774 | 775 | return Array(set) 776 | 777 | } else if let orderedSet = value as? NSOrderedSet { 778 | 779 | return orderedSet.array as! [NSManagedObject] 780 | 781 | } else { 782 | 783 | throw CoreDataEncoder.Error.invalidType 784 | } 785 | 786 | } else { 787 | 788 | return [] 789 | } 790 | } 791 | 792 | private func write(_ managedObject: NSManagedObject) throws { 793 | 794 | let key = try codingKey() 795 | 796 | var managedObjects = try collection() 797 | 798 | managedObjects.append(managedObject) 799 | 800 | let set = NSSet(array: managedObjects) 801 | 802 | // set value 803 | try encoder.set(set, forKey: key) 804 | } 805 | 806 | private func invalidTypeError(for value: Any) -> Error { 807 | 808 | let context = EncodingError.Context(codingPath: codingPath, 809 | debugDescription: "The expected value should be a relationship.", 810 | underlyingError: CoreDataEncoder.Error.invalidType) 811 | 812 | let error = EncodingError.invalidValue(value, context) 813 | 814 | return error 815 | } 816 | } 817 | } 818 | 819 | fileprivate extension CoreDataEncoder { 820 | 821 | struct Index: CodingKey { 822 | 823 | public let index: Int 824 | 825 | public init(intValue: Int) { 826 | 827 | self.index = intValue 828 | } 829 | 830 | init?(stringValue: String) { 831 | 832 | return nil 833 | } 834 | 835 | public var intValue: Int? { 836 | return index 837 | } 838 | 839 | public var stringValue: String { 840 | return "\(index)" 841 | } 842 | } 843 | } 844 | 845 | -------------------------------------------------------------------------------- /Sources/SwiftExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/2/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | internal extension String { 10 | 11 | func capitalizingFirstLetter() -> String { 12 | return prefix(1).uppercased() + dropFirst() 13 | } 14 | 15 | mutating func capitalizeFirstLetter() { 16 | self = self.capitalizingFirstLetter() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/CoreDataCodableTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CoreDataCodableTests.swift 3 | // ColemanCDA 4 | // 5 | // Created by Alsey Coleman Miller on 11/1/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import XCTest 11 | import CoreData 12 | import CoreDataCodable 13 | 14 | let SummitJSONIdentifiers = [6, 7, 22] 15 | 16 | final class CoreDataCodableTests: XCTestCase { 17 | 18 | func testAttributes() { 19 | 20 | let value = TestAttributes(identifier: TestAttributes.Identifier(rawValue: "test01"), 21 | boolean: true, 22 | data: Data([0x01, 0x02, 0x03]), 23 | date: Date(), 24 | decimal: Decimal(100.555555), 25 | double: 1.66666, 26 | float: 1.5555, 27 | int16: 16, 28 | int32: 32, 29 | int64: 64, 30 | string: "test", 31 | uri: URL(string: "https://swift.org")!, 32 | uuid: UUID(), 33 | //urlValue: URL(string: "https://apple.com")!, 34 | //uuidValue: UUID(), 35 | enumValue: .three, 36 | optional: nil) 37 | 38 | XCTAssertNoThrow(try context { 39 | 40 | let encoder = CoreDataEncoder(managedObjectContext: $0) 41 | encoder.log = { print($0) } 42 | 43 | print("Will encode") 44 | 45 | let managedObject = try encoder.encode(value) as! TestAttributesManagedObject 46 | 47 | print("Did encode") 48 | 49 | print(managedObject) 50 | 51 | XCTAssert(managedObject.identifier == value.identifier.rawValue) 52 | XCTAssert(managedObject.boolean == value.boolean) 53 | XCTAssert(managedObject.data == value.data) 54 | XCTAssert(managedObject.date == value.date) 55 | XCTAssert(managedObject.decimal.description == value.decimal.description) // NSDecimal bug? 56 | XCTAssert(managedObject.double == value.double) 57 | XCTAssert(managedObject.float == value.float) 58 | XCTAssert(managedObject.int16 == value.int16) 59 | XCTAssert(managedObject.int32 == value.int32) 60 | XCTAssert(managedObject.int64 == value.int64) 61 | XCTAssert(managedObject.string == value.string) 62 | XCTAssert(managedObject.uri == value.uri) 63 | XCTAssert(managedObject.uuid == value.uuid) 64 | //XCTAssert(managedObject.urlValue == value.urlValue.absoluteString) 65 | //XCTAssert(managedObject.uuidValue == value.uuidValue.uuidString) 66 | XCTAssert(managedObject.enumValue == value.enumValue.rawValue) 67 | XCTAssertNil(managedObject.optional) 68 | 69 | let decoder = CoreDataDecoder(managedObjectContext: $0) 70 | decoder.log = { print($0) } 71 | 72 | print("Will decode") 73 | 74 | let decoded = try decoder.decode(TestAttributes.self, with: value.identifier) 75 | 76 | print("Did decode") 77 | 78 | XCTAssert(decoded.identifier == value.identifier) 79 | XCTAssert(decoded.boolean == value.boolean) 80 | XCTAssert(decoded.data == value.data) 81 | XCTAssert(decoded.date == value.date) 82 | XCTAssert(decoded.decimal == value.decimal) 83 | XCTAssert(decoded.double == value.double) 84 | XCTAssert(decoded.float == value.float) 85 | XCTAssert(decoded.int16 == value.int16) 86 | XCTAssert(decoded.int32 == value.int32) 87 | XCTAssert(decoded.int64 == value.int64) 88 | XCTAssert(decoded.string == value.string) 89 | XCTAssert(decoded.uri == value.uri) 90 | XCTAssert(decoded.uuid == value.uuid) 91 | XCTAssert(decoded.enumValue == value.enumValue) 92 | XCTAssertNil(decoded.optional) 93 | XCTAssert("\(decoded)" == "\(value)") 94 | 95 | try $0.save() 96 | }) 97 | } 98 | 99 | func testFaultRelationship() { 100 | 101 | let parent = TestParent(identifier: TestParent.Identifier(rawValue: 100), 102 | child: TestChild.Identifier(rawValue: "child01"), 103 | children: [TestChild.Identifier(rawValue: "children01"), 104 | TestChild.Identifier(rawValue: "children02")]) 105 | 106 | XCTAssertNoThrow(try context { 107 | 108 | let encoder = CoreDataEncoder(managedObjectContext: $0) 109 | encoder.log = { print($0) } 110 | 111 | print("Will encode") 112 | 113 | let managedObject = try encoder.encode(parent) as! TestParentManagedObject 114 | 115 | print("Did encode") 116 | 117 | print(managedObject) 118 | 119 | XCTAssert(managedObject.identifier == parent.identifier.rawValue) 120 | XCTAssert(Set(managedObject.children.map({ $0.identifier })) == Set(parent.children.map({ $0.rawValue }))) 121 | XCTAssert(managedObject.child?.identifier == parent.child?.rawValue) 122 | 123 | let decoder = CoreDataDecoder(managedObjectContext: $0) 124 | decoder.log = { print($0) } 125 | 126 | print("Will decode") 127 | 128 | let decoded = try decoder.decode(TestParent.self, with: parent.identifier) 129 | 130 | print("Did decode") 131 | 132 | XCTAssert(decoded.identifier == parent.identifier) 133 | XCTAssert(decoded.child?.rawValue == parent.child?.rawValue) 134 | XCTAssert(Set(decoded.children.map({ $0.rawValue })) == Set(parent.children.map({ $0.rawValue }))) 135 | 136 | try $0.save() 137 | }) 138 | } 139 | 140 | func testFulfilledRelationships() { 141 | 142 | let parentIdentifier = TestFullfilledParent.Identifier(rawValue: 100) 143 | 144 | let child = TestChild(identifier: TestChild.Identifier(rawValue: "child01"), 145 | parent: nil, 146 | parentToOne: parentIdentifier) 147 | 148 | let children = [ 149 | TestChild(identifier: TestChild.Identifier(rawValue: "children01"), parent: parentIdentifier, parentToOne: nil), 150 | TestChild(identifier: TestChild.Identifier(rawValue: "children02"), parent: parentIdentifier, parentToOne: nil), 151 | TestChild(identifier: TestChild.Identifier(rawValue: "children03"), parent: parentIdentifier, parentToOne: nil) 152 | ] 153 | 154 | let parent = TestFullfilledParent( 155 | identifier: parentIdentifier, 156 | child: child, 157 | children: children 158 | ) 159 | 160 | XCTAssertNoThrow(try context { 161 | 162 | let encoder = CoreDataEncoder(managedObjectContext: $0) 163 | encoder.log = { print($0) } 164 | 165 | let managedObject = try encoder.encode(parent) as! TestParentManagedObject 166 | 167 | print(managedObject) 168 | 169 | XCTAssert(managedObject.identifier == parent.identifier.rawValue) 170 | XCTAssert(Set(managedObject.children.map({ $0.identifier })) == Set(parent.children.map({ $0.identifier.rawValue }))) 171 | XCTAssert(managedObject.child?.identifier == parent.child?.identifier.rawValue) 172 | 173 | let decoder = CoreDataDecoder(managedObjectContext: $0) 174 | decoder.log = { print($0) } 175 | 176 | print("Will decode") 177 | 178 | let decoded = try decoder.decode(TestFullfilledParent.self, with: parent.identifier) 179 | 180 | print("Did decode") 181 | 182 | XCTAssert(decoded.identifier == parent.identifier) 183 | XCTAssert(String(describing: decoded.child) == String(describing: parent.child)) 184 | XCTAssert(Set(decoded.children.map({ "\($0)" })) == Set(parent.children.map({ "\($0)" }))) 185 | 186 | try $0.save() 187 | }) 188 | } 189 | 190 | func testNested() { 191 | 192 | let parent = TestNested(identifier: "1", children: [ 193 | TestNested(identifier: "21"), 194 | TestNested(identifier: "22", children: [ 195 | TestNested(identifier: "31", children: [ 196 | TestNested(identifier: "41", children: []), 197 | ]), 198 | TestNested(identifier: "32", children: [ 199 | TestNested(identifier: "42") 200 | ]) 201 | ]) 202 | ]) 203 | 204 | XCTAssertNoThrow(try context { 205 | 206 | let encoder = CoreDataEncoder(managedObjectContext: $0) 207 | encoder.log = { print($0) } 208 | 209 | print("Will encode") 210 | 211 | let managedObject = try encoder.encode(parent) as! TestNestedManagedObject 212 | 213 | print("Did encode") 214 | 215 | print(managedObject) 216 | 217 | XCTAssert(managedObject.identifier == parent.identifier.rawValue) 218 | 219 | let decoder = CoreDataDecoder(managedObjectContext: $0) 220 | decoder.log = { print($0) } 221 | 222 | print("Will decode") 223 | 224 | let decoded = try decoder.decode(TestNested.self, with: "1" as TestNested.Identifier) 225 | 226 | print("Did decode") 227 | 228 | XCTAssert(decoded.identifier == parent.identifier) 229 | XCTAssert(String(describing: decoded) == String(describing: parent)) 230 | 231 | try $0.save() 232 | }) 233 | } 234 | 235 | func testSummit() { 236 | 237 | let jsonDecoder = JSONDecoder() 238 | 239 | for jsonIdentifier in SummitJSONIdentifiers { 240 | 241 | let filename = "Summit\(jsonIdentifier)" 242 | 243 | let testBundle = Bundle(for: type(of: self)) 244 | 245 | let resourcePath = testBundle.path(forResource: filename, ofType: "json", inDirectory: nil, forLocalization: nil)! 246 | 247 | let jsonData = try! Data(contentsOf: URL(fileURLWithPath: resourcePath)) 248 | 249 | XCTAssertNoThrow(try context { 250 | 251 | let summitJSON = try jsonDecoder.decode(SummitResponse.Summit.self, from: jsonData) 252 | 253 | let summit = Model.Summit(jsonDecodable: summitJSON) 254 | 255 | let encoder = CoreDataEncoder(managedObjectContext: $0) 256 | //encoder.log = { print($0) } 257 | 258 | print("Will encode") 259 | 260 | let managedObject = try encoder.encode(summit) as! SummitManagedObject 261 | 262 | print("Did encode") 263 | 264 | print(managedObject) 265 | 266 | XCTAssert(managedObject.identifier == summit.identifier.rawValue) 267 | 268 | let decoder = CoreDataDecoder(managedObjectContext: $0) 269 | //decoder.log = { print($0) } 270 | 271 | print("Will decode") 272 | 273 | let decoded = try decoder.decode(Model.Summit.self, with: summit.identifier) 274 | 275 | print("Did decode") 276 | 277 | XCTAssert(decoded.identifier == summit.identifier) 278 | 279 | try $0.save() 280 | }) 281 | } 282 | } 283 | } 284 | 285 | extension CoreDataCodableTests { 286 | 287 | var model: NSManagedObjectModel { 288 | 289 | return NSManagedObjectModel.mergedModel(from: Bundle.allBundles)! 290 | } 291 | 292 | func testSQLiteURL(_ function: String = #function) -> URL { 293 | 294 | let fileManager = FileManager.default 295 | 296 | // get cache folder 297 | 298 | let cacheURL = try! fileManager.url(for: .cachesDirectory, 299 | in: .userDomainMask, 300 | appropriateFor: nil, 301 | create: false) 302 | 303 | // get app folder 304 | let folderURL = cacheURL.appendingPathComponent("CoreDataCodableTests", isDirectory: true) 305 | 306 | // create folder if doesnt exist 307 | var folderExists: ObjCBool = false 308 | if fileManager.fileExists(atPath: folderURL.path, isDirectory: &folderExists) == false 309 | || folderExists.boolValue == false { 310 | 311 | try! fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true) 312 | } 313 | 314 | let fileURL = folderURL.appendingPathComponent(function + "." + UUID().uuidString + ".sqlite", isDirectory: false) 315 | 316 | print("Created SQLite file at \(fileURL)") 317 | 318 | return fileURL 319 | } 320 | 321 | func context (_ function: String = #function, _ block: (NSManagedObjectContext) throws -> Result) rethrows -> Result { 322 | 323 | let fileURL = testSQLiteURL(function) 324 | 325 | let managedObjectModel = self.model 326 | 327 | let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) 328 | 329 | let managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType) 330 | managedObjectContext.undoManager = nil 331 | managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator 332 | 333 | try! persistentStoreCoordinator.addPersistentStore(ofType: NSSQLiteStoreType, 334 | configurationName: nil, 335 | at: fileURL, 336 | options: nil) 337 | 338 | return try block(managedObjectContext) 339 | } 340 | } 341 | 342 | extension NSManagedObjectContext { 343 | 344 | func findOrCreate(identifier: NSObject, property: String, entityName: String) throws -> T { 345 | 346 | let fetchRequest = NSFetchRequest(entityName: entityName) 347 | fetchRequest.predicate = NSPredicate(format: "%K == %@", property, identifier) 348 | fetchRequest.fetchLimit = 1 349 | fetchRequest.includesSubentities = true 350 | fetchRequest.returnsObjectsAsFaults = false 351 | 352 | if let existing = try self.fetch(fetchRequest).first { 353 | 354 | return existing 355 | 356 | } else { 357 | 358 | // create a new entity 359 | let newManagedObject = NSEntityDescription.insertNewObject(forEntityName: entityName, into: self) as! T 360 | 361 | // set resource ID 362 | newManagedObject.setValue(identifier, forKey: property) 363 | 364 | return newManagedObject 365 | } 366 | } 367 | } 368 | 369 | protocol TestUnique { 370 | 371 | associatedtype Identifier: Codable, RawRepresentable 372 | 373 | var identifier: Identifier { get } 374 | } 375 | 376 | extension TestUnique where Self: CoreDataCodable, Self.Identifier: CoreDataIdentifier { 377 | 378 | static var identifierKey: String { return "identifier" } 379 | 380 | var coreDataIdentifier: CoreDataIdentifier { return self.identifier } 381 | } 382 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/SummitModel.xcdatamodeld/Model.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestAttributes.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/2/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataCodable 12 | 13 | struct TestAttributes: Codable, TestUnique { 14 | 15 | var identifier: Identifier 16 | 17 | var boolean: Bool 18 | 19 | var data: Data 20 | 21 | var date: Date 22 | 23 | var decimal: Decimal 24 | 25 | var double: Double 26 | 27 | var float: Float 28 | 29 | var int16: Int16 30 | 31 | var int32: Int32 32 | 33 | var int64: Int64 34 | 35 | var string: String 36 | 37 | var uri: URL 38 | 39 | var uuid: UUID 40 | 41 | //var urlValue: URL 42 | 43 | //var uuidValue: UUID 44 | 45 | var enumValue: TestEnum 46 | 47 | var optional: String? 48 | } 49 | 50 | extension TestAttributes { 51 | 52 | struct Identifier: Codable, RawRepresentable { 53 | 54 | var rawValue: String 55 | 56 | init(rawValue: String) { 57 | 58 | self.rawValue = rawValue 59 | } 60 | } 61 | } 62 | 63 | extension TestAttributes.Identifier: CoreDataIdentifier { 64 | 65 | init?(managedObject: NSManagedObject) { 66 | 67 | guard let managedObject = managedObject as? TestAttributesManagedObject 68 | else { return nil } 69 | 70 | self.rawValue = managedObject.identifier 71 | } 72 | 73 | func findOrCreate(in context: NSManagedObjectContext) throws -> NSManagedObject { 74 | 75 | return try TestAttributes.findOrCreate(self, in: context) 76 | } 77 | } 78 | 79 | extension TestAttributes.Identifier: Equatable { 80 | 81 | static func == (lhs: TestAttributes.Identifier, rhs: TestAttributes.Identifier) -> Bool { 82 | 83 | return lhs.rawValue == rhs.rawValue 84 | } 85 | } 86 | 87 | extension TestAttributes { 88 | 89 | enum TestEnum: String, Codable { 90 | 91 | case zero, one, two, three 92 | } 93 | } 94 | 95 | extension TestAttributes: CoreDataCodable { 96 | 97 | static var identifierKey: CodingKey { return CodingKeys.identifier } 98 | 99 | static func findOrCreate(_ identifier: TestAttributes.Identifier, in context: NSManagedObjectContext) throws -> TestAttributesManagedObject { 100 | 101 | let identifier = identifier.rawValue as NSString 102 | 103 | let identifierProperty = "identifier" 104 | 105 | let entityName = "TestAttributes" 106 | 107 | return try context.findOrCreate(identifier: identifier, property: identifierProperty, entityName: entityName) 108 | } 109 | } 110 | 111 | final class TestAttributesManagedObject: NSManagedObject { 112 | 113 | @NSManaged var identifier: String 114 | 115 | @NSManaged var boolean: Bool 116 | 117 | @NSManaged var data: Data 118 | 119 | @NSManaged var date: Date 120 | 121 | @NSManaged var decimal: NSDecimalNumber //Decimal 122 | 123 | @NSManaged var double: Double 124 | 125 | @NSManaged var float: Float 126 | 127 | @NSManaged var int16: Int16 128 | 129 | @NSManaged var int32: Int32 130 | 131 | @NSManaged var int64: Int64 132 | 133 | @NSManaged var string: String 134 | 135 | @NSManaged var uri: URL 136 | 137 | @NSManaged var uuid: UUID 138 | 139 | @NSManaged var urlValue: String 140 | 141 | @NSManaged var uuidValue: String 142 | 143 | @NSManaged var enumValue: String 144 | 145 | @NSManaged var optional: String? 146 | } 147 | 148 | extension TestAttributesManagedObject: DecodableManagedObject { 149 | 150 | var decodable: CoreDataCodable.Type { return TestAttributes.self } 151 | 152 | var decodedIdentifier: Any { return identifier } 153 | } 154 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestChild.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestChild.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/2/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataCodable 12 | 13 | struct TestChild: Codable, TestUnique { 14 | 15 | var identifier: Identifier 16 | 17 | var parent: TestParent.Identifier? 18 | 19 | var parentToOne: TestParent.Identifier? 20 | } 21 | 22 | extension TestChild { 23 | 24 | struct Identifier: Codable, RawRepresentable { 25 | 26 | var rawValue: String 27 | 28 | init(rawValue: String) { 29 | 30 | self.rawValue = rawValue 31 | } 32 | } 33 | } 34 | 35 | extension TestChild.Identifier: CoreDataIdentifier { 36 | 37 | init?(managedObject: NSManagedObject) { 38 | 39 | guard let managedObject = managedObject as? TestChildManagedObject 40 | else { return nil } 41 | 42 | self.rawValue = managedObject.identifier 43 | } 44 | 45 | func findOrCreate(in context: NSManagedObjectContext) throws -> NSManagedObject { 46 | 47 | return try TestChild.findOrCreate(self, in: context) 48 | } 49 | } 50 | 51 | extension TestChild.Identifier: Equatable { 52 | 53 | static func == (lhs: TestChild.Identifier, rhs: TestChild.Identifier) -> Bool { 54 | 55 | return lhs.rawValue == rhs.rawValue 56 | } 57 | } 58 | 59 | extension TestChild: CoreDataCodable { 60 | 61 | static var identifierKey: CodingKey { return CodingKeys.identifier } 62 | 63 | static func findOrCreate(_ identifier: TestChild.Identifier, in context: NSManagedObjectContext) throws -> TestChildManagedObject { 64 | 65 | let identifier = identifier.rawValue as NSString 66 | 67 | let identifierProperty = "identifier" 68 | 69 | let entityName = "TestChild" 70 | 71 | return try context.findOrCreate(identifier: identifier, property: identifierProperty, entityName: entityName) 72 | } 73 | } 74 | 75 | final class TestChildManagedObject: NSManagedObject { 76 | 77 | @NSManaged var identifier: String 78 | 79 | @NSManaged var parent: TestParentManagedObject? 80 | 81 | @NSManaged var parentToOne: TestParentManagedObject? 82 | } 83 | 84 | extension TestChildManagedObject: DecodableManagedObject { 85 | 86 | var decodable: CoreDataCodable.Type { return TestChild.self } 87 | 88 | var decodedIdentifier: Any { return identifier } 89 | } 90 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestFullfilledParent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestFullfilledParent.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/2/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataCodable 12 | 13 | struct TestFullfilledParent: Codable, TestUnique { 14 | 15 | typealias Identifier = TestParent.Identifier 16 | 17 | var identifier: Identifier 18 | 19 | var child: TestChild? 20 | 21 | var children: [TestChild] 22 | } 23 | 24 | extension TestFullfilledParent: CoreDataCodable { 25 | 26 | static var identifierKey: CodingKey { return CodingKeys.identifier } 27 | 28 | static func findOrCreate(_ identifier: TestFullfilledParent.Identifier, in context: NSManagedObjectContext) throws -> TestParentManagedObject { 29 | 30 | let identifier = identifier.rawValue as NSNumber 31 | 32 | let identifierProperty = "identifier" 33 | 34 | let entityName = "TestParent" 35 | 36 | return try context.findOrCreate(identifier: identifier, property: identifierProperty, entityName: entityName) 37 | } 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestModel.xcdatamodeld/Model.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestNested.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestNested.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/5/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataCodable 12 | 13 | struct TestNested: Codable, TestUnique { 14 | 15 | var identifier: TestNested.Identifier 16 | 17 | var children: [TestNested] 18 | 19 | var value: String = "Test value" 20 | 21 | var rawValue: Device = .iPhone 22 | 23 | init(identifier: TestNested.Identifier, 24 | children: [TestNested] = []) { 25 | 26 | self.identifier = identifier 27 | self.children = children 28 | } 29 | } 30 | 31 | extension TestNested { 32 | 33 | struct Identifier: Codable, RawRepresentable { 34 | 35 | var rawValue: String 36 | 37 | init(rawValue: String) { 38 | 39 | self.rawValue = rawValue 40 | } 41 | } 42 | 43 | enum Device: String, Codable { 44 | 45 | case iPhone, iPad, Mac 46 | } 47 | } 48 | 49 | extension TestNested.Identifier: ExpressibleByStringLiteral { 50 | 51 | public init(stringLiteral value: String) { 52 | 53 | self.rawValue = value 54 | } 55 | } 56 | 57 | extension TestNested.Identifier: CoreDataIdentifier { 58 | 59 | init?(managedObject: NSManagedObject) { 60 | 61 | guard let managedObject = managedObject as? TestNestedManagedObject 62 | else { return nil } 63 | 64 | self.rawValue = managedObject.identifier 65 | } 66 | 67 | func findOrCreate(in context: NSManagedObjectContext) throws -> NSManagedObject { 68 | 69 | return try TestNested.findOrCreate(self, in: context) 70 | } 71 | } 72 | 73 | extension TestNested.Identifier: Equatable { 74 | 75 | static func == (lhs: TestNested.Identifier, rhs: TestNested.Identifier) -> Bool { 76 | 77 | return lhs.rawValue == rhs.rawValue 78 | } 79 | } 80 | 81 | extension TestNested: CoreDataCodable { 82 | 83 | static var identifierKey: CodingKey { return CodingKeys.identifier } 84 | } 85 | 86 | extension TestNested { 87 | 88 | static func findOrCreate(_ identifier: TestNested.Identifier, in context: NSManagedObjectContext) throws -> TestNestedManagedObject { 89 | 90 | let identifier = identifier.rawValue as NSString 91 | 92 | let identifierProperty = "identifier" 93 | 94 | let entityName = "TestNested" 95 | 96 | return try context.findOrCreate(identifier: identifier, property: identifierProperty, entityName: entityName) 97 | } 98 | } 99 | 100 | final class TestNestedManagedObject: NSManagedObject { 101 | 102 | @NSManaged var identifier: String 103 | 104 | @NSManaged var parent: TestNestedManagedObject? 105 | 106 | @NSManaged var children: Set 107 | } 108 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestParent.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestParent.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/2/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataCodable 12 | 13 | struct TestParent: Codable, TestUnique { 14 | 15 | var identifier: Identifier 16 | 17 | var child: TestChild.Identifier? 18 | 19 | var children: [TestChild.Identifier] 20 | } 21 | 22 | extension TestParent { 23 | 24 | struct Identifier: Codable, RawRepresentable { 25 | 26 | var rawValue: Int64 27 | 28 | init(rawValue: Int64) { 29 | 30 | self.rawValue = rawValue 31 | } 32 | } 33 | } 34 | 35 | extension TestParent.Identifier: CoreDataIdentifier { 36 | 37 | init?(managedObject: NSManagedObject) { 38 | 39 | guard let managedObject = managedObject as? TestParentManagedObject 40 | else { return nil } 41 | 42 | self.rawValue = managedObject.identifier 43 | } 44 | 45 | func findOrCreate(in context: NSManagedObjectContext) throws -> NSManagedObject { 46 | 47 | return try TestParent.findOrCreate(self, in: context) 48 | } 49 | } 50 | 51 | extension TestParent.Identifier: Equatable { 52 | 53 | static func == (lhs: TestParent.Identifier, rhs: TestParent.Identifier) -> Bool { 54 | 55 | return lhs.rawValue == rhs.rawValue 56 | } 57 | } 58 | 59 | extension TestParent: CoreDataCodable { 60 | 61 | static var identifierKey: CodingKey { return CodingKeys.identifier } 62 | 63 | static func findOrCreate(_ identifier: TestParent.Identifier, in context: NSManagedObjectContext) throws -> TestParentManagedObject { 64 | 65 | let identifier = identifier.rawValue as NSNumber 66 | 67 | let identifierProperty = "identifier" 68 | 69 | let entityName = "TestParent" 70 | 71 | return try context.findOrCreate(identifier: identifier, property: identifierProperty, entityName: entityName) 72 | } 73 | } 74 | 75 | final class TestParentManagedObject: NSManagedObject { 76 | 77 | @NSManaged var identifier: Int64 78 | 79 | @NSManaged var child: TestChildManagedObject? 80 | 81 | @NSManaged var children: Set 82 | } 83 | 84 | extension TestParentManagedObject: DecodableManagedObject { 85 | 86 | var decodable: CoreDataCodable.Type { return TestParent.self } 87 | 88 | var decodedIdentifier: Any { return identifier } 89 | } 90 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestSummitJSON.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSummit.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/6/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataCodable 12 | 13 | // MARK: - JSON 14 | 15 | /// [OpenStack Summit](https://github.com/OpenStack-mobile/summit-app-ios) 16 | public struct SummitResponse: Codable, RawRepresentable { 17 | 18 | public var rawValue: Summit 19 | 20 | public init(rawValue: Summit) { 21 | 22 | self.rawValue = rawValue 23 | } 24 | 25 | public struct Summit: Codable { 26 | 27 | public typealias Identifier = Int64 28 | 29 | private enum CodingKeys: String, CodingKey { 30 | 31 | case identifier = "id" 32 | case name 33 | case timeZone = "time_zone" 34 | case datesLabel = "dates_label" 35 | case start = "start_date" 36 | case end = "end_date" 37 | case defaultStart = "schedule_start_date" 38 | case active 39 | case webpage = "page_url" 40 | case sponsors 41 | case speakers 42 | case startShowingVenues = "start_showing_venues_date" 43 | case ticketTypes = "ticket_types" 44 | case locations 45 | case tracks 46 | case trackGroups = "track_groups" 47 | case eventTypes = "event_types" 48 | case schedule 49 | case wirelessNetworks = "wifi_connections" 50 | } 51 | 52 | public let identifier: Identifier 53 | 54 | public var name: String 55 | 56 | public var timeZone: TimeZone 57 | 58 | public var datesLabel: String? 59 | 60 | public var start: Date 61 | 62 | public var end: Date 63 | 64 | /// Default start date for the Summit. 65 | public var defaultStart: Date? 66 | 67 | public var active: Bool 68 | 69 | public var webpage: URL 70 | 71 | public var sponsors: [Company] 72 | 73 | public var speakers: [Speaker] 74 | 75 | public var startShowingVenues: Date? 76 | 77 | public var ticketTypes: [TicketType] 78 | 79 | // Venue and Venue Rooms 80 | public var locations: [Location] 81 | 82 | public var tracks: [Track] 83 | 84 | public var trackGroups: [TrackGroup] 85 | 86 | public var eventTypes: [EventType] 87 | 88 | public var schedule: [Event] 89 | 90 | public var wirelessNetworks: [WirelessNetwork]? 91 | } 92 | 93 | public struct TimeZone: Codable { 94 | 95 | private enum CodingKeys: String, CodingKey { 96 | 97 | case name 98 | case countryCode = "country_code" 99 | case latitude 100 | case longitude 101 | case comments 102 | case offset 103 | } 104 | 105 | public var name: String 106 | 107 | public var countryCode: String 108 | 109 | public var latitude: Double 110 | 111 | public var longitude: Double 112 | 113 | public var comments: String 114 | 115 | public var offset: Int 116 | } 117 | 118 | public struct WirelessNetwork: Codable { 119 | 120 | public typealias Identifier = Int64 121 | 122 | private enum CodingKeys: String, CodingKey { 123 | 124 | case identifier = "id" 125 | case name = "ssid" 126 | case password 127 | case descriptionText = "description" 128 | case summit = "summit_id" 129 | } 130 | 131 | public let identifier: Identifier 132 | 133 | public let name: String 134 | 135 | public let password: String 136 | 137 | public let descriptionText: String? 138 | 139 | public let summit: Summit.Identifier 140 | } 141 | 142 | public struct Company: Codable { 143 | 144 | public typealias Identifier = Int64 145 | 146 | private enum CodingKeys: String, CodingKey { 147 | 148 | case identifier = "id" 149 | case name 150 | } 151 | 152 | public let identifier: Identifier 153 | 154 | public var name: String 155 | } 156 | 157 | public struct Speaker: Codable { 158 | 159 | public typealias Identifier = Int64 160 | 161 | public enum CodingKeys: String, CodingKey { 162 | 163 | case identifier = "id" 164 | case firstName = "first_name" 165 | case lastName = "last_name" 166 | case title 167 | case picture = "pic" 168 | case twitter 169 | case irc 170 | case biography = "bio" 171 | case affiliations 172 | } 173 | 174 | public let identifier: Identifier 175 | 176 | public var firstName: String 177 | 178 | public var lastName: String 179 | 180 | public var title: String? 181 | 182 | public var picture: URL 183 | 184 | public var twitter: String? 185 | 186 | public var irc: String? 187 | 188 | public var biography: String? 189 | 190 | public var affiliations: [Affiliation] 191 | } 192 | 193 | public struct Affiliation: Codable { 194 | 195 | public typealias Identifier = Int64 196 | 197 | private enum CodingKeys: String, CodingKey { 198 | 199 | case identifier = "id" 200 | case member = "owner_id" 201 | case start = "start_date" 202 | case end = "end_date" 203 | case isCurrent = "is_current" 204 | case organization 205 | } 206 | 207 | public let identifier: Identifier 208 | 209 | public var member: Member.Identifier 210 | 211 | public var start: Date? 212 | 213 | public var end: Date? 214 | 215 | public var isCurrent: Bool 216 | 217 | public var organization: AffiliationOrganization 218 | } 219 | 220 | public struct AffiliationOrganization: Codable { 221 | 222 | public typealias Identifier = Int64 223 | 224 | private enum CodingKeys: String, CodingKey { 225 | 226 | case identifier = "id" 227 | case name 228 | } 229 | 230 | public let identifier: Identifier 231 | 232 | public var name: String 233 | } 234 | 235 | public struct TicketType: Codable { 236 | 237 | public typealias Identifier = Int64 238 | 239 | private enum CodingKeys: String, CodingKey { 240 | 241 | case identifier = "id" 242 | case name 243 | case descriptionText = "description" 244 | } 245 | 246 | public let identifier: Identifier 247 | 248 | public var name: String 249 | 250 | public var descriptionText: String? 251 | } 252 | 253 | public struct Image: Codable { 254 | 255 | public typealias Identifier = Int64 256 | 257 | private enum CodingKeys: String, CodingKey { 258 | 259 | case identifier = "id" 260 | case url = "image_url" 261 | } 262 | 263 | public let identifier: Identifier 264 | 265 | public var url: URL 266 | } 267 | 268 | public enum Location: Codable { 269 | 270 | public typealias Identifier = Int64 271 | 272 | public enum ClassName: String, Codable { 273 | 274 | case SummitVenue, SummitExternalLocation, SummitHotel, SummitAirport, SummitVenueRoom 275 | } 276 | 277 | private enum CodingKeys: String, CodingKey { 278 | 279 | case type = "class_name" 280 | } 281 | 282 | case venue(Venue) 283 | case room(VenueRoom) 284 | 285 | public init(from decoder: Decoder) throws { 286 | 287 | let container = try decoder.container(keyedBy: CodingKeys.self) 288 | 289 | let type = try container.decode(ClassName.self, forKey: .type) 290 | 291 | switch type { 292 | 293 | case .SummitVenue, .SummitExternalLocation, .SummitHotel, .SummitAirport: 294 | 295 | let venue = try Venue(from: decoder) 296 | 297 | self = .venue(venue) 298 | 299 | case .SummitVenueRoom: 300 | 301 | let room = try VenueRoom(from: decoder) 302 | 303 | self = .room(room) 304 | } 305 | } 306 | 307 | public func encode(to encoder: Encoder) throws { 308 | 309 | switch self { 310 | case let .venue(venue): try venue.encode(to: encoder) 311 | case let .room(room): try room.encode(to: encoder) 312 | } 313 | } 314 | } 315 | 316 | public struct Venue: Codable { 317 | 318 | public typealias Identifier = Int64 319 | 320 | public typealias LocationType = Model.Venue.LocationType 321 | 322 | public typealias ClassName = Model.Venue.ClassName 323 | 324 | private enum CodingKeys: String, CodingKey { 325 | 326 | case identifier = "id" 327 | case name 328 | case latitude = "lat" 329 | case longitude = "lng" 330 | case address = "address_1" 331 | case city 332 | case state 333 | case zipCode = "zip_code" 334 | case country 335 | case maps 336 | case images 337 | case floors 338 | case locationType = "location_type" 339 | case descriptionText = "description" 340 | case type = "class_name" 341 | } 342 | 343 | public let identifier: Identifier 344 | 345 | public let type: ClassName 346 | 347 | public var name: String 348 | 349 | public var descriptionText: String? 350 | 351 | public var locationType: LocationType 352 | 353 | public var country: String 354 | 355 | public var address: String? 356 | 357 | public var city: String? 358 | 359 | public var zipCode: String? 360 | 361 | public var state: String? 362 | 363 | public var latitude: String? 364 | 365 | public var longitude: String? 366 | 367 | public var maps: [Image] 368 | 369 | public var images: [Image] 370 | 371 | public var floors: [VenueFloor]? 372 | } 373 | 374 | public struct VenueFloor: Codable { 375 | 376 | public typealias Identifier = Int64 377 | 378 | private enum CodingKeys: String, CodingKey { 379 | 380 | case identifier = "id" 381 | case name 382 | case descriptionText = "description" 383 | case number 384 | case image 385 | case venue = "venue_id" 386 | case rooms 387 | } 388 | 389 | public let identifier: Identifier 390 | 391 | public var name: String 392 | 393 | public var descriptionText: String? 394 | 395 | public var number: Int16 396 | 397 | public var image: URL? 398 | 399 | public var venue: Venue.Identifier 400 | 401 | public var rooms: [VenueRoom.Identifier]? 402 | } 403 | 404 | public struct VenueRoom: Codable { 405 | 406 | public typealias Identifier = Int64 407 | 408 | public enum ClassName: String, Codable { 409 | 410 | case SummitVenueRoom 411 | } 412 | 413 | private enum CodingKeys: String, CodingKey { 414 | 415 | case identifier = "id" 416 | case name 417 | case descriptionText = "description" 418 | case type = "class_name" 419 | case capacity 420 | case venue = "venue_id" 421 | case floor = "floor_id" 422 | } 423 | 424 | public let identifier: Identifier 425 | 426 | public let type: ClassName 427 | 428 | public var name: String 429 | 430 | public var descriptionText: String? 431 | 432 | public var capacity: Int? 433 | 434 | public var venue: Venue.Identifier 435 | 436 | public var floor: VenueFloor.Identifier? 437 | } 438 | 439 | public struct Track: Codable { 440 | 441 | public typealias Identifier = Int64 442 | 443 | private enum CodingKeys: String, CodingKey { 444 | 445 | case identifier = "id" 446 | case name 447 | case groups = "track_groups" 448 | } 449 | 450 | public let identifier: Identifier 451 | 452 | public var name: String 453 | 454 | public var groups: [TrackGroup.Identifier] 455 | } 456 | 457 | public struct TrackGroup: Codable { 458 | 459 | public typealias Identifier = Int64 460 | 461 | private enum CodingKeys: String, CodingKey { 462 | 463 | case identifier = "id" 464 | case name 465 | case descriptionText = "description" 466 | case color 467 | case tracks 468 | } 469 | 470 | public let identifier: Identifier 471 | 472 | public var name: String 473 | 474 | public var descriptionText: String? 475 | 476 | public var color: String 477 | 478 | public var tracks: [Track.Identifier] 479 | } 480 | 481 | public struct EventType: Codable { 482 | 483 | public typealias Identifier = Int64 484 | 485 | private enum CodingKeys: String, CodingKey { 486 | 487 | case identifier = "id" 488 | case name 489 | case color 490 | case blackOutTimes = "black_out_times" 491 | } 492 | 493 | public let identifier: Identifier 494 | 495 | public var name: String 496 | 497 | public var color: String 498 | 499 | public var blackOutTimes: Bool 500 | } 501 | 502 | public struct Event: Codable { 503 | 504 | public typealias Identifier = Int64 505 | 506 | private enum CodingKeys: String, CodingKey { 507 | 508 | case identifier = "id" 509 | case summit = "summit_id" 510 | case name = "title" 511 | case descriptionText = "description" 512 | case socialDescription = "social_description" 513 | case start = "start_date" 514 | case end = "end_date" 515 | case allowFeedback = "allow_feedback" 516 | case averageFeedback = "avg_feedback_rate" 517 | case type = "type_id" 518 | case sponsors 519 | case location = "location_id" 520 | case tags 521 | case track = "track_id" 522 | case videos 523 | case rsvp = "rsvp_link" 524 | case externalRSVP = "rsvp_external" 525 | case willRecord = "to_record" 526 | case attachment 527 | case slides 528 | case links 529 | 530 | // presentation 531 | case level 532 | case moderator = "moderator_speaker_id" 533 | case speakers 534 | } 535 | 536 | public let identifier: Identifier 537 | 538 | public var name: String 539 | 540 | public var summit: Summit.Identifier 541 | 542 | public var descriptionText: String? 543 | 544 | public var socialDescription: String? 545 | 546 | public var start: Date 547 | 548 | public var end: Date 549 | 550 | public var track: Track.Identifier? 551 | 552 | public var allowFeedback: Bool 553 | 554 | public var averageFeedback: Double 555 | 556 | public var type: EventType.Identifier 557 | 558 | public var rsvp: String? 559 | 560 | public var externalRSVP: Bool? 561 | 562 | public var willRecord: Bool? 563 | 564 | public var attachment: URL? 565 | 566 | public var sponsors: [Company.Identifier] 567 | 568 | public var tags: [Tag] 569 | 570 | public var location: Location.Identifier? 571 | 572 | // Not really a different entity 573 | //public var presentation: Presentation 574 | 575 | public var videos: [Video]? 576 | 577 | public var slides: [Slide]? 578 | 579 | public var links: [Link]? 580 | 581 | // Never comes from this JSON 582 | //public var groups: [Group] 583 | 584 | // Presentation values 585 | 586 | public var level: Model.Level? 587 | 588 | public var moderator: Speaker.Identifier? 589 | 590 | public var speakers: [Speaker.Identifier]? 591 | } 592 | 593 | public struct Link: Codable { 594 | 595 | public typealias Identifier = Int64 596 | 597 | private enum CodingKeys: String, CodingKey { 598 | 599 | case identifier = "id" 600 | case name 601 | case descriptionText = "description" 602 | case displayOnSite = "display_on_site" 603 | case featured 604 | case order 605 | case event = "presentation_id" 606 | case link 607 | } 608 | 609 | public let identifier: Identifier 610 | 611 | public var name: String? 612 | 613 | public var descriptionText: String? 614 | 615 | public var displayOnSite: Bool 616 | 617 | public var featured: Bool 618 | 619 | public var order: Int64 620 | 621 | public var link: String // not always valid URL 622 | 623 | public var event: Event.Identifier 624 | } 625 | 626 | public struct Tag: Codable { 627 | 628 | public typealias Identifier = Int64 629 | 630 | private enum CodingKeys: String, CodingKey { 631 | 632 | case identifier = "id" 633 | case name = "tag" 634 | } 635 | 636 | public let identifier: Identifier 637 | 638 | public var name: String 639 | } 640 | 641 | public struct Video: Codable { 642 | 643 | public typealias Identifier = Int64 644 | 645 | private enum CodingKeys: String, CodingKey { 646 | 647 | case identifier = "id" 648 | case name 649 | case descriptionText = "description" 650 | case displayOnSite = "display_on_site" 651 | case featured 652 | case event = "presentation_id" 653 | case youtube = "youtube_id" 654 | case dataUploaded = "data_uploaded" 655 | case highlighted 656 | case views 657 | case order 658 | } 659 | 660 | public let identifier: Identifier 661 | 662 | public var name: String 663 | 664 | public var descriptionText: String? 665 | 666 | public var displayOnSite: Bool 667 | 668 | public var featured: Bool 669 | 670 | public var highlighted: Bool 671 | 672 | public var youtube: String 673 | 674 | public var dataUploaded: Date 675 | 676 | public var order: Int64 677 | 678 | public var views: Int64 679 | 680 | public var event: Event.Identifier 681 | } 682 | 683 | public struct Slide: Codable { 684 | 685 | public typealias Identifier = Int64 686 | 687 | private enum CodingKeys: String, CodingKey { 688 | 689 | case identifier = "id" 690 | case name 691 | case descriptionText = "description" 692 | case displayOnSite = "display_on_site" 693 | case featured 694 | case order 695 | case event = "presentation_id" 696 | case link 697 | } 698 | 699 | public let identifier: Identifier 700 | 701 | public var name: String? 702 | 703 | public var descriptionText: String? 704 | 705 | public var displayOnSite: Bool 706 | 707 | public var featured: Bool 708 | 709 | public var order: Int64 710 | 711 | public var link: URL 712 | 713 | public var event: Event.Identifier 714 | } 715 | 716 | public struct Member: Codable { 717 | 718 | public typealias Identifier = Int64 719 | 720 | private enum CodingKeys: String, CodingKey { 721 | 722 | case identifier = "id" 723 | case firstName = "first_name" 724 | case lastName = "last_name" 725 | case gender 726 | case biography = "bio" 727 | case irc 728 | case twitter 729 | case linkedIn = "linked_in" 730 | case picture = "pic" 731 | case speakerRole = "speaker" 732 | case schedule = "schedule_summit_events" 733 | case groupEvents = "groups_events" 734 | case groups 735 | case attendeeRole = "attendee" 736 | case feedback 737 | case favoriteEvents = "favorite_summit_events" 738 | case affiliations 739 | } 740 | 741 | public let identifier: Identifier 742 | 743 | public let firstName: String 744 | 745 | public let lastName: String 746 | 747 | public let gender: String? 748 | 749 | public let picture: URL 750 | 751 | public let twitter: String? 752 | 753 | public let linkedIn: String? 754 | 755 | public let irc: String? 756 | 757 | public let biography: String? 758 | 759 | public let speakerRole: Speaker? 760 | 761 | public let attendeeRole: Attendee? 762 | 763 | public var schedule: [Event.Identifier] 764 | 765 | public let groupEvents: [Event.Identifier] 766 | 767 | public let favoriteEvents: [Event.Identifier] 768 | 769 | public let groups: [Group] 770 | 771 | public let feedback: [Feedback.Identifier] 772 | 773 | public let affiliations: [Affiliation] 774 | } 775 | 776 | public struct Attendee: Codable { 777 | 778 | public typealias Identifier = Int64 779 | 780 | public let identifier: Identifier 781 | 782 | public var member: Member.Identifier 783 | 784 | public var tickets: [TicketType.Identifier] 785 | } 786 | 787 | public struct Group: Codable { 788 | 789 | public typealias Identifier = Int64 790 | 791 | public let identifier: Identifier 792 | 793 | public var title: String 794 | 795 | public var descriptionText: String? 796 | 797 | public var code: String 798 | } 799 | 800 | public struct Feedback: Codable { 801 | 802 | public typealias Identifier = Int64 803 | 804 | public let identifier: Identifier 805 | 806 | public let rate: Int 807 | 808 | public let review: String 809 | 810 | public let date: Date 811 | 812 | public let event: Event.Identifier 813 | 814 | public let member: Member 815 | } 816 | } 817 | 818 | // MARK: - Model Conversion 819 | 820 | public protocol SummitJSONDecodable { 821 | 822 | associatedtype JSONDecodable: Swift.Decodable 823 | 824 | init(jsonDecodable: JSONDecodable) 825 | } 826 | 827 | public extension Collection where Self.Iterator.Element: SummitJSONDecodable { 828 | 829 | static func from(_ jsonDecodables: [Self.Iterator.Element.JSONDecodable]?) -> [Self.Iterator.Element] { 830 | 831 | return jsonDecodables?.map { Self.Iterator.Element.init(jsonDecodable: $0) } ?? [] 832 | } 833 | } 834 | 835 | private extension Collection where Self.Iterator.Element: RawRepresentable { 836 | 837 | static func from(_ rawValues: [Self.Iterator.Element.RawValue]?) -> [Self.Iterator.Element] { 838 | 839 | return rawValues?.compactMap { Self.Iterator.Element.init(rawValue: $0) } ?? [] 840 | } 841 | } 842 | 843 | private extension Int64 { 844 | 845 | func toIdentifier() -> T? where T: CoreDataIdentifier, T: RawRepresentable, T.RawValue == Int64 { 846 | 847 | return T.init(rawValue: self) 848 | } 849 | } 850 | 851 | private extension Collection where Self.Iterator.Element == Int64 { 852 | 853 | func toIdentifiers() -> [T] where T: CoreDataIdentifier, T: RawRepresentable, T.RawValue == Int64 { 854 | 855 | return compactMap { $0.toIdentifier() } 856 | } 857 | } 858 | 859 | private extension Optional where Wrapped: Collection, Wrapped.Iterator.Element == Int64 { 860 | 861 | func toIdentifiers() -> [T] where T: CoreDataIdentifier, T: RawRepresentable, T.RawValue == Int64 { 862 | 863 | return self?.toIdentifiers() ?? [] 864 | } 865 | } 866 | 867 | extension Model.Summit: SummitJSONDecodable { 868 | 869 | public init(jsonDecodable json: SummitResponse.Summit) { 870 | 871 | self.identifier = .init(json.identifier) 872 | self.name = json.name 873 | self.timeZone = json.timeZone.name 874 | self.datesLabel = json.datesLabel 875 | self.start = json.start 876 | self.end = json.end 877 | self.defaultStart = json.defaultStart 878 | self.active = json.active 879 | self.webpage = json.webpage 880 | self.startShowingVenues = json.startShowingVenues 881 | self.sponsors = .from(json.sponsors) 882 | self.speakers = .from(json.speakers) 883 | self.ticketTypes = .from(json.ticketTypes) 884 | self.locations = .from(json.locations) 885 | self.tracks = .from(json.tracks) 886 | self.trackGroups = .from(json.trackGroups) 887 | self.eventTypes = .from(json.eventTypes) 888 | self.schedule = .from(json.schedule) 889 | self.wirelessNetworks = .from(json.wirelessNetworks) 890 | } 891 | } 892 | 893 | extension Model.Company: SummitJSONDecodable { 894 | 895 | public init(jsonDecodable json: SummitResponse.Company) { 896 | 897 | self.identifier = .init(json.identifier) 898 | self.name = json.name 899 | } 900 | } 901 | 902 | extension Model.Speaker: SummitJSONDecodable { 903 | 904 | public init(jsonDecodable json: SummitResponse.Speaker) { 905 | 906 | self.identifier = .init(json.identifier) 907 | self.addressBookSectionName = AddressBook.section(for: json.firstName) 908 | self.firstName = json.firstName 909 | self.lastName = json.lastName 910 | self.title = json.title 911 | self.picture = json.picture 912 | self.twitter = json.twitter 913 | self.irc = json.irc 914 | self.biography = json.biography 915 | self.affiliations = .from(json.affiliations) 916 | } 917 | } 918 | 919 | extension Model.WirelessNetwork: SummitJSONDecodable { 920 | 921 | public init(jsonDecodable json: SummitResponse.WirelessNetwork) { 922 | 923 | self.identifier = .init(json.identifier) 924 | self.name = json.name 925 | self.password = json.password 926 | self.descriptionText = json.descriptionText 927 | self.summit = .init(json.summit) 928 | } 929 | } 930 | 931 | extension Model.Affiliation: SummitJSONDecodable { 932 | 933 | public init(jsonDecodable json: SummitResponse.Affiliation) { 934 | 935 | self.identifier = .init(json.identifier) 936 | self.member = .init(json.member) 937 | self.start = json.start 938 | self.end = json.end 939 | self.isCurrent = json.isCurrent 940 | self.organization = .init(jsonDecodable: json.organization) 941 | } 942 | } 943 | 944 | extension Model.AffiliationOrganization: SummitJSONDecodable { 945 | 946 | public init(jsonDecodable json: SummitResponse.AffiliationOrganization) { 947 | 948 | self.identifier = .init(json.identifier) 949 | self.name = json.name 950 | } 951 | } 952 | 953 | extension Model.TicketType: SummitJSONDecodable { 954 | 955 | public init(jsonDecodable json: SummitResponse.TicketType) { 956 | 957 | self.identifier = .init(json.identifier) 958 | self.name = json.name 959 | self.descriptionText = json.descriptionText 960 | } 961 | } 962 | 963 | extension Model.Image: SummitJSONDecodable { 964 | 965 | public init(jsonDecodable json: SummitResponse.Image) { 966 | 967 | self.identifier = .init(json.identifier) 968 | self.url = json.url 969 | } 970 | } 971 | 972 | extension Model.Location: SummitJSONDecodable { 973 | 974 | public init(jsonDecodable json: SummitResponse.Location) { 975 | 976 | switch json { 977 | case let .venue(jsonLocation): 978 | self = .venue(.init(jsonDecodable: jsonLocation)) 979 | case let .room(jsonLocation): 980 | self = .room(.init(jsonDecodable: jsonLocation)) 981 | } 982 | } 983 | } 984 | 985 | extension Model.Venue: SummitJSONDecodable { 986 | 987 | public init(jsonDecodable json: SummitResponse.Venue) { 988 | 989 | self.identifier = .init(json.identifier) 990 | self.venueType = json.type 991 | self.name = json.name 992 | self.descriptionText = json.descriptionText 993 | self.locationType = json.locationType 994 | self.country = json.country 995 | self.address = json.address 996 | self.city = json.city 997 | self.zipCode = json.zipCode 998 | self.state = json.state 999 | self.longitude = json.longitude 1000 | self.latitude = json.latitude 1001 | self.maps = .from(json.maps) 1002 | self.images = .from(json.images) 1003 | self.floors = .from(json.floors) 1004 | } 1005 | } 1006 | 1007 | extension Model.VenueRoom: SummitJSONDecodable { 1008 | 1009 | public init(jsonDecodable json: SummitResponse.VenueRoom) { 1010 | 1011 | self.identifier = .init(json.identifier) 1012 | self.name = json.name 1013 | self.descriptionText = json.descriptionText 1014 | self.capacity = json.capacity 1015 | self.venue = .init(json.venue) 1016 | self.floor = json.floor?.toIdentifier() 1017 | } 1018 | } 1019 | 1020 | extension Model.VenueFloor: SummitJSONDecodable { 1021 | 1022 | public init(jsonDecodable json: SummitResponse.VenueFloor) { 1023 | 1024 | self.identifier = .init(json.identifier) 1025 | self.name = json.name 1026 | self.descriptionText = json.descriptionText 1027 | self.number = json.number 1028 | self.image = json.image 1029 | self.venue = .init(json.venue) 1030 | self.rooms = json.rooms.toIdentifiers() 1031 | } 1032 | } 1033 | 1034 | extension Model.Track: SummitJSONDecodable { 1035 | 1036 | public init(jsonDecodable json: SummitResponse.Track) { 1037 | 1038 | self.identifier = .init(json.identifier) 1039 | self.name = json.name 1040 | self.groups = json.groups.toIdentifiers() 1041 | } 1042 | } 1043 | 1044 | extension Model.TrackGroup: SummitJSONDecodable { 1045 | 1046 | public init(jsonDecodable json: SummitResponse.TrackGroup) { 1047 | 1048 | self.identifier = .init(json.identifier) 1049 | self.name = json.name 1050 | self.descriptionText = json.descriptionText 1051 | self.color = json.color 1052 | self.tracks = json.tracks.toIdentifiers() 1053 | } 1054 | } 1055 | 1056 | extension Model.Event: SummitJSONDecodable { 1057 | 1058 | public init(jsonDecodable json: SummitResponse.Event) { 1059 | 1060 | self.identifier = .init(json.identifier) 1061 | self.name = json.name 1062 | self.summit = .init(json.summit) 1063 | self.descriptionText = json.descriptionText 1064 | self.socialDescription = json.socialDescription 1065 | self.start = json.start 1066 | self.end = json.end 1067 | self.track = json.track?.toIdentifier() 1068 | self.allowFeedback = json.allowFeedback 1069 | self.averageFeedback = json.averageFeedback 1070 | self.eventType = .init(json.type) 1071 | self.rsvp = json.rsvp 1072 | self.externalRSVP = json.externalRSVP ?? false 1073 | self.willRecord = json.willRecord ?? false 1074 | self.attachment = json.attachment 1075 | self.sponsors = json.sponsors.toIdentifiers() 1076 | self.tags = .from(json.tags) 1077 | self.location = json.location?.toIdentifier() 1078 | self.videos = .from(json.videos) 1079 | self.slides = .from(json.slides) 1080 | self.links = .from(json.links) 1081 | self.presentation = .init(jsonDecodable: json) // decodes from self 1082 | } 1083 | } 1084 | 1085 | extension Model.EventType: SummitJSONDecodable { 1086 | 1087 | public init(jsonDecodable json: SummitResponse.EventType) { 1088 | 1089 | self.identifier = .init(json.identifier) 1090 | self.name = json.name 1091 | self.color = json.color 1092 | self.blackOutTimes = json.blackOutTimes 1093 | } 1094 | } 1095 | 1096 | extension Model.Presentation: SummitJSONDecodable { 1097 | 1098 | public init(jsonDecodable json: SummitResponse.Event) { 1099 | 1100 | self.identifier = .init(json.identifier) 1101 | self.level = json.level 1102 | self.moderator = json.moderator?.toIdentifier() 1103 | self.speakers = json.speakers.toIdentifiers() 1104 | } 1105 | } 1106 | 1107 | extension Model.Link: SummitJSONDecodable { 1108 | 1109 | public init(jsonDecodable json: SummitResponse.Link) { 1110 | 1111 | self.identifier = .init(json.identifier) 1112 | self.name = json.name 1113 | self.descriptionText = json.descriptionText 1114 | self.displayOnSite = json.displayOnSite 1115 | self.featured = json.featured 1116 | self.order = json.order 1117 | self.link = json.link 1118 | self.event = .init(json.event) 1119 | } 1120 | } 1121 | 1122 | extension Model.Tag: SummitJSONDecodable { 1123 | 1124 | public init(jsonDecodable json: SummitResponse.Tag) { 1125 | 1126 | self.identifier = .init(json.identifier) 1127 | self.name = json.name 1128 | } 1129 | } 1130 | 1131 | extension Model.Video: SummitJSONDecodable { 1132 | 1133 | public init(jsonDecodable json: SummitResponse.Video) { 1134 | 1135 | self.identifier = .init(json.identifier) 1136 | self.name = json.name 1137 | self.descriptionText = json.descriptionText 1138 | self.displayOnSite = json.displayOnSite 1139 | self.featured = json.featured 1140 | self.highlighted = json.highlighted 1141 | self.youtube = json.youtube 1142 | self.order = json.order 1143 | self.dataUploaded = json.dataUploaded 1144 | self.views = json.views 1145 | self.event = .init(json.event) 1146 | } 1147 | } 1148 | 1149 | extension Model.Slide: SummitJSONDecodable { 1150 | 1151 | public init(jsonDecodable json: SummitResponse.Slide) { 1152 | 1153 | self.identifier = .init(json.identifier) 1154 | self.name = json.name ?? "" 1155 | self.descriptionText = json.descriptionText 1156 | self.displayOnSite = json.displayOnSite 1157 | self.featured = json.featured 1158 | self.order = json.order 1159 | self.event = .init(json.event) 1160 | self.link = json.link 1161 | } 1162 | } 1163 | 1164 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestSummitManagedObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSummitManagedObject.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/6/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | 10 | import Foundation 11 | import CoreData 12 | import CoreDataCodable 13 | 14 | /// Base CoreData Entity `NSManagedObject` subclass for CoreSummit. 15 | open class Entity: NSManagedObject { 16 | 17 | /// The unique identifier of this entity. 18 | @NSManaged var identifier: Int64 19 | 20 | /// The date this object was stored in its entirety. 21 | @NSManaged var cached: Date? 22 | } 23 | 24 | public extension Entity { 25 | 26 | static var identifierProperty: String { return "identifier" } 27 | 28 | func didCache() { 29 | 30 | self.cached = Date() 31 | } 32 | 33 | static func entity(in context: NSManagedObjectContext) -> NSEntityDescription { 34 | 35 | let className = NSStringFromClass(self as AnyClass) 36 | 37 | struct Cache { 38 | static var entities = [String: NSEntityDescription]() 39 | } 40 | 41 | // try to get from cache 42 | if let entity = Cache.entities[className] { 43 | 44 | return entity 45 | } 46 | 47 | // search for entity with class name 48 | guard let entity = context.persistentStoreCoordinator?.managedObjectModel[self] 49 | else { fatalError("Could not find entity") } 50 | 51 | Cache.entities[className] = entity 52 | 53 | return entity 54 | } 55 | } 56 | 57 | extension NSManagedObjectModel { 58 | 59 | subscript(managedObjectType: NSManagedObject.Type) -> NSEntityDescription? { 60 | 61 | // search for entity with class name 62 | 63 | let className = NSStringFromClass(managedObjectType) 64 | 65 | return self.entities.first { $0.managedObjectClassName == className } 66 | } 67 | } 68 | 69 | public final class SummitManagedObject: Entity { 70 | 71 | /// The date this summit was fetched from the server. 72 | @NSManaged public var initialDataLoad: Date? 73 | 74 | @NSManaged public var name: String 75 | 76 | @NSManaged public var timeZone: String 77 | 78 | @NSManaged public var datesLabel: String? 79 | 80 | @NSManaged public var start: Date 81 | 82 | @NSManaged public var end: Date 83 | 84 | @NSManaged public var defaultStart: Date? 85 | 86 | @NSManaged public var webpage: String 87 | 88 | @NSManaged public var active: Bool 89 | 90 | @NSManaged public var startShowingVenues: Date? 91 | 92 | @NSManaged public var sponsors: Set 93 | 94 | @NSManaged public var speakers: Set 95 | 96 | @NSManaged public var ticketTypes: Set 97 | 98 | @NSManaged public var locations: Set 99 | 100 | @NSManaged public var tracks: Set 101 | 102 | @NSManaged public var trackGroups: Set 103 | 104 | @NSManaged public var eventTypes: Set 105 | 106 | @NSManaged public var schedule: Set 107 | 108 | @NSManaged public var wirelessNetworks: Set 109 | } 110 | 111 | public final class CompanyManagedObject: Entity { 112 | 113 | @NSManaged public var name: String 114 | 115 | // Inverse Relationships 116 | 117 | @NSManaged public var events: Set 118 | 119 | @NSManaged public var summits: Set 120 | } 121 | 122 | public final class EventManagedObject: Entity { 123 | 124 | @NSManaged public var name: String 125 | 126 | @NSManaged public var descriptionText: String? 127 | 128 | @NSManaged public var socialDescription: String? 129 | 130 | @NSManaged public var start: Date 131 | 132 | @NSManaged public var end: Date 133 | 134 | @NSManaged public var allowFeedback: Bool 135 | 136 | @NSManaged public var averageFeedback: Double 137 | 138 | @NSManaged public var rsvp: String? 139 | 140 | @NSManaged public var externalRSVP: Bool 141 | 142 | @NSManaged public var willRecord: Bool 143 | 144 | @NSManaged public var attachment: String? 145 | 146 | @NSManaged public var track: TrackManagedObject? 147 | 148 | @NSManaged public var eventType: EventTypeManagedObject 149 | 150 | @NSManaged public var sponsors: Set 151 | 152 | @NSManaged public var tags: Set 153 | 154 | @NSManaged public var location: LocationManagedObject? 155 | 156 | @NSManaged public var presentation: PresentationManagedObject 157 | 158 | @NSManaged public var videos: Set 159 | 160 | @NSManaged public var slides: Set 161 | 162 | @NSManaged public var links: Set 163 | 164 | @NSManaged public var groups: Set 165 | 166 | @NSManaged public var summit: SummitManagedObject 167 | 168 | // MARK: - Inverse Relationhips 169 | 170 | @NSManaged public var members: Set 171 | } 172 | 173 | public final class EventTypeManagedObject: Entity { 174 | 175 | @NSManaged public var name: String 176 | 177 | @NSManaged public var color: String 178 | 179 | @NSManaged public var blackOutTimes: Bool 180 | 181 | // Inverse Relationships 182 | 183 | @NSManaged public var events: Set 184 | 185 | @NSManaged public var summits: Set 186 | } 187 | 188 | public final class GroupManagedObject: Entity { 189 | 190 | @NSManaged public var title: String 191 | 192 | @NSManaged public var descriptionText: String? 193 | 194 | @NSManaged public var code: String 195 | } 196 | 197 | open class LocationManagedObject: Entity { 198 | 199 | @NSManaged open var name: String 200 | 201 | @NSManaged open var descriptionText: String? 202 | 203 | // Inverse Relationships 204 | 205 | @NSManaged open var events: Set 206 | 207 | @NSManaged open var summit: SummitManagedObject 208 | } 209 | 210 | public final class VenueManagedObject: LocationManagedObject { 211 | 212 | @NSManaged public var venueType: String 213 | 214 | @NSManaged public var locationType: String 215 | 216 | @NSManaged public var country: String 217 | 218 | @NSManaged public var address: String? 219 | 220 | @NSManaged public var city: String? 221 | 222 | @NSManaged public var zipCode: String? 223 | 224 | @NSManaged public var state: String? 225 | 226 | @NSManaged public var latitude: String? 227 | 228 | @NSManaged public var longitude: String? 229 | 230 | @NSManaged public var images: Set 231 | 232 | @NSManaged public var maps: Set 233 | 234 | @NSManaged public var floors: Set 235 | } 236 | 237 | public final class VenueRoomManagedObject: LocationManagedObject { 238 | 239 | @NSManaged public var capacity: NSNumber? 240 | 241 | @NSManaged public var venue: VenueManagedObject 242 | 243 | @NSManaged public var floor: VenueFloorManagedObject? 244 | } 245 | 246 | public final class VenueFloorManagedObject: Entity { 247 | 248 | @NSManaged public var name: String 249 | 250 | @NSManaged public var descriptionText: String? 251 | 252 | @NSManaged public var number: Int16 253 | 254 | @NSManaged public var image: String? 255 | 256 | @NSManaged public var venue: VenueManagedObject 257 | 258 | @NSManaged public var rooms: Set 259 | } 260 | 261 | public final class ImageManagedObject: Entity { 262 | 263 | @NSManaged public var url: String 264 | } 265 | 266 | public final class PresentationManagedObject: Entity { 267 | 268 | @NSManaged public var level: String? 269 | 270 | @NSManaged public var track: TrackManagedObject? 271 | 272 | @NSManaged public var moderator: SpeakerManagedObject? 273 | 274 | @NSManaged public var speakers: Set 275 | 276 | // Inverse Relationships 277 | 278 | @NSManaged public var event: EventManagedObject 279 | } 280 | 281 | public final class TrackManagedObject: Entity { 282 | 283 | @NSManaged public var name: String 284 | 285 | @NSManaged public var groups: Set 286 | 287 | // Inverse Relationships 288 | 289 | @NSManaged public var events: Set 290 | 291 | @NSManaged public var summits: Set 292 | } 293 | 294 | public final class TrackGroupManagedObject: Entity { 295 | 296 | @NSManaged public var name: String 297 | 298 | @NSManaged public var descriptionText: String? 299 | 300 | @NSManaged public var color: String 301 | 302 | @NSManaged public var tracks: Set 303 | 304 | // Inverse Relationships 305 | 306 | @NSManaged public var summits: Set 307 | } 308 | 309 | 310 | public final class SpeakerManagedObject: Entity { 311 | 312 | @NSManaged public var firstName: String 313 | 314 | @NSManaged public var lastName: String 315 | 316 | @NSManaged public var addressBookSectionName: String 317 | 318 | @NSManaged public var title: String? 319 | 320 | @NSManaged public var picture: String 321 | 322 | @NSManaged public var twitter: String? 323 | 324 | @NSManaged public var irc: String? 325 | 326 | @NSManaged public var biography: String? 327 | 328 | @NSManaged public var affiliations: Set 329 | 330 | // Inverse Relationships 331 | 332 | @NSManaged public var summits: Set 333 | 334 | @NSManaged public var presentationModerator: Set 335 | 336 | @NSManaged public var presentationSpeaker: Set 337 | } 338 | 339 | public final class SummitTypeManagedObject: Entity { 340 | 341 | @NSManaged public var name: String 342 | 343 | @NSManaged public var color: String 344 | } 345 | 346 | public final class TicketTypeManagedObject: Entity { 347 | 348 | @NSManaged public var name: String 349 | 350 | @NSManaged public var descriptionText: String? 351 | } 352 | 353 | public final class TagManagedObject: Entity { 354 | 355 | @NSManaged public var name: String 356 | } 357 | 358 | public final class VideoManagedObject: Entity { 359 | 360 | @NSManaged public var name: String 361 | 362 | @NSManaged public var descriptionText: String? 363 | 364 | @NSManaged public var displayOnSite: Bool 365 | 366 | @NSManaged public var featured: Bool 367 | 368 | @NSManaged public var youtube: String 369 | 370 | @NSManaged public var order: Int64 371 | 372 | @NSManaged public var views: Int64 373 | 374 | @NSManaged public var highlighted: Bool 375 | 376 | @NSManaged public var dataUploaded: Date 377 | 378 | @NSManaged public var event: EventManagedObject 379 | } 380 | 381 | public final class SlideManagedObject: Entity { 382 | 383 | @NSManaged public var name: String 384 | 385 | @NSManaged public var descriptionText: String? 386 | 387 | @NSManaged public var displayOnSite: Bool 388 | 389 | @NSManaged public var featured: Bool 390 | 391 | @NSManaged public var order: Int64 392 | 393 | @NSManaged public var link: String 394 | 395 | @NSManaged public var event: EventManagedObject 396 | } 397 | 398 | public final class LinkManagedObject: Entity { 399 | 400 | @NSManaged public var name: String 401 | 402 | @NSManaged public var descriptionText: String? 403 | 404 | @NSManaged public var displayOnSite: Bool 405 | 406 | @NSManaged public var featured: Bool 407 | 408 | @NSManaged public var order: Int64 409 | 410 | @NSManaged public var link: String 411 | 412 | @NSManaged public var event: EventManagedObject 413 | } 414 | 415 | public final class WirelessNetworkManagedObject: Entity { 416 | 417 | @NSManaged public var name: String 418 | 419 | @NSManaged public var password: String 420 | 421 | @NSManaged public var descriptionText: String? 422 | 423 | @NSManaged public var summit: SummitManagedObject 424 | } 425 | 426 | public final class AffiliationManagedObject: Entity { 427 | 428 | @NSManaged public var member: MemberManagedObject 429 | 430 | @NSManaged public var start: Date? 431 | 432 | @NSManaged public var end: Date? 433 | 434 | @NSManaged public var isCurrent: Bool 435 | 436 | @NSManaged public var organization: AffiliationOrganizationManagedObject 437 | } 438 | 439 | public final class AffiliationOrganizationManagedObject: Entity { 440 | 441 | @NSManaged public var name: String 442 | } 443 | 444 | public final class MemberManagedObject: Entity { 445 | 446 | @NSManaged public var firstName: String 447 | 448 | @NSManaged public var lastName: String 449 | 450 | @NSManaged public var gender: String? 451 | 452 | @NSManaged public var picture: String 453 | 454 | @NSManaged public var twitter: String? 455 | 456 | @NSManaged public var linkedIn: String? 457 | 458 | @NSManaged public var irc: String? 459 | 460 | @NSManaged public var biography: String? 461 | 462 | @NSManaged public var speakerRole: SpeakerManagedObject? 463 | 464 | @NSManaged public var attendeeRole: AttendeeManagedObject? 465 | 466 | @NSManaged public var schedule: Set 467 | 468 | @NSManaged public var groups: Set 469 | 470 | @NSManaged public var groupEvents: Set 471 | 472 | @NSManaged public var feedback: Set 473 | 474 | @NSManaged public var favoriteEvents: Set 475 | 476 | @NSManaged public var affiliations: Set 477 | } 478 | 479 | public final class AttendeeManagedObject: Entity { 480 | 481 | @NSManaged public var member: MemberManagedObject 482 | 483 | @NSManaged public var tickets: Set 484 | } 485 | 486 | open class FeedbackManagedObject: Entity { 487 | 488 | @NSManaged open var rate: Int16 489 | 490 | @NSManaged open var review: String 491 | 492 | @NSManaged open var date: Date 493 | 494 | @NSManaged open var event: EventManagedObject 495 | 496 | @NSManaged open var member: MemberManagedObject 497 | } 498 | 499 | // MARK: - DecodableManagedObject 500 | 501 | extension Entity { 502 | 503 | public var decodedIdentifier: Any { return identifier } 504 | } 505 | 506 | extension SummitManagedObject: DecodableManagedObject { 507 | 508 | public var decodable: CoreDataCodable.Type { return Model.Summit.self } 509 | } 510 | 511 | extension WirelessNetworkManagedObject: DecodableManagedObject { 512 | 513 | public var decodable: CoreDataCodable.Type { return Model.WirelessNetwork.self } 514 | } 515 | 516 | extension CompanyManagedObject: DecodableManagedObject { 517 | 518 | public var decodable: CoreDataCodable.Type { return Model.Company.self } 519 | } 520 | 521 | extension SpeakerManagedObject: DecodableManagedObject { 522 | 523 | public var decodable: CoreDataCodable.Type { return Model.Speaker.self } 524 | } 525 | 526 | extension AffiliationManagedObject: DecodableManagedObject { 527 | 528 | public var decodable: CoreDataCodable.Type { return Model.Affiliation.self } 529 | } 530 | 531 | extension AffiliationOrganizationManagedObject: DecodableManagedObject { 532 | 533 | public var decodable: CoreDataCodable.Type { return Model.AffiliationOrganization.self } 534 | } 535 | 536 | extension TicketTypeManagedObject: DecodableManagedObject { 537 | 538 | public var decodable: CoreDataCodable.Type { return Model.TicketType.self } 539 | } 540 | 541 | extension ImageManagedObject: DecodableManagedObject { 542 | 543 | public var decodable: CoreDataCodable.Type { return Model.Image.self } 544 | } 545 | 546 | extension VenueManagedObject: DecodableManagedObject { 547 | 548 | public var decodable: CoreDataCodable.Type { return Model.Venue.self } 549 | } 550 | 551 | extension VenueRoomManagedObject: DecodableManagedObject { 552 | 553 | public var decodable: CoreDataCodable.Type { return Model.VenueRoom.self } 554 | } 555 | 556 | extension VenueFloorManagedObject: DecodableManagedObject { 557 | 558 | public var decodable: CoreDataCodable.Type { return Model.VenueFloor.self } 559 | } 560 | 561 | extension TrackManagedObject: DecodableManagedObject { 562 | 563 | public var decodable: CoreDataCodable.Type { return Model.Track.self } 564 | } 565 | 566 | extension TrackGroupManagedObject: DecodableManagedObject { 567 | 568 | public var decodable: CoreDataCodable.Type { return Model.TrackGroup.self } 569 | } 570 | 571 | extension EventManagedObject: DecodableManagedObject { 572 | 573 | public var decodable: CoreDataCodable.Type { return Model.Event.self } 574 | } 575 | 576 | extension EventTypeManagedObject: DecodableManagedObject { 577 | 578 | public var decodable: CoreDataCodable.Type { return Model.EventType.self } 579 | } 580 | 581 | extension PresentationManagedObject: DecodableManagedObject { 582 | 583 | public var decodable: CoreDataCodable.Type { return Model.Presentation.self } 584 | } 585 | 586 | extension LinkManagedObject: DecodableManagedObject { 587 | 588 | public var decodable: CoreDataCodable.Type { return Model.Link.self } 589 | } 590 | 591 | extension TagManagedObject: DecodableManagedObject { 592 | 593 | public var decodable: CoreDataCodable.Type { return Model.Tag.self } 594 | } 595 | 596 | extension VideoManagedObject: DecodableManagedObject { 597 | 598 | public var decodable: CoreDataCodable.Type { return Model.Video.self } 599 | } 600 | 601 | extension SlideManagedObject: DecodableManagedObject { 602 | 603 | public var decodable: CoreDataCodable.Type { return Model.Slide.self } 604 | } 605 | -------------------------------------------------------------------------------- /Tests/CoreDataCodableTests/TestSummitModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestSummitModel.swift 3 | // CoreDataCodable 4 | // 5 | // Created by Alsey Coleman Miller on 11/6/17. 6 | // Copyright © 2017 ColemanCDA. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreData 11 | import CoreDataCodable 12 | 13 | public struct Model { 14 | 15 | public struct Summit: Codable { 16 | 17 | public struct Identifier: Codable, RawRepresentable { 18 | 19 | public let rawValue: Int64 20 | 21 | public init?(rawValue: Int64) { 22 | 23 | guard rawValue > 0 else { return nil } 24 | 25 | self.rawValue = rawValue 26 | } 27 | 28 | internal init(_ identifier: Int64) { 29 | 30 | assert(identifier > 0) 31 | 32 | self.rawValue = identifier 33 | } 34 | } 35 | 36 | public let identifier: Identifier 37 | 38 | public var name: String 39 | 40 | public var timeZone: String 41 | 42 | public var datesLabel: String? 43 | 44 | public var start: Date 45 | 46 | public var end: Date 47 | 48 | /// Default start date for the Summit. 49 | public var defaultStart: Date? 50 | 51 | public var active: Bool 52 | 53 | public var webpage: URL 54 | 55 | public var sponsors: [Company] 56 | 57 | public var speakers: [Speaker] 58 | 59 | public var startShowingVenues: Date? 60 | 61 | public var ticketTypes: [TicketType] 62 | 63 | // Venue and Venue Rooms 64 | public var locations: [Location] 65 | 66 | public var tracks: [Track] 67 | 68 | public var trackGroups: [TrackGroup] 69 | 70 | public var eventTypes: [EventType] 71 | 72 | public var schedule: [Event] 73 | 74 | public var wirelessNetworks: [WirelessNetwork] 75 | } 76 | 77 | public struct WirelessNetwork: Codable { 78 | 79 | public struct Identifier: Codable, RawRepresentable { 80 | 81 | public let rawValue: Int64 82 | 83 | public init?(rawValue: Int64) { 84 | 85 | guard rawValue > 0 else { return nil } 86 | 87 | self.rawValue = rawValue 88 | } 89 | 90 | internal init(_ identifier: Int64) { 91 | 92 | assert(identifier > 0) 93 | 94 | self.rawValue = identifier 95 | } 96 | } 97 | 98 | public let identifier: Identifier 99 | 100 | public let name: String 101 | 102 | public let password: String 103 | 104 | public let descriptionText: String? 105 | 106 | public let summit: Summit.Identifier 107 | } 108 | 109 | public struct Company: Codable { 110 | 111 | public struct Identifier: Codable, RawRepresentable { 112 | 113 | public let rawValue: Int64 114 | 115 | public init?(rawValue: Int64) { 116 | 117 | guard rawValue > 0 else { return nil } 118 | 119 | self.rawValue = rawValue 120 | } 121 | 122 | internal init(_ identifier: Int64) { 123 | 124 | assert(identifier > 0) 125 | 126 | self.rawValue = identifier 127 | } 128 | } 129 | 130 | public let identifier: Identifier 131 | 132 | public var name: String 133 | } 134 | 135 | public struct Speaker: Codable { 136 | 137 | public struct Identifier: Codable, RawRepresentable { 138 | 139 | public let rawValue: Int64 140 | 141 | public init?(rawValue: Int64) { 142 | 143 | guard rawValue > 0 else { return nil } 144 | 145 | self.rawValue = rawValue 146 | } 147 | 148 | internal init(_ identifier: Int64) { 149 | 150 | assert(identifier > 0) 151 | 152 | self.rawValue = identifier 153 | } 154 | } 155 | 156 | public let identifier: Identifier 157 | 158 | public var addressBookSectionName: String 159 | 160 | public var firstName: String 161 | 162 | public var lastName: String 163 | 164 | public var title: String? 165 | 166 | public var picture: URL 167 | 168 | public var twitter: String? 169 | 170 | public var irc: String? 171 | 172 | public var biography: String? 173 | 174 | public var affiliations: [Affiliation] 175 | } 176 | 177 | public struct Affiliation: Codable { 178 | 179 | public struct Identifier: Codable, RawRepresentable { 180 | 181 | public let rawValue: Int64 182 | 183 | public init?(rawValue: Int64) { 184 | 185 | guard rawValue > 0 else { return nil } 186 | 187 | self.rawValue = rawValue 188 | } 189 | 190 | internal init(_ identifier: Int64) { 191 | 192 | assert(identifier > 0) 193 | 194 | self.rawValue = identifier 195 | } 196 | } 197 | 198 | public let identifier: Identifier 199 | 200 | public var member: Model.Member.Identifier 201 | 202 | public var start: Date? 203 | 204 | public var end: Date? 205 | 206 | public var isCurrent: Bool 207 | 208 | public var organization: AffiliationOrganization 209 | } 210 | 211 | public struct AffiliationOrganization: Codable { 212 | 213 | public struct Identifier: Codable, RawRepresentable { 214 | 215 | public let rawValue: Int64 216 | 217 | public init?(rawValue: Int64) { 218 | 219 | guard rawValue > 0 else { return nil } 220 | 221 | self.rawValue = rawValue 222 | } 223 | 224 | internal init(_ identifier: Int64) { 225 | 226 | assert(identifier > 0) 227 | 228 | self.rawValue = identifier 229 | } 230 | } 231 | 232 | public let identifier: Identifier 233 | 234 | public var name: String 235 | } 236 | 237 | public struct TicketType: Codable { 238 | 239 | public struct Identifier: Codable, RawRepresentable { 240 | 241 | public let rawValue: Int64 242 | 243 | public init?(rawValue: Int64) { 244 | 245 | guard rawValue > 0 else { return nil } 246 | 247 | self.rawValue = rawValue 248 | } 249 | 250 | internal init(_ identifier: Int64) { 251 | 252 | assert(identifier > 0) 253 | 254 | self.rawValue = identifier 255 | } 256 | } 257 | 258 | public let identifier: Identifier 259 | 260 | public var name: String 261 | 262 | public var descriptionText: String? 263 | } 264 | 265 | public struct Image: Codable { 266 | 267 | public struct Identifier: Codable, RawRepresentable { 268 | 269 | public let rawValue: Int64 270 | 271 | public init?(rawValue: Int64) { 272 | 273 | guard rawValue > 0 else { return nil } 274 | 275 | self.rawValue = rawValue 276 | } 277 | 278 | internal init(_ identifier: Int64) { 279 | 280 | assert(identifier > 0) 281 | 282 | self.rawValue = identifier 283 | } 284 | } 285 | 286 | public let identifier: Identifier 287 | 288 | public var url: URL 289 | } 290 | 291 | public enum Location: Codable { 292 | 293 | public struct Identifier: Codable, RawRepresentable { 294 | 295 | public let rawValue: Int64 296 | 297 | public init?(rawValue: Int64) { 298 | 299 | guard rawValue > 0 else { return nil } 300 | 301 | self.rawValue = rawValue 302 | } 303 | 304 | internal init(_ identifier: Int64) { 305 | 306 | assert(identifier > 0) 307 | 308 | self.rawValue = identifier 309 | } 310 | } 311 | 312 | case venue(Venue) 313 | case room(VenueRoom) 314 | 315 | public var identifier: CoreDataIdentifier { 316 | 317 | switch self { 318 | case let .venue(venue): return venue.identifier 319 | case let .room(room): return room.identifier 320 | } 321 | } 322 | 323 | public init(from decoder: Decoder) throws { 324 | 325 | var errors = [Swift.Error]() 326 | 327 | do { 328 | 329 | let venue = try Venue(from: decoder) 330 | 331 | self = .venue(venue) 332 | return 333 | 334 | } catch { 335 | 336 | errors.append(error) 337 | } 338 | 339 | do { 340 | 341 | let venue = try VenueRoom(from: decoder) 342 | 343 | self = .room(venue) 344 | return 345 | 346 | } catch { 347 | 348 | errors.append(error) 349 | } 350 | 351 | struct InvalidLocationError: Error { 352 | 353 | let errors: [Error] 354 | 355 | let context: DecodingError.Context 356 | } 357 | 358 | throw InvalidLocationError(errors: errors, context: DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Could not decode location.")) 359 | } 360 | 361 | public func encode(to encoder: Encoder) throws { 362 | 363 | switch self { 364 | case let .venue(venue): return try venue.encode(to: encoder) 365 | case let .room(room): return try room.encode(to: encoder) 366 | } 367 | } 368 | } 369 | 370 | public struct Venue: Codable { 371 | 372 | public struct Identifier: Codable, RawRepresentable { 373 | 374 | public let rawValue: Int64 375 | 376 | public init?(rawValue: Int64) { 377 | 378 | guard rawValue > 0 else { return nil } 379 | 380 | self.rawValue = rawValue 381 | } 382 | 383 | internal init(_ identifier: Int64) { 384 | 385 | assert(identifier > 0) 386 | 387 | self.rawValue = identifier 388 | } 389 | } 390 | 391 | public enum LocationType: String, Codable { 392 | 393 | case Internal, External, None 394 | } 395 | 396 | public enum ClassName: String, Codable { 397 | 398 | case SummitVenue, SummitExternalLocation, SummitHotel, SummitAirport 399 | } 400 | 401 | public let identifier: Identifier 402 | 403 | public let venueType: ClassName 404 | 405 | public var name: String 406 | 407 | public var descriptionText: String? 408 | 409 | public var locationType: LocationType 410 | 411 | public var country: String 412 | 413 | public var address: String? 414 | 415 | public var city: String? 416 | 417 | public var zipCode: String? 418 | 419 | public var state: String? 420 | 421 | public var latitude: String? 422 | 423 | public var longitude: String? 424 | 425 | public var maps: [Image] 426 | 427 | public var images: [Image] 428 | 429 | public var floors: [VenueFloor] 430 | } 431 | 432 | public struct VenueFloor: Codable { 433 | 434 | public struct Identifier: Codable, RawRepresentable { 435 | 436 | public let rawValue: Int64 437 | 438 | public init?(rawValue: Int64) { 439 | 440 | guard rawValue > 0 else { return nil } 441 | 442 | self.rawValue = rawValue 443 | } 444 | 445 | internal init(_ identifier: Int64) { 446 | 447 | assert(identifier > 0) 448 | 449 | self.rawValue = identifier 450 | } 451 | } 452 | 453 | public let identifier: Identifier 454 | 455 | public var name: String 456 | 457 | public var descriptionText: String? 458 | 459 | public var number: Int16 460 | 461 | public var image: URL? 462 | 463 | public var venue: Venue.Identifier 464 | 465 | public var rooms: [VenueRoom.Identifier] 466 | } 467 | 468 | public struct VenueRoom: Codable { 469 | 470 | public struct Identifier: Codable, RawRepresentable { 471 | 472 | public let rawValue: Int64 473 | 474 | public init?(rawValue: Int64) { 475 | 476 | guard rawValue > 0 else { return nil } 477 | 478 | self.rawValue = rawValue 479 | } 480 | 481 | internal init(_ identifier: Int64) { 482 | 483 | assert(identifier > 0) 484 | 485 | self.rawValue = identifier 486 | } 487 | } 488 | 489 | public let identifier: Identifier 490 | 491 | public var name: String 492 | 493 | public var descriptionText: String? 494 | 495 | public var capacity: Int? 496 | 497 | public var venue: Venue.Identifier 498 | 499 | public var floor: VenueFloor.Identifier? 500 | } 501 | 502 | public struct Track: Codable { 503 | 504 | public struct Identifier: Codable, RawRepresentable { 505 | 506 | public let rawValue: Int64 507 | 508 | public init?(rawValue: Int64) { 509 | 510 | guard rawValue > 0 else { return nil } 511 | 512 | self.rawValue = rawValue 513 | } 514 | 515 | internal init(_ identifier: Int64) { 516 | 517 | assert(identifier > 0) 518 | 519 | self.rawValue = identifier 520 | } 521 | } 522 | 523 | public let identifier: Identifier 524 | 525 | public var name: String 526 | 527 | public var groups: [TrackGroup.Identifier] 528 | } 529 | 530 | public struct TrackGroup: Codable { 531 | 532 | public struct Identifier: Codable, RawRepresentable { 533 | 534 | public let rawValue: Int64 535 | 536 | public init?(rawValue: Int64) { 537 | 538 | guard rawValue > 0 else { return nil } 539 | 540 | self.rawValue = rawValue 541 | } 542 | 543 | internal init(_ identifier: Int64) { 544 | 545 | assert(identifier > 0) 546 | 547 | self.rawValue = identifier 548 | } 549 | } 550 | 551 | public let identifier: Identifier 552 | 553 | public var name: String 554 | 555 | public var descriptionText: String? 556 | 557 | public var color: String 558 | 559 | public var tracks: [Track.Identifier] 560 | } 561 | 562 | public struct EventType: Codable { 563 | 564 | public struct Identifier: Codable, RawRepresentable { 565 | 566 | public let rawValue: Int64 567 | 568 | public init?(rawValue: Int64) { 569 | 570 | guard rawValue > 0 else { return nil } 571 | 572 | self.rawValue = rawValue 573 | } 574 | 575 | internal init(_ identifier: Int64) { 576 | 577 | assert(identifier > 0) 578 | 579 | self.rawValue = identifier 580 | } 581 | } 582 | 583 | public let identifier: Identifier 584 | 585 | public var name: String 586 | 587 | public var color: String 588 | 589 | public var blackOutTimes: Bool 590 | } 591 | 592 | public struct Event: Codable { 593 | 594 | public struct Identifier: Codable, RawRepresentable { 595 | 596 | public let rawValue: Int64 597 | 598 | public init?(rawValue: Int64) { 599 | 600 | guard rawValue > 0 else { return nil } 601 | 602 | self.rawValue = rawValue 603 | } 604 | 605 | internal init(_ identifier: Int64) { 606 | 607 | assert(identifier > 0) 608 | 609 | self.rawValue = identifier 610 | } 611 | } 612 | 613 | public let identifier: Identifier 614 | 615 | public var name: String 616 | 617 | public var summit: Summit.Identifier 618 | 619 | public var descriptionText: String? 620 | 621 | public var socialDescription: String? 622 | 623 | public var start: Date 624 | 625 | public var end: Date 626 | 627 | public var track: Track.Identifier? 628 | 629 | public var allowFeedback: Bool 630 | 631 | public var averageFeedback: Double 632 | 633 | public var eventType: EventType.Identifier 634 | 635 | public var rsvp: String? 636 | 637 | public var externalRSVP: Bool? 638 | 639 | public var willRecord: Bool? 640 | 641 | public var attachment: URL? 642 | 643 | public var sponsors: [Company.Identifier] 644 | 645 | public var tags: [Tag] 646 | 647 | public var location: Location.Identifier? 648 | 649 | public var videos: [Video] 650 | 651 | public var slides: [Slide] 652 | 653 | public var links: [Link] 654 | 655 | // Never comes from this JSON 656 | //public var groups: [Group] 657 | 658 | // Presentation values 659 | 660 | // created from self 661 | public var presentation: Presentation 662 | } 663 | 664 | public struct Presentation: Codable { 665 | 666 | public struct Identifier: Codable, RawRepresentable { 667 | 668 | public let rawValue: Int64 669 | 670 | public init?(rawValue: Int64) { 671 | 672 | guard rawValue > 0 else { return nil } 673 | 674 | self.rawValue = rawValue 675 | } 676 | 677 | internal init(_ identifier: Int64) { 678 | 679 | assert(identifier > 0) 680 | 681 | self.rawValue = identifier 682 | } 683 | } 684 | 685 | public let identifier: Identifier 686 | 687 | public var level: Level? 688 | 689 | public var moderator: Speaker.Identifier? 690 | 691 | public var speakers: [Speaker.Identifier] 692 | } 693 | 694 | public enum Level: String, Codable { 695 | 696 | case beginner = "Beginner" 697 | case intermediate = "Intermediate" 698 | case advanced = "Advanced" 699 | case notApplicable = "N/A" 700 | } 701 | 702 | public struct Link: Codable { 703 | 704 | public struct Identifier: Codable, RawRepresentable { 705 | 706 | public let rawValue: Int64 707 | 708 | public init?(rawValue: Int64) { 709 | 710 | guard rawValue > 0 else { return nil } 711 | 712 | self.rawValue = rawValue 713 | } 714 | 715 | internal init(_ identifier: Int64) { 716 | 717 | assert(identifier > 0) 718 | 719 | self.rawValue = identifier 720 | } 721 | } 722 | 723 | public let identifier: Identifier 724 | 725 | public var name: String? 726 | 727 | public var descriptionText: String? 728 | 729 | public var displayOnSite: Bool 730 | 731 | public var featured: Bool 732 | 733 | public var order: Int64 734 | 735 | public var link: String // not always valid URL 736 | 737 | public var event: Event.Identifier 738 | } 739 | 740 | public struct Tag: Codable { 741 | 742 | public struct Identifier: Codable, RawRepresentable { 743 | 744 | public let rawValue: Int64 745 | 746 | public init?(rawValue: Int64) { 747 | 748 | guard rawValue > 0 else { return nil } 749 | 750 | self.rawValue = rawValue 751 | } 752 | 753 | internal init(_ identifier: Int64) { 754 | 755 | assert(identifier > 0) 756 | 757 | self.rawValue = identifier 758 | } 759 | } 760 | 761 | public let identifier: Identifier 762 | 763 | public var name: String 764 | } 765 | 766 | public struct Video: Codable { 767 | 768 | public struct Identifier: Codable, RawRepresentable { 769 | 770 | public let rawValue: Int64 771 | 772 | public init?(rawValue: Int64) { 773 | 774 | guard rawValue > 0 else { return nil } 775 | 776 | self.rawValue = rawValue 777 | } 778 | 779 | internal init(_ identifier: Int64) { 780 | 781 | assert(identifier > 0) 782 | 783 | self.rawValue = identifier 784 | } 785 | } 786 | 787 | public let identifier: Identifier 788 | 789 | public var name: String 790 | 791 | public var descriptionText: String? 792 | 793 | public var displayOnSite: Bool 794 | 795 | public var featured: Bool 796 | 797 | public var highlighted: Bool 798 | 799 | public var youtube: String 800 | 801 | public var dataUploaded: Date 802 | 803 | public var order: Int64 804 | 805 | public var views: Int64 806 | 807 | public var event: Event.Identifier 808 | } 809 | 810 | public struct Slide: Codable { 811 | 812 | public struct Identifier: Codable, RawRepresentable { 813 | 814 | public let rawValue: Int64 815 | 816 | public init?(rawValue: Int64) { 817 | 818 | guard rawValue > 0 else { return nil } 819 | 820 | self.rawValue = rawValue 821 | } 822 | 823 | internal init(_ identifier: Int64) { 824 | 825 | assert(identifier > 0) 826 | 827 | self.rawValue = identifier 828 | } 829 | } 830 | 831 | public let identifier: Identifier 832 | 833 | public var name: String 834 | 835 | public var descriptionText: String? 836 | 837 | public var displayOnSite: Bool 838 | 839 | public var featured: Bool 840 | 841 | public var order: Int64 842 | 843 | public var link: URL 844 | 845 | public var event: Event.Identifier 846 | } 847 | 848 | public struct Member: Codable { 849 | 850 | public struct Identifier: Codable, RawRepresentable { 851 | 852 | public let rawValue: Int64 853 | 854 | public init?(rawValue: Int64) { 855 | 856 | guard rawValue > 0 else { return nil } 857 | 858 | self.rawValue = rawValue 859 | } 860 | 861 | internal init(_ identifier: Int64) { 862 | 863 | assert(identifier > 0) 864 | 865 | self.rawValue = identifier 866 | } 867 | } 868 | 869 | public let identifier: Identifier 870 | 871 | public let firstName: String 872 | 873 | public let lastName: String 874 | 875 | public let gender: String? 876 | 877 | public let picture: URL 878 | 879 | public let twitter: String? 880 | 881 | public let linkedIn: String? 882 | 883 | public let irc: String? 884 | 885 | public let biography: String? 886 | 887 | public let speakerRole: Speaker? 888 | 889 | public let attendeeRole: Attendee? 890 | 891 | public var schedule: [Event.Identifier] 892 | 893 | public let groupEvents: [Event.Identifier] 894 | 895 | public let favoriteEvents: [Event.Identifier] 896 | 897 | public let groups: [Group] 898 | 899 | public let feedback: [Feedback.Identifier] 900 | 901 | public let affiliations: [Affiliation] 902 | } 903 | 904 | public struct Attendee: Codable { 905 | 906 | public struct Identifier: Codable, RawRepresentable { 907 | 908 | public let rawValue: Int64 909 | 910 | public init?(rawValue: Int64) { 911 | 912 | guard rawValue > 0 else { return nil } 913 | 914 | self.rawValue = rawValue 915 | } 916 | 917 | internal init(_ identifier: Int64) { 918 | 919 | assert(identifier > 0) 920 | 921 | self.rawValue = identifier 922 | } 923 | } 924 | 925 | public let identifier: Identifier 926 | 927 | public var member: Member.Identifier 928 | 929 | public var tickets: [TicketType.Identifier] 930 | } 931 | 932 | public struct Group: Codable { 933 | 934 | public struct Identifier: Codable, RawRepresentable { 935 | 936 | public let rawValue: Int64 937 | 938 | public init?(rawValue: Int64) { 939 | 940 | guard rawValue > 0 else { return nil } 941 | 942 | self.rawValue = rawValue 943 | } 944 | 945 | internal init(_ identifier: Int64) { 946 | 947 | assert(identifier > 0) 948 | 949 | self.rawValue = identifier 950 | } 951 | } 952 | 953 | public let identifier: Identifier 954 | 955 | public var title: String 956 | 957 | public var descriptionText: String? 958 | 959 | public var code: String 960 | } 961 | 962 | public struct Feedback: Codable { 963 | 964 | public struct Identifier: Codable, RawRepresentable { 965 | 966 | public let rawValue: Int64 967 | 968 | public init?(rawValue: Int64) { 969 | 970 | guard rawValue > 0 else { return nil } 971 | 972 | self.rawValue = rawValue 973 | } 974 | 975 | internal init(_ identifier: Int64) { 976 | 977 | assert(identifier > 0) 978 | 979 | self.rawValue = identifier 980 | } 981 | } 982 | 983 | public let identifier: Identifier 984 | 985 | public let rate: Int 986 | 987 | public let review: String 988 | 989 | public let date: Date 990 | 991 | public let event: Event.Identifier 992 | 993 | public let member: Member 994 | } 995 | } 996 | 997 | /// Type for organizing `Person` entities into an address book. 998 | public struct AddressBook { 999 | 1000 | /// All the sections in the address book. 1001 | public static let sections = ["#","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] 1002 | 1003 | /// Generate an uppercase letter to use as an address book section derived from the person's name. 1004 | public static func section(for name: String) -> String { 1005 | 1006 | let unknownLetter = sections[0] // "#" 1007 | 1008 | guard let firstLetter = name.first 1009 | else { return unknownLetter } 1010 | 1011 | let uppercaseLetter = String(firstLetter).uppercased() 1012 | 1013 | return sections.contains(uppercaseLetter) ? uppercaseLetter : unknownLetter 1014 | } 1015 | } 1016 | 1017 | // MARK: - SummitUnique 1018 | 1019 | public protocol SummitUnique { 1020 | 1021 | associatedtype Identifier: Codable, RawRepresentable 1022 | 1023 | var identifier: Identifier { get } 1024 | } 1025 | 1026 | extension SummitUnique where Self: CoreDataCodable, Self.Identifier: CoreDataIdentifier { 1027 | 1028 | // All identifier properties should be same as summit 1029 | public static var identifierKey: CodingKey { return Model.Summit.identifierKey } 1030 | 1031 | public var coreDataIdentifier: CoreDataIdentifier { return identifier } 1032 | } 1033 | 1034 | extension Model.Summit: SummitUnique { } 1035 | extension Model.WirelessNetwork: SummitUnique { } 1036 | extension Model.Company: SummitUnique { } 1037 | extension Model.Speaker: SummitUnique { } 1038 | extension Model.Affiliation: SummitUnique { } 1039 | extension Model.AffiliationOrganization: SummitUnique { } 1040 | extension Model.TicketType: SummitUnique { } 1041 | extension Model.Image: SummitUnique { } 1042 | extension Model.Venue: SummitUnique { } 1043 | extension Model.VenueRoom: SummitUnique { } 1044 | extension Model.VenueFloor: SummitUnique { } 1045 | extension Model.Track: SummitUnique { } 1046 | extension Model.TrackGroup: SummitUnique { } 1047 | extension Model.Event: SummitUnique { } 1048 | extension Model.EventType: SummitUnique { } 1049 | extension Model.Presentation: SummitUnique { } 1050 | extension Model.Link: SummitUnique { } 1051 | extension Model.Tag: SummitUnique { } 1052 | extension Model.Video: SummitUnique { } 1053 | extension Model.Slide: SummitUnique { } 1054 | 1055 | // MARK: - CoreDataIdentifier 1056 | 1057 | public protocol SummitCoreDataIdentifier: CoreDataIdentifier { 1058 | 1059 | associatedtype ManagedObject: Entity 1060 | } 1061 | 1062 | extension SummitCoreDataIdentifier where Self: RawRepresentable, Self.RawValue == Int64 { 1063 | 1064 | public func findOrCreate(in context: NSManagedObjectContext) throws -> NSManagedObject { 1065 | 1066 | let entityName = ManagedObject.entity(in: context).name! 1067 | 1068 | return try context.findOrCreate(identifier: self.rawValue as NSNumber, 1069 | property: "identifier", 1070 | entityName: entityName) 1071 | } 1072 | 1073 | public init?(managedObject: NSManagedObject) { 1074 | 1075 | guard let managedObject = managedObject as? ManagedObject 1076 | else { return nil } 1077 | 1078 | self.init(rawValue: managedObject.identifier) 1079 | } 1080 | } 1081 | 1082 | extension Model.Summit.Identifier: SummitCoreDataIdentifier { 1083 | 1084 | public typealias ManagedObject = SummitManagedObject 1085 | } 1086 | 1087 | extension Model.WirelessNetwork.Identifier: SummitCoreDataIdentifier { 1088 | 1089 | public typealias ManagedObject = WirelessNetworkManagedObject 1090 | } 1091 | 1092 | extension Model.Company.Identifier: SummitCoreDataIdentifier { 1093 | 1094 | public typealias ManagedObject = CompanyManagedObject 1095 | } 1096 | 1097 | extension Model.Speaker.Identifier: SummitCoreDataIdentifier { 1098 | 1099 | public typealias ManagedObject = SpeakerManagedObject 1100 | } 1101 | 1102 | extension Model.Affiliation.Identifier: SummitCoreDataIdentifier { 1103 | 1104 | public typealias ManagedObject = AffiliationManagedObject 1105 | } 1106 | 1107 | extension Model.AffiliationOrganization.Identifier: SummitCoreDataIdentifier { 1108 | 1109 | public typealias ManagedObject = AffiliationOrganizationManagedObject 1110 | } 1111 | 1112 | extension Model.TicketType.Identifier: SummitCoreDataIdentifier { 1113 | 1114 | public typealias ManagedObject = TicketTypeManagedObject 1115 | } 1116 | 1117 | extension Model.Image.Identifier: SummitCoreDataIdentifier { 1118 | 1119 | public typealias ManagedObject = ImageManagedObject 1120 | } 1121 | 1122 | extension Model.Venue.Identifier: SummitCoreDataIdentifier { 1123 | 1124 | public typealias ManagedObject = VenueManagedObject 1125 | } 1126 | 1127 | extension Model.VenueRoom.Identifier: SummitCoreDataIdentifier { 1128 | 1129 | public typealias ManagedObject = VenueRoomManagedObject 1130 | } 1131 | 1132 | extension Model.VenueFloor.Identifier: SummitCoreDataIdentifier { 1133 | 1134 | public typealias ManagedObject = VenueFloorManagedObject 1135 | } 1136 | 1137 | extension Model.Track.Identifier: SummitCoreDataIdentifier { 1138 | 1139 | public typealias ManagedObject = TrackManagedObject 1140 | } 1141 | 1142 | extension Model.TrackGroup.Identifier: SummitCoreDataIdentifier { 1143 | 1144 | public typealias ManagedObject = TrackGroupManagedObject 1145 | } 1146 | 1147 | extension Model.Event.Identifier: SummitCoreDataIdentifier { 1148 | 1149 | public typealias ManagedObject = EventManagedObject 1150 | } 1151 | 1152 | extension Model.EventType.Identifier: SummitCoreDataIdentifier { 1153 | 1154 | public typealias ManagedObject = EventTypeManagedObject 1155 | } 1156 | 1157 | extension Model.Presentation.Identifier: SummitCoreDataIdentifier { 1158 | 1159 | public typealias ManagedObject = PresentationManagedObject 1160 | } 1161 | 1162 | extension Model.Link.Identifier: SummitCoreDataIdentifier { 1163 | 1164 | public typealias ManagedObject = LinkManagedObject 1165 | } 1166 | 1167 | extension Model.Tag.Identifier: SummitCoreDataIdentifier { 1168 | 1169 | public typealias ManagedObject = TagManagedObject 1170 | } 1171 | 1172 | extension Model.Video.Identifier: SummitCoreDataIdentifier { 1173 | 1174 | public typealias ManagedObject = VideoManagedObject 1175 | } 1176 | 1177 | extension Model.Slide.Identifier: SummitCoreDataIdentifier { 1178 | 1179 | public typealias ManagedObject = SlideManagedObject 1180 | } 1181 | 1182 | extension Model.Member.Identifier: SummitCoreDataIdentifier { 1183 | 1184 | public typealias ManagedObject = MemberManagedObject 1185 | } 1186 | 1187 | extension Model.Attendee.Identifier: SummitCoreDataIdentifier { 1188 | 1189 | public typealias ManagedObject = AttendeeManagedObject 1190 | } 1191 | 1192 | extension Model.Feedback.Identifier: SummitCoreDataIdentifier { 1193 | 1194 | public typealias ManagedObject = FeedbackManagedObject 1195 | } 1196 | 1197 | extension Model.Location.Identifier: SummitCoreDataIdentifier { 1198 | 1199 | public typealias ManagedObject = LocationManagedObject 1200 | } 1201 | 1202 | // MARK: - CoreDataCodable 1203 | 1204 | extension Model.Summit: CoreDataCodable { 1205 | 1206 | public static var identifierKey: CodingKey { return CodingKeys.identifier } 1207 | } 1208 | 1209 | extension Model.WirelessNetwork: CoreDataCodable { } 1210 | extension Model.Company: CoreDataCodable { } 1211 | extension Model.Speaker: CoreDataCodable { } 1212 | extension Model.Affiliation: CoreDataCodable { } 1213 | extension Model.AffiliationOrganization: CoreDataCodable { } 1214 | extension Model.TicketType: CoreDataCodable { } 1215 | extension Model.Image: CoreDataCodable { } 1216 | extension Model.Venue: CoreDataCodable { } 1217 | extension Model.VenueRoom: CoreDataCodable { } 1218 | extension Model.VenueFloor: CoreDataCodable { } 1219 | extension Model.Track: CoreDataCodable { } 1220 | extension Model.TrackGroup: CoreDataCodable { } 1221 | extension Model.Event: CoreDataCodable { } 1222 | extension Model.EventType: CoreDataCodable { } 1223 | extension Model.Presentation: CoreDataCodable { } 1224 | extension Model.Link: CoreDataCodable { } 1225 | extension Model.Tag: CoreDataCodable { } 1226 | extension Model.Video: CoreDataCodable { } 1227 | extension Model.Slide: CoreDataCodable { } 1228 | 1229 | extension Model.Location: CoreDataCodable { 1230 | 1231 | public static var identifierKey: CodingKey { return Model.Summit.identifierKey } 1232 | 1233 | public var coreDataIdentifier: CoreDataIdentifier { return identifier } 1234 | } 1235 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import CoreDataCodableTests 3 | 4 | XCTMain([ 5 | testCase(CoreDataCodableTests.allTests), 6 | ]) 7 | --------------------------------------------------------------------------------