├── header.png
├── Framework
├── CoreDataDandy.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── CoreDataDandy.xcscheme
│ └── project.pbxproj
└── CoreDataDandy
│ ├── Info.plist
│ └── CoreDataDandy.h
├── Tests
├── CoreDataDandyTests.xcodeproj
│ ├── project.xcworkspace
│ │ └── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── xcschemes
│ │ │ └── CoreDataDandyTests.xcscheme
│ └── project.pbxproj
├── .swiftlint.yml
└── CoreDataDandyTests
│ ├── Models
│ ├── Hat+CoreDataClass.swift
│ ├── Slander+CoreDataClass.swift
│ ├── Space+CoreDataClass.swift
│ ├── Dandy_+CoreDataClass.swift
│ ├── Flattery+CoreDataClass.swift
│ ├── Gossip+CoreDataClass.swift
│ ├── Plebian+CoreDataClass.swift
│ ├── Material+CoreDataClass.swift
│ ├── Conclusion+CoreDataClass.swift
│ ├── Conclusion+Finalization.swift
│ ├── Plebian+CoreDataProperties.swift
│ ├── Slander+CoreDataProperties.swift
│ ├── Flattery+CoreDataProperties.swift
│ ├── Space+CoreDataProperties.swift
│ ├── Conclusion+CoreDataProperties.swift
│ ├── Material+CoreDataProperties.swift
│ ├── Gossip+CoreDataProperties.swift
│ ├── Dandy_+CoreDataProperties.swift
│ └── Hat+CoreDataProperties.swift
│ ├── Info.plist
│ ├── DandyModel.xcdatamodeld
│ └── DandyModel.xcdatamodel
│ │ └── contents
│ └── CoreDataDandyTests.swift
├── .travis.yml
├── .gitignore
├── CoreDataDandy.podspec
├── Sources
├── Extensions
│ ├── NSManagedObject+Dandy.swift
│ ├── NSFileManager+Directory.swift
│ ├── Dictionary+Dandy.swift
│ └── NSEntityDescription+Dandy.swift
├── Core
│ ├── MappingFinalizer.swift
│ ├── Logger.swift
│ ├── PropertyDescription.swift
│ ├── EntityMapper.swift
│ ├── PersistentStackCoordinator.swift
│ ├── Serializer.swift
│ ├── ObjectFactory.swift
│ └── CoreDataDandy.swift
├── Constants.swift
└── Value Conversion
│ ├── ValueConverter.swift
│ ├── CoreDataValueConverter.swift
│ └── ConvertibleType.swift
├── LICENSE
└── README.md
/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ml-archive/CoreDataDandy/master/header.png
--------------------------------------------------------------------------------
/Framework/CoreDataDandy.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Tests/.swiftlint.yml:
--------------------------------------------------------------------------------
1 | reporter: "xcode"
2 | line_length: 180
3 | excluded:
4 | - CoreDataDandyTests/CoreDataDandyTests.swift
5 | - CoreDataDandyTests/Conclusion+CoreDataProperties.swift
6 | disabled_rules:
7 | - trailing_newline
8 | - type_name
9 | - variable_name
10 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Hat+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Hat+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Hat)
13 | public class Hat: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Slander+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Slander+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Slander)
13 | public class Slander: Gossip {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Space+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Space+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Space)
13 | public class Space: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Dandy_+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dandy_+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Dandy_)
13 | public class Dandy_: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Flattery+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Flattery+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Flattery)
13 | public class Flattery: Gossip {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Gossip+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Gossip+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Gossip)
13 | public class Gossip: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Plebian+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Plebian+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Plebian)
13 | public class Plebian: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Material+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Material+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Material)
13 | public class Material: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Conclusion+CoreDataClass.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Conclusion+CoreDataClass.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 | @objc(Conclusion)
13 | public class Conclusion: NSManagedObject {
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | osx_image: xcode8
3 | env:
4 | global:
5 | - PROJECT=Tests/CoreDataDandyTests.xcodeproj
6 | - SCHEME=CoreDataDandyTests
7 | - DESTINATION="OS=10.0,name=iPhone 7"
8 | script:
9 | - set -o pipefail
10 | - xcodebuild -version
11 | - xcodebuild -showsdks
12 | - xcodebuild -project "$PROJECT" -scheme "$SCHEME" -destination "$DESTINATION" -sdk iphonesimulator test
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Conclusion+Finalization.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Conclusion+Finalization.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 10/31/16.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 | extension Conclusion: MappingFinalizer {
12 | public func finalizeMapping(of json: JSONObject) {
13 | if var content = content {
14 | content += "_FINALIZED"
15 | self.content = content
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # From Alamofire: https://github.com/Alamofire/Alamofire/blob/master/.gitignore#L11
2 |
3 | # Mac OS X
4 | .DS_Store
5 |
6 | # Xcode
7 | build/
8 | *.pbxuser
9 | !default.pbxuser
10 | *.mode1v3
11 | !default.mode1v3
12 | *.mode2v3
13 | !default.mode2v3
14 | *.perspectivev3
15 | !default.perspectivev3
16 | xcuserdata
17 | *.xccheckout
18 | *.moved-aside
19 | DerivedData
20 | *.hmap
21 | *.ipa
22 | *.xcuserstate
23 |
24 | # SPM
25 | .build/
26 |
27 | # Carthage
28 | Carthage/Build
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Plebian+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Plebian+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Plebian {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Plebian");
17 | }
18 |
19 | @NSManaged public var name: String?
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Slander+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Slander+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Slander {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Slander");
17 | }
18 |
19 | @NSManaged public var statement: String?
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Flattery+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Flattery+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Flattery {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Flattery");
17 | }
18 |
19 | @NSManaged public var ambition: String?
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Space+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Space+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Space {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Space");
17 | }
18 |
19 | @NSManaged public var name: String?
20 | @NSManaged public var spaceState: String?
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Conclusion+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Conclusion+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Conclusion {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Conclusion");
17 | }
18 |
19 | @NSManaged public var content: String?
20 | @NSManaged public var id: String?
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Material+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Material+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Material {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Material");
17 | }
18 |
19 | @NSManaged public var name: String?
20 | @NSManaged public var origin: String?
21 | @NSManaged public var hats: Hat?
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Gossip+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Gossip+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Gossip {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Gossip");
17 | }
18 |
19 | @NSManaged public var details: String?
20 | @NSManaged public var secret: String?
21 | @NSManaged public var topic: String?
22 | @NSManaged public var purveyor: Dandy_?
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------
/CoreDataDandy.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'CoreDataDandy'
3 | s.version = '0.6.1'
4 | s.summary = 'A feature-light wrapper around Core Data that simplifies common database operations.'
5 | s.description = 'Initializes your Core Data stack, manages your saves, inserts, and fetches, and maps json to NSManagedObjects.'
6 | s.homepage = 'https://github.com/fuzz-productions/CoreDataDandy'
7 | s.license = 'MIT'
8 | s.author = { 'Noah Blake' => 'noah@fuzzproductions.com' }
9 | s.source = { :git => "https://github.com/fuzz-productions/CoreDataDandy.git", :tag => s.version.to_s }
10 | s.social_media_url = 'https://twitter.com/fuzzpro'
11 |
12 | s.platform = :ios, '9.0'
13 | s.requires_arc = true
14 |
15 | s.source_files = 'Sources/**/*'
16 |
17 | s.frameworks = 'CoreData'
18 | end
19 |
--------------------------------------------------------------------------------
/Framework/CoreDataDandy/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 0.6.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Sources/Extensions/NSManagedObject+Dandy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSManagedObject+Dandy.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 10/31/16.
6 | //
7 | //
8 |
9 | import CoreData
10 |
11 | extension NSManagedObject {
12 | public class func entityDescription() -> NSEntityDescription? {
13 | return NSEntityDescription.entity(forEntityName: String(describing: self),
14 | in: Dandy.coordinator.mainContext)
15 | }
16 |
17 | public class func inserted() -> NSManagedObject? {
18 | if #available(iOS 10.0, *) {
19 | return self.init(context: Dandy.coordinator.mainContext)
20 | } else {
21 | if let description = entityDescription() {
22 | return NSManagedObject(entity: description,
23 | insertInto: Dandy.coordinator.mainContext)
24 | }
25 | }
26 |
27 | return nil
28 | }
29 |
30 | public class func type(named className: String) -> NSManagedObject.Type? {
31 | return NSClassFromString(className) as? NSManagedObject.Type
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This code is distributed under the terms and conditions of the MIT license.
2 |
3 | Copyright (c) 2015-2016 Fuzz Productions, LLC.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/Sources/Core/MappingFinalizer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MappingFinalizer.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 2/5/16.
6 | // Copyright © 2016 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | public protocol MappingFinalizer {
29 | func finalizeMapping(of json: JSONObject)
30 | }
31 |
--------------------------------------------------------------------------------
/Sources/Extensions/NSFileManager+Directory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSFileManager+Directory.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 4/28/16.
6 | //
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | extension FileManager {
13 | /// The url of the NSFileManager's `.DocumentDirectory`
14 | static var documentDirectoryURL: URL {
15 | let urls = `default`.urls(for: .documentDirectory, in: .userDomainMask)
16 |
17 | if let url = urls.last {
18 | return url
19 | }
20 |
21 | preconditionFailure("Failed to find or a Documents directory.")
22 | }
23 |
24 | /// Returns whether a directory exists at a given file url.
25 | ///
26 | /// - parameter url: The file url of the directory. For instance "file://root/dandy/documents"
27 | ///
28 | /// - returns: Whether a directory exists at the given url. Note that false may indicate either that
29 | /// a file exists at this url or that nothing exists at this directory.
30 | static func directoryExists(at url: URL) -> Bool {
31 | let path = url.pathComponents.joined(separator: "/")
32 | return `default`.fileExists(atPath: path)
33 | }
34 |
35 | /// Creates a directory at a given URL.
36 | ///
37 | /// - parameter url: The file url of the directory to create.
38 | ///
39 | /// - throws: A number of errors may lead to an exception here. The two most common exceptions are raised
40 | /// when a directory already exists at this url or in response to insufficient user permissions.
41 | static func createDirectory(at url: URL) throws {
42 | try `default`.createDirectory(at: url, withIntermediateDirectories: false, attributes: nil)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Framework/CoreDataDandy/CoreDataDandy.h:
--------------------------------------------------------------------------------
1 | //
2 | // CoreDataDandy.h
3 | // CoreDataDandy
4 | //
5 | // Created by Noah Blake on 2/2/16.
6 | // Copyright © 2016 Fuzz Productions. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | #import
29 |
30 | //! Project version number for CoreDataDandy.
31 | FOUNDATION_EXPORT double CoreDataDandyVersionNumber;
32 |
33 | //! Project version string for CoreDataDandy.
34 | FOUNDATION_EXPORT const unsigned char CoreDataDandyVersionString[];
35 |
36 |
37 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Dandy_+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dandy_+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Dandy_ {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Dandy_");
17 | }
18 |
19 | @NSManaged public var bio: String?
20 | @NSManaged public var dandyID: String?
21 | @NSManaged public var dateOfBirth: NSDate?
22 | @NSManaged public var name: String?
23 | @NSManaged public var gossip: NSSet?
24 | @NSManaged public var hats: NSSet?
25 | @NSManaged public var predecessor: Dandy_?
26 | @NSManaged public var successor: Dandy_?
27 |
28 | }
29 |
30 | // MARK: Generated accessors for gossip
31 | extension Dandy_ {
32 |
33 | @objc(addGossipObject:)
34 | @NSManaged public func addToGossip(_ value: Gossip)
35 |
36 | @objc(removeGossipObject:)
37 | @NSManaged public func removeFromGossip(_ value: Gossip)
38 |
39 | @objc(addGossip:)
40 | @NSManaged public func addToGossip(_ values: NSSet)
41 |
42 | @objc(removeGossip:)
43 | @NSManaged public func removeFromGossip(_ values: NSSet)
44 |
45 | }
46 |
47 | // MARK: Generated accessors for hats
48 | extension Dandy_ {
49 |
50 | @objc(addHatsObject:)
51 | @NSManaged public func addToHats(_ value: Hat)
52 |
53 | @objc(removeHatsObject:)
54 | @NSManaged public func removeFromHats(_ value: Hat)
55 |
56 | @objc(addHats:)
57 | @NSManaged public func addToHats(_ values: NSSet)
58 |
59 | @objc(removeHats:)
60 | @NSManaged public func removeFromHats(_ values: NSSet)
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/Models/Hat+CoreDataProperties.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Hat+CoreDataProperties.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 11/10/16.
6 | //
7 | //
8 |
9 | import Foundation
10 | import CoreData
11 |
12 |
13 | extension Hat {
14 |
15 | @nonobjc public class func fetchRequest() -> NSFetchRequest {
16 | return NSFetchRequest(entityName: "Hat");
17 | }
18 |
19 | @NSManaged public var name: String?
20 | @NSManaged public var styleDescription: String?
21 | @NSManaged public var dandies: NSOrderedSet?
22 | @NSManaged public var primaryMaterial: Material?
23 |
24 | }
25 |
26 | // MARK: Generated accessors for dandies
27 | extension Hat {
28 |
29 | @objc(insertObject:inDandiesAtIndex:)
30 | @NSManaged public func insertIntoDandies(_ value: Dandy_, at idx: Int)
31 |
32 | @objc(removeObjectFromDandiesAtIndex:)
33 | @NSManaged public func removeFromDandies(at idx: Int)
34 |
35 | @objc(insertDandies:atIndexes:)
36 | @NSManaged public func insertIntoDandies(_ values: [Dandy_], at indexes: NSIndexSet)
37 |
38 | @objc(removeDandiesAtIndexes:)
39 | @NSManaged public func removeFromDandies(at indexes: NSIndexSet)
40 |
41 | @objc(replaceObjectInDandiesAtIndex:withObject:)
42 | @NSManaged public func replaceDandies(at idx: Int, with value: Dandy_)
43 |
44 | @objc(replaceDandiesAtIndexes:withDandies:)
45 | @NSManaged public func replaceDandies(at indexes: NSIndexSet, with values: [Dandy_])
46 |
47 | @objc(addDandiesObject:)
48 | @NSManaged public func addToDandies(_ value: Dandy_)
49 |
50 | @objc(removeDandiesObject:)
51 | @NSManaged public func removeFromDandies(_ value: Dandy_)
52 |
53 | @objc(addDandies:)
54 | @NSManaged public func addToDandies(_ values: NSOrderedSet)
55 |
56 | @objc(removeDandies:)
57 | @NSManaged public func removeFromDandies(_ values: NSOrderedSet)
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/Sources/Core/Logger.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Logger.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 1/24/16.
6 | // Copyright © 2016 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 |
29 | import Foundation
30 |
31 | // MARK: - Warnings -
32 | func log(_ message: @autoclosure () -> String, filename: String = #file, function: String = #function, line: Int = #line) {
33 | #if DEBUG
34 | NSLog("[\(NSString(string: filename).lastPathComponent):\(line)] \(function) - %@", message())
35 | #endif
36 | }
37 |
38 | func format(_ message: String, with error: NSError? = nil) -> String {
39 | #if DEBUG
40 | var errorDescription = ""
41 | if let error = error {
42 | errorDescription = " Error:\n" + error.description
43 | }
44 | let warning = "(CoreDataDandy) warning: " + message + errorDescription
45 | return warning
46 | #else
47 | return ""
48 | #endif
49 | }
50 |
--------------------------------------------------------------------------------
/Sources/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // CoreDataDandy
4 | //
5 | // Created by Noah Blake on 6/20/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import Foundation
29 |
30 | // MARK: - Error constants -
31 | let DandyErrorDomain = "CoreDataDandyDomain"
32 |
33 | // MARK: - xcdatamodel decorations -
34 | // Inserted into the userInfo of an entity to mark its primaryKey
35 | let PRIMARY_KEY = "@primaryKey"
36 | // A special primaryKey for identifying a unique entity in the database. Multiple instances of this entity will not be produced.
37 | let SINGLETON = "@singleton"
38 | // Marks the mapping value in a userInfo used to read json into a property. For instance, if the json value of interest is expected
39 | // to be keyed as "id" for a property named "dandyID," specify "@mapping":"id" in dandyID's userInfo.
40 | let MAPPING = "@mapping"
41 | // An @mapping keyword used to turn off Dandy mapping for a given property.
42 | let NO_MAPPING = "@false"
43 |
44 | let CACHED_MAPPING_LOCATION = "EntityMapper_EntityMapper"
45 |
46 | public typealias JSONObject = [String: Any]
47 |
--------------------------------------------------------------------------------
/Sources/Extensions/Dictionary+Dandy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Dictionary+Dandy.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 1/22/16.
6 | // Copyright © 2016 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import Foundation
29 |
30 |
31 | public extension Dictionary {
32 | /// Convenience function for adding values from one dictionary to another, like
33 | /// `NSMutableDictionary`'s `-addEntriesFromDictionary`
34 | public mutating func addEntriesFrom(_ dictionary: Dictionary) {
35 | for (key, value) in dictionary {
36 | self[key] = value
37 | }
38 | }
39 | }
40 |
41 | /// Functions similarly to `NSDictionary's` valueForKeyPath.
42 | ///
43 | /// - parameter keypath: The keypath of the value.
44 | /// - parameter dictionary: The dictionary in which the value may exist.
45 | ///
46 | /// - returns: The value at the given keypath is one exists.
47 | /// If no key exists at the specified keypath, nil is returned.
48 | func _value(at keypath: String,
49 | of dictionary: JSONObject) -> T? {
50 | let keys = keypath.components(separatedBy: ".")
51 | var copy = dictionary
52 | var possibleValue: Any?
53 | for key in keys {
54 | possibleValue = copy[key] ?? nil
55 | if let value = copy[key] as? JSONObject {
56 | copy = value
57 | } else if possibleValue is NSNull {
58 | // A null has been encountered in the dictionary. Return it, and ignore further potential nesting.
59 | return possibleValue as? T
60 | }
61 | }
62 |
63 | return possibleValue as? T
64 | }
65 |
--------------------------------------------------------------------------------
/Framework/CoreDataDandy.xcodeproj/xcshareddata/xcschemes/CoreDataDandy.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Sources/Value Conversion/ValueConverter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TypeConverters.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 10/26/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import Foundation
29 |
30 | /// Adopters of this protocol provide implementations of `convert(_:)` so that all type conversions may proceed
31 | /// via a consistent interface.
32 | protocol ValueConverter {
33 | /// Attempts to convert a value from one type to another. Note that the value must be castable to a specified type
34 | /// for the conversion to succeed.
35 | func convert(_ value: Any) -> Any?
36 | /// A convenience function for checking type conformance before attempting type conversion.
37 | func convert(_ value: Any, to type: T.Type, using converter: (T) -> Any?) -> Any?
38 | }
39 | extension ValueConverter {
40 | func convert(_ value: Any, to type: T.Type, using converter: (T) -> Any?) -> Any? {
41 | if let value = value as? T {
42 | return converter(value)
43 | }
44 | return nil
45 | }
46 | }
47 |
48 | struct BooleanConverter: ValueConverter {
49 | func convert(_ value: Any) -> Any? {
50 | return convert(value, to: BooleanConvertible.self) { $0.convertToBoolean() }
51 | }
52 | }
53 |
54 | struct DateConverter: ValueConverter {
55 | func convert(_ value: Any) -> Any? {
56 | return convert(value, to: DateConvertible.self) { $0.convertToDate() }
57 | }
58 | }
59 |
60 | struct DataConverter: ValueConverter {
61 | func convert(_ value: Any) -> Any? {
62 | return convert(value, to: DataConvertible.self) { $0.convertToData() }
63 | }
64 | }
65 |
66 | struct DoubleConverter: ValueConverter {
67 | func convert(_ value: Any) -> Any? {
68 | return convert(value, to: DoubleConvertible.self) { $0.convertToDouble() }
69 | }
70 | }
71 |
72 | struct DecimalConverter: ValueConverter {
73 | func convert(_ value: Any) -> Any? {
74 | return convert(value, to: DecimalConvertible.self) { $0.convertToDecimal() }
75 | }
76 | }
77 |
78 | struct FloatConverter: ValueConverter {
79 | func convert(_ value: Any) -> Any? {
80 | return convert(value, to: FloatConvertible.self) { $0.convertToFloat() }
81 | }
82 | }
83 |
84 | struct IntConverter: ValueConverter {
85 | func convert(_ value: Any) -> Any? {
86 | return convert(value, to: IntConvertible.self) { $0.convertToInt() }
87 | }
88 | }
89 |
90 | struct StringConverter: ValueConverter {
91 | func convert(_ value: Any) -> Any? {
92 | return convert(value, to: StringConvertible.self) { $0.convertToString() }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests.xcodeproj/xcshareddata/xcschemes/CoreDataDandyTests.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
34 |
40 |
41 |
42 |
43 |
44 |
50 |
51 |
52 |
53 |
54 |
55 |
65 |
66 |
72 |
73 |
74 |
75 |
76 |
77 |
83 |
84 |
90 |
91 |
92 |
93 |
95 |
96 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/Sources/Value Conversion/CoreDataValueConverter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TypeConverters.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 10/26/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import CoreData
29 |
30 | /// The central type conversion class.
31 | ///
32 | /// `CoreDataValueConverter` compares a given value to a given entity's property type, then attempts to convert the value
33 | /// to the property type. This class ensures that values written to the NSManagedObject are always of the appropriate
34 | /// tyope.
35 | ///
36 | /// The actual conversion is conducted by an appropriate `ValueConverter`.
37 | public struct CoreDataValueConverter {
38 | /// A shared dateFormatter for regularly converting strings of a known pattern
39 | /// to dates and vice-versa.
40 | public static let dateFormatter = DateFormatter()
41 |
42 | /// Maps `NSAttributeTypes` to their corresponding type converters.
43 | fileprivate static let typeConverters: [NSAttributeType: ValueConverter] = [
44 | .integer16AttributeType: IntConverter(),
45 | .integer32AttributeType: IntConverter(),
46 | .integer64AttributeType: IntConverter(),
47 | .decimalAttributeType: DecimalConverter(),
48 | .doubleAttributeType: DoubleConverter(),
49 | .floatAttributeType: FloatConverter(),
50 | .stringAttributeType: StringConverter(),
51 | .booleanAttributeType: BooleanConverter(),
52 | .dateAttributeType: DateConverter(),
53 | .binaryDataAttributeType: DataConverter()
54 | ]
55 |
56 | /// Attempts to convert a given value to a type matching the specified entity property type. For instance,
57 | /// if "3" is passed but the specified entity's property is defined as an NSNumber, @3 will be returned.
58 | ///
59 | /// For a list of supported `value` types, see `ConvertibleType`
60 | ///
61 | /// - parameter value: The value to convert.
62 | /// - parameter entity: The entity this value is expected to map to.
63 | /// - parameter property: The property on the entity where this value will be written.
64 | ///
65 | /// - returns: If the conversion was successful, the converted value. Otherwise, nil.
66 | public static func convert(_ value: Any,
67 | for entity: NSEntityDescription,
68 | property: String) -> Any? {
69 | if let attributeDescription = entity.propertiesByName[property] as? NSAttributeDescription {
70 | return convert(value, to: attributeDescription.attributeType)
71 | }
72 | return nil
73 | }
74 |
75 | /// The class's central function. Attempts to convert values from one type to another.
76 | /// In general, this method is invoked indirectly via convertValue:forEntity:property
77 | ///
78 | /// - parameter value: The value to convert.
79 | /// - parameter type: The desired end type of the value.
80 | ///
81 | /// - returns: If the conversion was successful, the converted value. Otherwise, nil.
82 | public static func convert(_ value: Any,
83 | to type: NSAttributeType) -> Any? {
84 | if let converter = typeConverters[type] {
85 | return converter.convert(value)
86 | }
87 | return nil
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | [](https://travis-ci.org/fuzz-productions/CoreDataDandy)
4 | [](https://github.com/fuzz-productions/CoreDataDandy)
5 | [](https://cocoapods.org/pods/CoreDataDandy)
6 | [](https://github.com/fuzz-productions/CoreDataDandy/blob/master/LICENSE)
7 |
8 | ## Introduction
9 | Core Data Dandy is a feature-light wrapper around Core Data that simplifies common database operations.
10 |
11 | ## Feature summary
12 |
13 | * Initializes and maintains a Core Data stack.
14 | * Provides convenience methods for saves, inserts, fetches, and deletes.
15 | * Maps json into NSManagedObjects via a lightweight API.
16 | * Deserializes NSManagedObjects into json
17 |
18 | ## Installation
19 |
20 | ### Carthage
21 |
22 | ```
23 | github "fuzz-productions/CoreDataDandy" ~> 0.6.1
24 | ```
25 |
26 | ### CocoaPods
27 |
28 | ```
29 | pod 'CoreDataDandy', '0.6.1'
30 | ```
31 |
32 | ## Usage
33 |
34 | All standard usage of Core Data Dandy should flow through CoreDataDandy's sharedDandy. More advanced users, however, may find its various components useful in isolation.
35 |
36 | ### Bootstrapping
37 | ```swift
38 | CoreDataDandy.wake("ModelName")
39 | ```
40 |
41 | ### Saving and deleting
42 |
43 | Save with or without a closure.
44 |
45 | ```swift
46 | Dandy.save()
47 | Dandy.save { error in
48 | // Respond to save completion.
49 | }
50 | ```
51 |
52 | Delete with or without a closure.
53 |
54 | ```swift
55 | Dandy.delete(object)
56 | Dandy.delete(object) {
57 | // Respond to deletion completion.
58 | }
59 | ```
60 |
61 | Destroy the contents of the database. Called, for example, to recover from a failure to perform a migration.
62 |
63 | ```swift
64 | Dandy.tearDown()
65 | ```
66 |
67 | ### Fetching
68 |
69 | Fetch all objects of a given type.
70 |
71 | ```swift
72 | Dandy.fetch(Gossip.self)
73 | ```
74 |
75 | Fetch an object corresponding to an entity and primaryKey value.
76 |
77 | ```swift
78 | Dandy.fetchUnique(Hat.self, identifiedBy: "bowler")
79 | ```
80 |
81 | Fetch an array of objects filtered by a predicate.
82 |
83 | ```swift
84 | Dandy.fetch(Gossip.self, filterBy: NSPredicate(format: "topic == %@", "John Keats"))
85 | ```
86 |
87 | ### Insertions and updates
88 |
89 | Insert object of a given type.
90 |
91 | ```swift
92 | Dandy.insert(Gossip.self)
93 | ```
94 |
95 | Insert or fetch a unique a object from a primary key.
96 |
97 | ```swift
98 | Dandy.insertUnique(Slander.self, identifiedBy: "WILDE")
99 | ```
100 |
101 | Upsert a unique object, or insert and update a non-unique object.
102 |
103 | ```swift
104 | Dandy.upsert(Gossip.self, from: json)
105 | ```
106 |
107 | Upsert an array of unique objects, or insert and update non-unique objects.
108 |
109 | ```swift
110 | Dandy.batchUpsert(Gossip.self, from: json)
111 | ```
112 |
113 | ### Mapping finalization
114 |
115 | Objects requiring custom mapping finalization should adopt the `MappingFinalizer` protocol. The protocol has a single function, `finalizeMapping(_:)`.
116 |
117 | ```swift
118 | extension Conclusion: MappingFinalizer {
119 | func finalizeMapping(of json: [String : AnyObject]) {
120 | if var content = content {
121 | content += "_FINALIZED"
122 | self.content = content
123 | }
124 | }
125 | }
126 | ```
127 |
128 | ### Serialization
129 |
130 | Serialize a single object.
131 |
132 | ```swift
133 | Serializer.serialize(gossip)
134 | ```
135 |
136 | Serialize an array of objects.
137 |
138 | ```swift
139 | Serializer.serialize([byron, wilde, andre3000])
140 | ```
141 |
142 | Serialize an object and its relationships.
143 |
144 | ```swift
145 | Serializer.serialize(gossip, including: ["purveyor"])
146 | ```
147 |
148 | Serialize an object and its nested relationships.
149 |
150 | ```swift
151 | Serializer.serialize(gossip, including: ["purveyor.hats.material, purveyor.predecessor"])
152 | ```
153 |
154 | ## xcdatamodel decorations
155 |
156 | CoreDataDandy supports four xcdatamodel attributes. All decorations are declared and documented in DandyConstants.
157 |
158 | **@primaryKey**
159 |
160 | Add this decoration to the entity's userInfo to specify which property on the entity functions as its primaryKey.
161 |
162 | **@mapping**
163 |
164 | Add this decoration to a property to specify an alternate mapping for this property. For instance, if a property is named "abbreviatedState," but the json value for this property is found at the key "state," add @mapping : state to the abbreviatedState's userInfo.
165 |
166 | **@false**
167 |
168 | Use this decoration in conjunction with the @mapping keyword to disable mapping to the property. For instance, if your entity has an attribute named "secret" that you'd prefer to map yourself, add @mapping : @false to secret's userInfo.
169 |
170 | **@singleton**
171 |
172 | Add this decoration to an entity's userInfo if there should never be more than one instance of this entity in your database. This decoration may be useful for objects like Tokens and CurrentUsers, though it's primarily included to suggest the kind of decorations that may be added in the future.
173 |
174 | ## Warnings
175 |
176 | To receive console warnings in Swift projects, add the entry -D DEBUG in your project's build settings under Swift Compiler - Custom Flags.
177 |
--------------------------------------------------------------------------------
/Sources/Core/PropertyDescription.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PropertyDescription.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 7/3/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import CoreData
29 |
30 | /// The type of a `PropertyDescription`.
31 | ///
32 | /// - Unknown: An unknown property description type. Generally indicative of an improperly instantiated `PropertyDescription.`
33 | /// - Attribute: Marks a property description corresponding to an attribute.
34 | /// - Relationship: Marks a property description corresponding to an relationship.
35 | enum PropertyType: Int {
36 | case unknown = 0
37 | case attribute
38 | case relationship
39 | }
40 |
41 | /// `PropertyDescription` provides a convenient means of accessing information necessary to map json into an
42 | /// NSManagedObject. It encapsulates values found in `NSAttributeDescriptions` and `NSRelationshipDescriptions`, such as
43 | /// an attribute's type or if a relationship is ordered or not.
44 | final class PropertyDescription: NSObject {
45 | /// The name of of the property.
46 | var name = String()
47 |
48 | /// The property type: .Unknown, .Attribute, or .Relationship.
49 | var type = PropertyType.unknown
50 |
51 | /// The type of an attribute.
52 | var attributeType: NSAttributeType?
53 |
54 | /// A boolean indicating whether the property is optional or not.
55 | var optional = true
56 |
57 | /// The entity description of a relationship.
58 | var destinationEntity: NSEntityDescription?
59 |
60 | /// A boolean describing if a relationship is ordered or not. By default, false.
61 | var ordered = false
62 |
63 | /// A boolean describing if a relationship is toMany or not. By default, false.
64 | var toMany = false
65 |
66 | override init() { super.init() }
67 |
68 | /// An initializer that builds a `PropertyDescription` from either an `NSAttributeDescription` or an
69 | /// `NSRelationshipDescription`.
70 | ///
71 | /// Note that a default object without meaningful values will be returned if neither of the above is
72 | convenience init(description: Any) {
73 | if let description = description as? NSAttributeDescription {
74 | self.init(attributeDescription: description)
75 | } else if let description = description as? NSRelationshipDescription {
76 | self.init(relationshipDescription: description)
77 | } else {
78 | self.init()
79 | log(format("Unknown property type for description: \(description)"))
80 | }
81 | }
82 |
83 | /// An initializer that builds a `PropertyDescription` by extracting relevant values from an `NSAttributeDescription`.
84 | fileprivate init(attributeDescription: NSAttributeDescription) {
85 | name = attributeDescription.name
86 | type = PropertyType.attribute
87 | attributeType = attributeDescription.attributeType
88 | optional = attributeDescription.isOptional
89 | super.init()
90 | }
91 |
92 | /// An initializer that builds a `PropertyDescription` by extracting relevant values from an `NSRelationshipDescription`.
93 | fileprivate init(relationshipDescription: NSRelationshipDescription) {
94 | name = relationshipDescription.name
95 | type = PropertyType.relationship
96 | destinationEntity = relationshipDescription.destinationEntity
97 | ordered = relationshipDescription.isOrdered
98 | toMany = relationshipDescription.isToMany
99 | optional = relationshipDescription.isOptional
100 | super.init()
101 | }
102 | }
103 |
104 | // MARK: - -
105 | extension PropertyDescription : NSCoding {
106 | convenience init?(coder aDecoder: NSCoder) {
107 | self.init()
108 | if let n = aDecoder.decodeObject(forKey: "name") as? String {
109 | name = n
110 | }
111 | type = PropertyType(rawValue: aDecoder.decodeInteger(forKey: "type"))!
112 | attributeType = NSAttributeType(rawValue: UInt(aDecoder.decodeInteger(forKey: "attributeType")))!
113 | ordered = aDecoder.decodeBool(forKey: "ordered")
114 | toMany = aDecoder.decodeBool(forKey: "toMany")
115 | }
116 |
117 | @objc func encode(with aCoder: NSCoder) {
118 | aCoder.encode(name, forKey:"name")
119 | aCoder.encode(type.rawValue, forKey:"type")
120 | if let attributeType = attributeType {
121 | aCoder.encode(Int(attributeType.rawValue), forKey:"attributeType")
122 | }
123 | if let destinationEntity = destinationEntity {
124 | aCoder.encode(destinationEntity, forKey:"destinationEntity")
125 | }
126 | aCoder.encode(ordered, forKey: "ordered")
127 | aCoder.encode(toMany, forKey: "toMany")
128 | }
129 |
130 | override var hashValue: Int {
131 | get {
132 | return "\(name)_\(type)_\(attributeType)_\(destinationEntity)_\(ordered)_\(toMany)".hashValue
133 | }
134 | }
135 | }
136 |
137 | // MARK: - Equality -
138 | extension PropertyDescription {
139 | /// Compares the hashValue of two `PropertyDescription` objects. `PropertyDescription` objects are never considered
140 | /// equal to other types.
141 | override func isEqual(_ object: Any?) -> Bool {
142 | if let object = object as? PropertyDescription {
143 | return hashValue == object.hashValue
144 | } else {
145 | return false
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Sources/Extensions/NSEntityDescription+Dandy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSEntity+Dandy.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 1/22/16.
6 | // Copyright © 2016 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import CoreData
29 |
30 | // MARK: - NSEntityDescription+UserInfo -
31 | extension NSEntityDescription {
32 | /// Recursively collects all userInfo values from potential superentities
33 | var allUserInfo: [AnyHashable: Any]? {
34 | get {
35 | return collectedEntityValuesFromDictionaryClosure({return $0.userInfo as? JSONObject})
36 | }
37 | }
38 |
39 | /// Recursively collects all attribute values from potential superentities
40 | var allAttributes: [String: NSAttributeDescription]? {
41 | get {
42 | return collectedEntityValuesFromDictionaryClosure({return $0.attributesByName}) as? [String: NSAttributeDescription]
43 | }
44 | }
45 |
46 | /// Recursively collects all relationship values from potential superentities
47 | var allRelationships: [String: NSRelationshipDescription]? {
48 | get {
49 | return collectedEntityValuesFromDictionaryClosure({return $0.relationshipsByName}) as? [String: NSRelationshipDescription]
50 | }
51 | }
52 |
53 | /// Recursively collects arbitrary values from potential superentities. This function contains the boilerplate
54 | /// required for collecting userInfo, attributesByName, and relationshipsByName.
55 | ///
56 | /// - parameter dictionaryClosure: A closure returning userInfo, attributesByName, or relationshipsByName.
57 | ///
58 | /// - returns: The values collected from the entity's hierarchy.
59 | fileprivate func collectedEntityValuesFromDictionaryClosure(_ dictionaryClosure: (NSEntityDescription) -> JSONObject?) -> JSONObject? {
60 | var values = JSONObject()
61 | // Collect values down the entity hierarchy, stopping on the current entity.
62 | // This approach ensures children override parent values.
63 | for entity in entityHierarchy {
64 | if let newValues = dictionaryClosure(entity) {
65 | values.addEntriesFrom(newValues)
66 | }
67 | }
68 | return values
69 | }
70 |
71 | /// - returns: The entity's hierarchy, sorted by "superiority". The most super entity will be the first element
72 | /// in the array, the current entity will be the last.
73 | fileprivate var entityHierarchy: [NSEntityDescription] {
74 | get {
75 | var entities = [NSEntityDescription]()
76 | var entity: NSEntityDescription? = self
77 | while let currentEntity = entity {
78 | entities.insert(currentEntity, at: 0)
79 | entity = entity?.superentity
80 | }
81 | return entities
82 | }
83 | }
84 | }
85 |
86 |
87 | // MARK: - NSEntityDescription+Construction -
88 | extension NSEntityDescription {
89 | class func forEntity(_ name: String) -> NSEntityDescription? {
90 | return NSEntityDescription.entity(forEntityName: name, in: Dandy.coordinator.mainContext)
91 | }
92 |
93 | class func forType(_ type: T.Type) -> NSEntityDescription? {
94 | return forEntity(String(describing: type))
95 | }
96 | }
97 |
98 | // MARK: - NSEntityDescription+PrimaryKey -
99 | extension NSEntityDescription {
100 | /// Returns the primary key of of the `NSEntityDescription`, a value used to ensure a unique record
101 | /// for this entity.
102 | ///
103 | /// - returns: The property on the entity marked as a unique constraint or as its primaryKey if either is found.
104 | /// Otherwise, nil.
105 | var primaryKey: String? {
106 | get {
107 | return allUserInfo?[PRIMARY_KEY] as? String
108 | }
109 | }
110 |
111 | /// Returns whether the entity is unique or not.
112 | ///
113 | /// - returns: If the entity has either one unique constraint or an identified primaryKey attribute, true.
114 | /// Otherwise, false.
115 | var isUnique: Bool {
116 | get {
117 | return primaryKey != nil
118 | }
119 | }
120 |
121 | /// Extracts the value of a primary key from the passed in json if one can be extracted. Takes alternate mappings
122 | /// for the primaryKey into account.
123 | ///
124 | /// - parameter json: JSON form which a primaryKey will be extracted
125 | func primaryKeyValueFromJSON(_ json: JSONObject) -> Any? {
126 | if let primaryKey = primaryKey,
127 | let entityMap = EntityMapper.map(self) {
128 | let filteredMap = entityMap.filter({ $1.name == primaryKey }).map({ $0.0 })
129 | // If the primary key has an alternate mapping, return the value from the alternate mapping.
130 | // Otherwise, return the json value matching the name of the primary key.
131 | if let mappedKey = filteredMap.first {
132 | return json[mappedKey] ?? nil
133 | }
134 |
135 | return json[primaryKey] ?? nil
136 | }
137 | return nil
138 | }
139 |
140 | /// Generates a predicate to fetch for the entity with a given primary key value
141 | ///
142 | /// - parameter primaryKeyValue: The value of the entity's primary key
143 | /// - returns: A predicate that may be used to fetch unique objects
144 | func primaryKeyPredicate(for primaryKeyValue: Any) -> NSPredicate? {
145 | if let primaryKey = primaryKey,
146 | let value: Any = CoreDataValueConverter.convert(primaryKeyValue, for: self, property: primaryKey) {
147 | return NSPredicate(format: "%K = %@", argumentArray: [primaryKey, value])
148 | }
149 | return nil
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/Sources/Core/EntityMapper.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EntityMapper.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 6/21/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import CoreData
29 |
30 | struct EntityMapper {
31 | // MARK: - Entity Mapping -
32 | /// The primary function of this class. Creates, caches, and returns mappings that are subsequently
33 | /// used to read json into an instance of an entity.
34 | ///
35 | /// - parameter entity: The `NSEntityDescription` to map
36 | ///
37 | /// - returns: A mapping used to read json into the specified entity.
38 | @discardableResult static func map(_ entity: NSEntityDescription) -> [String: PropertyDescription]? {
39 | // Search for a cached entity map
40 | if let entityName = entity.name {
41 | // A mapping has already been created for this entity. Return it.
42 | if let map = cachedEntityMap[entityName] {
43 | return map
44 | } else {
45 | // A mapping has not been created for this entity. Create it, cache it, and return it.
46 | var map = [String: PropertyDescription]()
47 |
48 | // Map attributes
49 | if let attributes = entity.allAttributes {
50 | add(attributes, to: &map)
51 | }
52 |
53 | // Map relationships
54 | if let relationships = entity.allRelationships {
55 | add(relationships, to: &map)
56 | }
57 |
58 | archive(map, forEntity:entityName)
59 | return map
60 | }
61 | } else {
62 | log(format("Entity Name is nil for Entity " + entity.description + ". No mapping will be returned"))
63 | }
64 | return nil
65 | }
66 |
67 | /// A convenience function for producing mapped values of an entity's attributes relationships.
68 | ///
69 | /// - parameter dictionary: A dictionary containing either NSAttributeDescriptions or NSRelationshipDescriptions
70 | /// - parameter map: The map for reading json into an entity
71 | fileprivate static func add(_ dictionary: JSONObject, to map: inout [String: PropertyDescription]) {
72 | for (name, description) in dictionary {
73 | if let propertyDescription = description as? NSPropertyDescription,
74 | let newMapping = mappingForUserInfo(propertyDescription.userInfo) {
75 | // Do not add values specified as non-mapping to the mapping dictionary
76 | if newMapping != NO_MAPPING {
77 | map[newMapping] = PropertyDescription(description: description)
78 | }
79 | } else {
80 | map[name] = PropertyDescription(description: description)
81 | }
82 | }
83 | }
84 |
85 | /// Returns any mapping values found in a userInfo dictionary.
86 | ///
87 | /// - parameter userInfo: The userInfo of an `NSEntityDescription`, `NSAttributeDescription`, or `NSRelationshipDescription`
88 | ///
89 | /// - returns: A mapping value if one was found. Otherwise, nil.
90 | static func mappingForUserInfo(_ userInfo: [AnyHashable: Any]?) -> String? {
91 | if let userInfo = userInfo as? [String: String],
92 | let mapping = userInfo[MAPPING] {
93 | return mapping
94 | }
95 |
96 | return nil
97 | }
98 | }
99 |
100 | // MARK: - EntityMapper+Caching -
101 | extension EntityMapper {
102 | /// A lazy, nillable reference to a cached map.
103 | fileprivate static var _cachedEntityMap: [String: [String: PropertyDescription]]?
104 | /// A dictionary containing mappings in the following structure: ['entityName': 'map'].
105 | static var cachedEntityMap: [String: [String: PropertyDescription]] {
106 | get {
107 | if _cachedEntityMap == nil {
108 | if let archivedMap = NSKeyedUnarchiver.unarchiveObject(withFile: self.entityMapFilePath) as? [String: [String: PropertyDescription]] {
109 | _cachedEntityMap = archivedMap
110 | } else {
111 | _cachedEntityMap = [String: [String: PropertyDescription]]()
112 | }
113 | }
114 | return _cachedEntityMap!
115 | }
116 |
117 | set {
118 | _cachedEntityMap = newValue
119 | }
120 | }
121 |
122 | /// - returns: The file path where the entityMap is archived.
123 | fileprivate static var entityMapFilePath: String = {
124 | let pathArray = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
125 | let documentPath = pathArray.first!
126 | return NSString(string: documentPath).appendingPathComponent(CACHED_MAPPING_LOCATION)
127 | }()
128 |
129 | /// Archives an entity's mapping. Note, this mapping, will be saved to the `cachedEntityMap` at the key
130 | /// of the forEntity parameter.
131 | ///
132 | /// - parameter map: A mapping for reading json into an entity.
133 | /// - parameter forEntity: The name of the entity `map` corresponds to.
134 | fileprivate static func archive(_ map: [String: PropertyDescription], forEntity entity: String) {
135 | cachedEntityMap[entity] = map
136 | NSKeyedArchiver.archiveRootObject(cachedEntityMap, toFile: entityMapFilePath)
137 | }
138 |
139 | /// Clears cached mappings.
140 | ///
141 | /// This method should be invoked when the database is undergoing a migration or any other time
142 | /// where the cached entity mappings may be invalidated..
143 | static func clearCache() {
144 | _cachedEntityMap = nil
145 | if FileManager.default.fileExists(atPath: entityMapFilePath) {
146 | do {
147 | try FileManager.default.removeItem(atPath: entityMapFilePath)
148 | } catch {
149 | log(format("Failure to remove entity map from cache"))
150 | }
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/DandyModel.xcdatamodeld/DandyModel.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 |
--------------------------------------------------------------------------------
/Sources/Core/PersistentStackCoordinator.swift:
--------------------------------------------------------------------------------
1 |
2 | //
3 | // PersistentStackCoordinator.swift
4 | // CoreDataDandy
5 | //
6 | // Created by Noah Blake on 6/20/15.
7 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
8 | //
9 | // This code is distributed under the terms and conditions of the MIT license.
10 | //
11 | // Permission is hereby granted, free of charge, to any person obtaining a copy
12 | // of this software and associated documentation files (the "Software"), to
13 | // deal in the Software without restriction, including without limitation the
14 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
15 | // sell copies of the Software, and to permit persons to whom the Software is
16 | // furnished to do so, subject to the following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be included in
19 | // all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
27 | // IN THE SOFTWARE.
28 |
29 | import CoreData
30 |
31 | /// The class responsible for maintaining the Core Data stack, including the `NSManagedObjectContexts`,
32 | /// the `NSPersistentStore`, and the `NSPersistentStoreCoordinators`.
33 | open class PersistentStackCoordinator {
34 | fileprivate var managedObjectModelName: String
35 | fileprivate var persistentStoreConnectionCompletion: (() -> Void)?
36 |
37 | public init(managedObjectModelName: String,
38 | persistentStoreConnectionCompletion: (() -> Void)? = nil) {
39 | self.managedObjectModelName = managedObjectModelName
40 | self.persistentStoreConnectionCompletion = persistentStoreConnectionCompletion
41 | }
42 |
43 | // MARK: - Lazy stack initialization -
44 | /// The .xcdatamodel to read from.
45 | lazy var managedObjectModel: NSManagedObjectModel = {
46 | for bundle in Bundle.allBundles {
47 | if let url = bundle.url(forResource: self.managedObjectModelName, withExtension: "momd"),
48 | let mom = NSManagedObjectModel(contentsOf: url) {
49 | return mom
50 | }
51 | }
52 | preconditionFailure("Failed to find a managed object model named \(self.managedObjectModelName) in any bundle.")
53 | }()
54 |
55 | /// The persistent store coordinator, which manages disk operations.
56 | open lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
57 | var coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
58 | coordinator.resetPersistentStore()
59 | return coordinator
60 | }()
61 |
62 | /// The primary managed object context. Note the inclusion of the parent context, which takes disk operations off
63 | /// the main thread.
64 | open lazy var mainContext: NSManagedObjectContext = { [unowned self] in
65 | var mainContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
66 | mainContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType)
67 | self.connectPrivateContextToPersistentStoreCoordinator()
68 | mainContext.parent = self.privateContext
69 | return mainContext
70 | }()
71 |
72 | /// Connects the private context with its PSC on the correct thread, waits for the connection to take place,
73 | /// then announces its completion via the initializationCompletion closure.
74 | func connectPrivateContextToPersistentStoreCoordinator() {
75 | self.privateContext.performAndWait({ [unowned self] in
76 | self.privateContext.persistentStoreCoordinator = self.persistentStoreCoordinator
77 | if let completion = self.persistentStoreConnectionCompletion {
78 | DispatchQueue.main.async(execute: {
79 | completion()
80 | })
81 | }
82 | })
83 | }
84 |
85 | /// A context that escorts disk operations off the main thread.
86 | lazy var privateContext: NSManagedObjectContext = { [unowned self] in
87 | var privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
88 | privateContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyObjectTrumpMergePolicyType)
89 | return privateContext
90 | }()
91 |
92 | // MARK: - Convenience accessors -
93 | /// - returns: The path to the sqlite file that stores the application's data.
94 | static var persistentStoreURL: URL = {
95 | return FileManager.documentDirectoryURL.appendingPathComponent("Model.sqlite")
96 | }()
97 |
98 | // MARK: - Stack clearing -
99 | /// Clear the managed object contexts.
100 | func resetManageObjectContext() {
101 | mainContext.performAndWait({[unowned self] in
102 | self.mainContext.reset()
103 | self.privateContext.performAndWait({
104 | self.privateContext.reset()
105 | })
106 | })
107 | }
108 |
109 | /// Attempt to remove existing persistent stores attach a new one.
110 | /// Note: this method should not be invoked in lazy instantiatiations of a persistentStoreCoordinator. Instead,
111 | /// directly call the corresponding function on the coordinator itself.
112 | open func resetPersistentStore() {
113 | persistentStoreCoordinator.resetPersistentStore()
114 | }
115 | }
116 |
117 | // MARK: - NSPersistentStoreCoordinator+Recovery -
118 | extension NSPersistentStoreCoordinator {
119 | /// Attempt to remove existing persistent stores attach a new one.
120 | func resetPersistentStore() {
121 | for store in persistentStores {
122 | do {
123 | try remove(store)
124 | } catch {
125 | log(format("Failure to remove persistent store"))
126 | }
127 | }
128 | do {
129 | let document = FileManager.documentDirectoryURL
130 | if !FileManager.directoryExists(at: document) {
131 | // In the event a Document directory does not exist, create one.
132 | // Otherwise, the persistent store will not be added.
133 | try FileManager.createDirectory(at: document)
134 | }
135 |
136 | let options = [NSMigratePersistentStoresAutomaticallyOption: NSNumber(value: true as Bool), NSInferMappingModelAutomaticallyOption: NSNumber(value: true as Bool)]
137 | try addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil,
138 | at: PersistentStackCoordinator.persistentStoreURL,
139 | options: options)
140 |
141 | } catch {
142 | var dict = JSONObject()
143 | dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data" as AnyObject?
144 | dict[NSLocalizedFailureReasonErrorKey] = "There was an error creating or loading the application's saved data." as AnyObject?
145 | dict[NSUnderlyingErrorKey] = error as NSError
146 | let error = NSError(domain: DandyErrorDomain, code: 9999, userInfo: dict)
147 | log(format("Failure to add persistent store", with: error))
148 | do {
149 | try FileManager.default.removeItem(at: PersistentStackCoordinator.persistentStoreURL)
150 | } catch {
151 | log(format("Failure to remove cached sqlite file"))
152 | }
153 | EntityMapper.clearCache()
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/Sources/Core/Serializer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Serializer.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 7/15/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import CoreData
29 | fileprivate func < (lhs: T?, rhs: T?) -> Bool {
30 | switch (lhs, rhs) {
31 | case let (l?, r?):
32 | return l < r
33 | case (nil, _?):
34 | return true
35 | default:
36 | return false
37 | }
38 | }
39 |
40 | fileprivate func > (lhs: T?, rhs: T?) -> Bool {
41 | switch (lhs, rhs) {
42 | case let (l?, r?):
43 | return l > r
44 | default:
45 | return rhs < lhs
46 | }
47 | }
48 |
49 | /**
50 | Serializes NSManagedObjects objects to json.
51 | */
52 | public struct Serializer {
53 | /// Produces json representing an object and, potentially, members of its relationship tree.
54 | ///
55 | /// Specify each relationship you wish to include in the serialization by including its name in the relationship
56 | /// array. To serialize further into a relationship "tree", use keypaths.
57 | ///
58 | /// As an example, imagine the following relationship tree:
59 | ///
60 | /// dandy (relationships to)
61 | /// -> hats
62 | /// -> gossip (relationship to)
63 | /// -> purveyor
64 | ///
65 | /// - If no relationships are specified when serializing the dandy object, only its attributes will be serialized
66 | /// into json.
67 | /// - If ["hats", "gossip"] is specified when serializing the dandy object, Dandy's attributes will be serialize
68 | /// along with its hats and gossipe relationships.
69 | /// - If ["gossip.purveyor"] is specified when serializing the dandy object, Dandy's attributes, gossip, and
70 | /// gossip's purveyor will be serialized into json.
71 | ///
72 | /// Note: attributes with nil values will not be included in the returned json. However, nil relationships that are
73 | /// specified will be included as empty arrays or objects.
74 | ///
75 | /// - parameter object: An object to serialize into json
76 | /// - parameter relationships: Relationships and keypaths to nested relationships targeted for serialization.
77 | ///
78 | /// - returns: A json representation of this object and its relationships if one could be produced. Otherwise, nil.
79 | public static func serialize(_ object: NSManagedObject,
80 | including relationships: [String]? = nil) -> JSONObject? {
81 | var json = JSONObject()
82 | let map = EntityMapper.map(object.entity)
83 | if let map = map {
84 | for (property, description) in map {
85 | // Map attributes, ensuring mapping conversion
86 | if description.type == .attribute {
87 | json[property] = object.value(forKey: description.name)
88 | }
89 | else if let relationships = relationships
90 | , (relationships.contains(description.name)
91 | || nestedSerializationTargets(for: description.name, including: relationships)?.count > 0) {
92 | let nestedRelationships = nestedSerializationTargets(for: description.name, including: relationships)
93 | // Map relationships and recurse into nested relationships
94 | if description.toMany {
95 | let relatedObjects = description.ordered ? (object.value(forKey: description.name) as AnyObject).array
96 | : (object.value(forKey: description.name) as AnyObject).allObjects
97 | if let relatedObjects = relatedObjects as? [NSManagedObject] {
98 | if relatedObjects.count > 0 {
99 | json[property] = serialize(relatedObjects, including: nestedRelationships)
100 | }
101 | else {
102 | json[property] = [[:]]
103 | }
104 | }
105 | // Assume nils to denote empty objects
106 | else {
107 | json[property] = [[:]]
108 | }
109 | }
110 | else {
111 | if let relationship = object.value(forKey: description.name) as? NSManagedObject {
112 | json[property] = serialize(relationship, including: nestedRelationships)
113 | }
114 | // Assume nils to denote empty objects
115 | else {
116 | json[property] = [:]
117 | }
118 | }
119 | }
120 | }
121 | }
122 | if json.count == 0 {
123 | log(format("Failed to serialize object \(object) including relationships \(relationships)"))
124 | return nil
125 | }
126 | return json
127 | }
128 |
129 | /// Recursively invokes other class methods to produce a json array, including relationships.
130 | ///
131 | /// - parameter objects: An array of `NSManagedObjects` to serialize
132 | /// - parameter relationships: The relationships targeted for serialization.
133 | ///
134 | /// - returns: A json representation of the objects and their relationships if one could be produced. Otherwise, nil.
135 | public static func serialize(_ objects: [NSManagedObject],
136 | including relationships: [String]? = nil) -> [JSONObject]? {
137 | var json = [JSONObject]()
138 | for object in objects {
139 | if let relationshipJSON = serialize(object, including: relationships) {
140 | json.append(relationshipJSON)
141 | }
142 | }
143 | return json.count > 0 ? json: nil
144 | }
145 |
146 | /// Determines which relationships to a given relationship require serialization. Relationships to a relationship
147 | /// are referred to as "nested" relationships. Invoked at every "level" of serialization to recursively convert
148 | /// keypaths with the name of a top-level relationship into a string which no longer references that relationship.
149 | ///
150 | /// - parameter relationship: The top-level relationship to query.
151 | /// - parameter relationships: All serialization targets for the top-level object.
152 | ///
153 | /// - returns: An array of nested relationships targeted for serialization.
154 | static func nestedSerializationTargets(for relationship: String,
155 | including nestedRelationships: [String]?) -> [String]? {
156 | if let nestedRelationships = nestedRelationships {
157 | let keypaths = nestedRelationships.filter({$0.range(of: relationship) != nil && $0.range(of: ".") != nil})
158 | // Eliminate the relationship name and the period, recursing one level deeper.
159 | let nestedTargets = keypaths.map({$0.replacingOccurrences(of: relationship + ".", with: "")})
160 | return nestedTargets.count > 0 ? nestedTargets: nil
161 | }
162 | return nil
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/Sources/Value Conversion/ConvertibleType.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ConvertibleTypes.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 10/26/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import Foundation
29 |
30 | // MARK: - ConvertibleType -
31 | protocol BooleanConvertible {
32 | func convertToBoolean() -> Bool?
33 | }
34 |
35 | protocol DateConvertible {
36 | func convertToDate() -> Date?
37 | }
38 |
39 | protocol DataConvertible {
40 | func convertToData() -> Data?
41 | }
42 |
43 | protocol DoubleConvertible {
44 | func convertToDouble() -> Double?
45 | }
46 |
47 | protocol DecimalConvertible {
48 | func convertToDecimal() -> NSDecimalNumber?
49 | }
50 |
51 | protocol FloatConvertible {
52 | func convertToFloat() -> Float?
53 | }
54 |
55 | protocol IntConvertible {
56 | func convertToInt() -> Int?
57 | }
58 |
59 | protocol StringConvertible {
60 | func convertToString() -> String?
61 | }
62 |
63 | protocol NilConvertible {
64 | func convertToNil() -> Any?
65 | }
66 |
67 | protocol NumericConvertible: DoubleConvertible, DecimalConvertible, FloatConvertible, IntConvertible {}
68 | protocol ConvertibleType: BooleanConvertible, DataConvertible, DateConvertible, NumericConvertible, StringConvertible {}
69 |
70 | // MARK: - Date -
71 | extension Date: DateConvertible, NumericConvertible, StringConvertible {
72 | func convertToDate() -> Date? {
73 | return self
74 | }
75 |
76 | func convertToDecimal() -> NSDecimalNumber? {
77 | return NSDecimalNumber(value: self.timeIntervalSince1970 as Double)
78 | }
79 |
80 | func convertToDouble() -> Double? {
81 | return timeIntervalSince1970
82 | }
83 |
84 | func convertToFloat() -> Float? {
85 | return Float(timeIntervalSince1970)
86 | }
87 |
88 | func convertToInt() -> Int? {
89 | return Int(round(self.timeIntervalSince1970))
90 | }
91 |
92 | func convertToString() -> String? {
93 | return CoreDataValueConverter.dateFormatter.string(from: self)
94 | }
95 | }
96 | // MARK: - Data -
97 | extension Data : DataConvertible, StringConvertible {
98 | func convertToData() -> Data? {
99 | return self
100 | }
101 |
102 | func convertToString() -> String? {
103 | return String(data: self, encoding: String.Encoding.utf8)
104 | }
105 | }
106 | // MARK: - Numbers -
107 | protocol NumericConvertibleType: SignedNumber, ConvertibleType { }
108 | extension NumericConvertibleType {
109 | private func asDouble() -> Double? {
110 | switch self {
111 | case let i as Int: return Double(i)
112 | case let f as Float: return Double(f)
113 | case let d as Double: return d
114 | default: return nil
115 | }
116 | }
117 |
118 | func convertToBoolean() -> Bool? {
119 | if self == 0 {
120 | return false
121 | } else if self >= 1 {
122 | return true
123 | }
124 | return nil
125 | }
126 |
127 | func convertToDate() -> Date? {
128 | if let double = asDouble() {
129 | return Date(timeIntervalSince1970: TimeInterval(double))
130 | }
131 | return nil
132 | }
133 |
134 | func convertToData() -> Data? {
135 | var cpy = self
136 | return Data(bytes: &cpy, count: MemoryLayout.size)
137 | }
138 |
139 | func convertToDecimal() -> NSDecimalNumber? {
140 | if let double = asDouble() {
141 | return NSDecimalNumber(value: Double(double))
142 | }
143 | return nil
144 | }
145 |
146 | func convertToDouble() -> Double? {
147 | if let double = asDouble() {
148 | return double
149 | }
150 | return nil
151 | }
152 |
153 | func convertToFloat() -> Float? {
154 | if let double = asDouble() {
155 | return Float(double)
156 | }
157 | return nil
158 | }
159 |
160 | func convertToInt() -> Int? {
161 | if let double = asDouble() {
162 | return Int(double)
163 | }
164 | return nil
165 | }
166 |
167 | func convertToString() -> String? {
168 | return "\(self)"
169 | }
170 | }
171 | extension Int: NumericConvertibleType { }
172 | extension Double: NumericConvertibleType { }
173 | extension Float: NumericConvertibleType { }
174 |
175 | extension NSNumber: ConvertibleType {
176 | func convertToBoolean() -> Bool? {
177 | return doubleValue.convertToBoolean()
178 | }
179 |
180 | func convertToDate() -> Date? {
181 | return doubleValue.convertToDate()
182 | }
183 |
184 | func convertToData() -> Data? {
185 | return doubleValue.convertToData()
186 | }
187 |
188 | func convertToDecimal() -> NSDecimalNumber? {
189 | return doubleValue.convertToDecimal()
190 | }
191 |
192 | func convertToDouble() -> Double? {
193 | return doubleValue.convertToDouble()
194 | }
195 |
196 | func convertToFloat() -> Float? {
197 | return doubleValue.convertToFloat()
198 | }
199 |
200 | func convertToInt() -> Int? {
201 | return doubleValue.convertToInt()
202 | }
203 |
204 | func convertToString() -> String? {
205 | return doubleValue.convertToString()
206 | }
207 | }
208 |
209 | // MARK: - Strings -
210 | extension String: ConvertibleType, NilConvertible {
211 | func convertToBoolean() -> Bool? {
212 | let lowercaseValue = lowercased()
213 | if lowercaseValue == "yes"
214 | || lowercaseValue == "true"
215 | || lowercaseValue == "1" {
216 | return true
217 | } else if lowercaseValue == "no"
218 | || lowercaseValue == "false"
219 | || lowercaseValue == "0" {
220 | return false
221 | }
222 | return nil
223 | }
224 |
225 | func convertToDate() -> Date? {
226 | return CoreDataValueConverter.dateFormatter.date(from: self as String)
227 | }
228 |
229 | func convertToData() -> Data? {
230 | return data(using: String.Encoding(rawValue: String.Encoding.utf8.rawValue))
231 | }
232 |
233 | func convertToDecimal() -> NSDecimalNumber? {
234 | if let num = Double(self) {
235 | return NSDecimalNumber(value: num)
236 | }
237 |
238 | return nil
239 | }
240 |
241 | func convertToDouble() -> Double? {
242 | return Double(self)
243 | }
244 |
245 | func convertToFloat() -> Float? {
246 | return Float(self)
247 | }
248 |
249 | func convertToInt() -> Int? {
250 | return Int(self)
251 | }
252 |
253 | func convertToString() -> String? {
254 | if lowercased() == "null"
255 | || lowercased() == "nil" {
256 | return nil
257 | }
258 |
259 | return self
260 | }
261 |
262 | func convertToNil() -> Any? {
263 | return convertToString()
264 | }
265 | }
266 |
267 | extension NSString: ConvertibleType, NilConvertible {
268 | func convertToBoolean() -> Bool? {
269 | return String(self).convertToBoolean()
270 | }
271 |
272 | func convertToDate() -> Date? {
273 | return String(self).convertToDate()
274 | }
275 |
276 | func convertToData() -> Data? {
277 | return String(self).convertToData()
278 | }
279 |
280 | func convertToDecimal() -> NSDecimalNumber? {
281 | return String(self).convertToDecimal()
282 | }
283 |
284 | func convertToDouble() -> Double? {
285 | return String(self).convertToDouble()
286 | }
287 |
288 | func convertToFloat() -> Float? {
289 | return String(self).convertToFloat()
290 | }
291 |
292 | func convertToInt() -> Int? {
293 | return String(self).convertToInt()
294 | }
295 |
296 | func convertToString() -> String? {
297 | return String(self).convertToString()
298 | }
299 |
300 | func convertToNil() -> Any? {
301 | return String(self).convertToNil()
302 | }
303 | }
304 |
305 | extension NSNull: NilConvertible {
306 | func convertToNil() -> Any? {
307 | return nil
308 | }
309 | }
310 |
--------------------------------------------------------------------------------
/Sources/Core/ObjectFactory.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ObjectFactory.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 7/4/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import CoreData
29 |
30 | public struct ObjectFactory {
31 |
32 | /// Returns an object of a given type constructed from the provided json. This function is primarily accessed
33 | /// within Dandy to recursively produce objects when parsing nested json, and is thereby only accessed indirectly.
34 | /// Others, however, may find direct access to this convenience useful.
35 | ///
36 | /// By default, this function will recursively parse through a json hierarchy.
37 | ///
38 | /// Note that this method enforces the specification of a primaryKey for the given entity.
39 | ///
40 | /// Finally, as invocations of this function implicitly involve database fetches, it may bottleneck when used to
41 | /// process json with thousands of objects.
42 | ///
43 | /// - parameter type: The type of object to make.
44 | /// - parameter from: The json to map into the returned object.
45 | ///
46 | /// - returns: A Model of the specified type if one could be inserted or fetched. The values that could be mapped
47 | /// from the json to the object will be found on the returned object.
48 | @discardableResult public static func make(_ type: Model.Type,
49 | from json: JSONObject) -> Model? {
50 | guard let entity = type.entityDescription() else {
51 | log(format("An entityDescription was not found for type \(type) from json \n\(json)."))
52 | return nil
53 | }
54 |
55 | var object: NSManagedObject? = nil
56 |
57 | if entity.isUnique {
58 | // Attempt to fetch or create unique object for primaryKey
59 | if let primaryKeyValue = entity.primaryKeyValueFromJSON(json) {
60 | object = Dandy.insertUnique(type, identifiedBy: primaryKeyValue)
61 | }
62 | } else {
63 | // The object is not unique. Simply insert it.
64 | object = Dandy.insert(type)
65 | }
66 |
67 | if let object = object {
68 | build(object, from: json)
69 | finalizeMapping(of: object, from: json)
70 |
71 | return object as? Model
72 | }
73 |
74 | log(format("An object could not be made for entity \(entity.name) from json \n\(json)."))
75 | return nil
76 | }
77 |
78 | /// Transcribes attributes and relationships from json to a given object. Use this function to perform bulk upates
79 | /// on an object from json.
80 | ///
81 | /// - parameter object: The `NSManagedObject` to configure.
82 | /// - parameter json: The json to map into the returned object.
83 | ///
84 | /// - returns: The object passed in with newly mapped values where mapping was possible.
85 | @discardableResult public static func build(_ object: Model,
86 | from json: JSONObject) -> Model {
87 | if let map = EntityMapper.map(object.entity) {
88 | // Begin mapping values from json to object properties
89 | for (key, description) in map {
90 | if let value: Any = _value(at: key, of: json) {
91 | if description.type == .attribute,
92 | let type = description.attributeType {
93 | // A valid mapping was found for an attribute of a known type
94 | object.setValue(CoreDataValueConverter.convert(value, to: type), forKey: description.name)
95 | } else if description.type == .relationship {
96 | // A valid mapping was found for a relationship of a known type
97 | make(description, to: object, from: value)
98 | }
99 | }
100 | }
101 | }
102 | return object
103 | }
104 |
105 | /// Builds a relationship to a passed in object from json.
106 | /// Note that the json type must match the relationship type. For instance, passing a json array to build a toOne
107 | /// relationship is invalid, just as passing a single json object to build a toMany relationship is invalid.
108 | ///
109 | /// - parameter relationship: An object specifying the details of the relationship, including its name, whether it is
110 | /// toMany, and whether it is ordered.
111 | /// - parameter object: The parent object or "owner" of the relationship. If relationship objects are built, they will
112 | /// be assigned to this object relationship.
113 | /// - parameter json: The json with which to build the related objects.
114 | ///
115 | /// - returns: The object passed in with a newly mapped relationship if relationship objects were built.
116 | @discardableResult static func make(_ relationship: PropertyDescription,
117 | to object: NSManagedObject,
118 | from json: Any) -> NSManagedObject {
119 | guard let relatedEntity = relationship.destinationEntity else {
120 | log(format("The entity named \(relationship.name) for entity \(object.entity.name) lacks an NSEntityDescription. No relationthip will be built."))
121 | return object
122 | }
123 |
124 | guard let relatedTypeName = relatedEntity.managedObjectClassName,
125 | let relatedEntityType = NSManagedObject.type(named: relatedTypeName) else {
126 | log(format("No type could be found for \(relatedEntity.managedObjectClassName) of type named \(relatedEntity.name). Provide a subclassed type to enable serialization."))
127 | return object
128 | }
129 |
130 | if let json = json as? JSONObject,
131 | !relationship.toMany {
132 | // A dictionary was passed for a toOne relationship
133 | if let relation = make(relatedEntityType, from: json) {
134 | object.setValue(relation, forKey: relationship.name)
135 | } else {
136 | // No relationship could be made from the json. Nil out the relationship.
137 | log(format("A relationship named \(relationship.name) could not be made for \(object) from json \n\(json).\n\(relationship.name) will be nilled out if it is an optional relationship."))
138 |
139 | object.nilIfOptional(relationship)
140 | }
141 |
142 | return object
143 | } else if let json = json as? [JSONObject],
144 | relationship.toMany {
145 | // An array was passed for a toMany relationship
146 | var relations = [NSManagedObject]()
147 | for child in json {
148 | if let relation = make(relatedEntityType, from: child) {
149 | relations.append(relation)
150 | } else {
151 | log(format("A relationship named \(relationship.name) could not be established for object \(object) from json \n\(child)."))
152 | }
153 | }
154 |
155 | object.setValue(relationship.ordered ? NSOrderedSet(array: relations)
156 | : NSSet(array: relations),
157 | forKey: relationship.name)
158 |
159 | return object
160 | } else if let possibleNull = json as? NilConvertible,
161 | possibleNull.convertToNil() == nil {
162 | // A nil-convertible value was passed
163 | object.nilIfOptional(relationship)
164 | } else {
165 | // The value provided did not match the expected type. For instance, an array was passed where an object
166 | // was expected.
167 | log(format("A relationship named \(relationship.name) could not be established for object \n\(object) from json \n\(json)."
168 | + {
169 | if relationship.toMany {
170 | return " An array is expected to create toMany relationships."
171 | } else {
172 | return " An object is expected to create toOne relationships."
173 | }
174 | }()
175 | + "\(relationship.name)"))
176 | }
177 |
178 | return object
179 | }
180 |
181 | /// Allows for adopters of `MappingFinalizer` to perform custom mapping after the ObjectFactory has completed its
182 | /// work.
183 | ///
184 | /// - parameter object: The newly created object and the potential adopter of `MappingFinalizer`.
185 | /// - parameter from: The json that was used to create the object. Note that this json will include all nested
186 | /// "child" relationships, but no "parent" relationships.
187 | fileprivate static func finalizeMapping(of object: NSManagedObject, from json: JSONObject) {
188 | if let object = object as? MappingFinalizer {
189 | object.finalizeMapping(of: json)
190 | }
191 | }
192 | }
193 |
194 | // MARK: - NSManagedObject+Nil -
195 | private extension NSManagedObject {
196 | /// If a property is optional, set it to nil.
197 | ///
198 | /// - parameter property: The relationship to nil if optional.
199 | func nilIfOptional(_ property: PropertyDescription) {
200 | if property.optional {
201 | setValue(nil, forKey: property.name)
202 | }
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/Sources/Core/CoreDataDandy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreDataDandy.swift
3 | // CoreDataDandy
4 | //
5 | // Created by Noah Blake on 6/20/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import CoreData
29 |
30 | /// `CoreDataDandy` provides an interface to the majority of the module's features, which include Core Data
31 | /// bootstrapping, main and background-threaded context management, convenient `NSFetchRequests`,
32 | /// database inserts, database deletes, and `NSManagedObject` deserialization.
33 | open class CoreDataDandy {
34 | // MARK: - Properties -
35 | /// A singleton encapsulating much of CoreDataDandy's base functionality.
36 | fileprivate static let defaultDandy = CoreDataDandy()
37 |
38 | /// The default implementation of Dandy. Subclasses looking to extend or alter Dandy's functionality
39 | /// should override this getter and provide a new instance.
40 | open class var sharedDandy: CoreDataDandy {
41 | return defaultDandy
42 | }
43 |
44 | /// A manager of the NSManagedObjectContext, NSPersistentStore, and NSPersistentStoreCoordinator.
45 | /// Accessing this property directly is generaly discouraged - it is intended for use within the module alone.
46 | open var coordinator: PersistentStackCoordinator!
47 |
48 | // MARK: - Initialization-
49 | /// Bootstraps the application's core data stack.
50 | ///
51 | /// - parameter managedObjectModelName: The name of the .xcdatamodel file
52 | /// - parameter completion: A completion block executed on initialization completion
53 | @discardableResult open class func wake(_ managedObjectModelName: String,
54 | completion: (() -> Void)? = nil) -> CoreDataDandy {
55 | EntityMapper.clearCache()
56 | sharedDandy.coordinator = PersistentStackCoordinator(managedObjectModelName: managedObjectModelName,
57 | persistentStoreConnectionCompletion: completion)
58 | return sharedDandy
59 | }
60 |
61 | // MARK: - Deinitialization -
62 | /// Removes all cached data from the application without endangering future database
63 | /// interactions.
64 | open func tearDown() {
65 | coordinator.resetManageObjectContext()
66 |
67 | do {
68 | try FileManager.default.removeItem(at: PersistentStackCoordinator.persistentStoreURL as URL)
69 | } catch {
70 | log(format("Failed to delete persistent store"))
71 | }
72 |
73 | coordinator.resetPersistentStore()
74 | EntityMapper.clearCache()
75 | save()
76 | }
77 |
78 | // MARK: - Inserts -
79 | /// Inserts a new Model from the specified entity type. In general, this function should not be invoked
80 | /// directly, as its incautious use is likely to lead to database leaks.
81 | ///
82 | /// - parameter type: The type of Model to insert
83 | ///
84 | /// - returns: A managed object if one could be inserted for the specified Entity.
85 | @discardableResult open func insert(_ type: Model.Type) -> Model? {
86 | if let entityDescription = type.entityDescription() {
87 | // Ignore this insert if the entity is a singleton and a pre-existing insert exists.
88 | if entityDescription.primaryKey == SINGLETON {
89 | if let singleton = singleton(type) {
90 | return singleton
91 | }
92 | }
93 | // Otherwise, insert a new managed object
94 | return type.inserted() as? Model
95 | } else {
96 | log(format("NSEntityDescriptionNotFound for entity named " + String(describing: type) + ". No object will be returned"))
97 | return nil
98 | }
99 | }
100 |
101 | /// MARK: - Upserts -
102 | /// This function performs upserts differently depending on whether the Model is marked as unique or not.
103 | ///
104 | /// If the Model is marked as unique (either through an @primaryKey decoration or an xcdatamode constraint), the
105 | /// primaryKeyValue is extracted and an upsert is performed through
106 | /// `upsertUnique(_:, identifiedBy:) -> NSManagedObject?`.
107 | ///
108 | /// Otherwise, an insert is performed and a Model is written to from the json provided.
109 | ///
110 | /// - parameter type: The type of Model to insert
111 | /// - parameter json: A json object to map into the returned object's attributes and relationships
112 | ///
113 | /// - returns: A managed object if one could be created.
114 | @discardableResult open func upsert(_ type: Model.Type,
115 | from json: JSONObject) -> Model? {
116 | guard let entity = NSEntityDescription.forType(type) else {
117 | log(format("Could not retrieve NSEntityDescription for type \(type)"))
118 | return nil
119 | }
120 |
121 | if entity.isUnique {
122 | if let primaryKeyValue = entity.primaryKeyValueFromJSON(json) {
123 | return upsertUnique(type, identifiedBy: primaryKeyValue, from: json)
124 | } else {
125 | log(format("Could not retrieve primary key from json \(json)."))
126 | return nil
127 | }
128 | }
129 |
130 | if let managedObject = insert(type) {
131 | return ObjectFactory.build(managedObject, from: json)
132 | }
133 |
134 | return nil
135 | }
136 |
137 | /// Attempts to build an array Models from a json array. Through recursion, behaves identically to
138 | /// upsert(_:, _:) -> Model?.
139 | ///
140 | /// - parameter type: The type of Model to insert
141 | /// - parameter json: An array to map into the returned objects' attributes and relationships
142 | ///
143 | /// - returns: An array of managed objects if one could be created.
144 | @discardableResult open func batchUpsert(_ type: Model.Type,
145 | from json: [JSONObject]) -> [Model]? {
146 | var models = [Model]()
147 | for object in json {
148 | if let model = upsert(type, from: object) {
149 | models.append(model)
150 | }
151 | }
152 | return models.isEmpty ? nil : models
153 | }
154 |
155 | // MARK: - Unique objects -
156 | /// Attempts to fetch a Model of the specified type matching the primary key provided.
157 | /// - If no property on the type's `NSEntityDescription` is marked with the @primaryKey identifier or constraint,
158 | /// a warning is issued and no managed object is returned.
159 | /// - If an object matching the primaryKey is found, it is returned. Otherwise a new object is inserted and returned.
160 | /// - If more than one object is fetched for this primaryKey, a warning is issued and one is returned.
161 | ///
162 | /// - parameter type: The type of Model to insert.
163 | /// - parameter primaryKeyValue: The value of the unique object's primary key
164 | @discardableResult open func insertUnique(_ type: Model.Type,
165 | identifiedBy primaryKeyValue: Any) -> Model? {
166 | // Return an object if one exists. Otherwise, attempt to insert one.
167 | if let object = fetchUnique(type, identifiedBy: primaryKeyValue) {
168 | return object
169 | } else if let entityDescription = type.entityDescription(),
170 | let primaryKey = entityDescription.primaryKey {
171 | let object = insert(type)
172 | let convertedPrimaryKeyValue = CoreDataValueConverter.convert(primaryKeyValue, for: entityDescription, property: primaryKey)
173 | object?.setValue(convertedPrimaryKeyValue, forKey: primaryKey)
174 |
175 | return object
176 | }
177 | return nil
178 | }
179 |
180 | /// Invokes `upsertUnique(_:, identifiedBy:) -> Model?`, then attempts to write values from
181 | /// the provided JSON into the returned object.
182 | ///
183 | /// - parameter type: The type of the requested entity
184 | /// - parameter primaryKeyValue: The value of the unique object's primary key
185 | /// - parameter json: A json object to map into the returned object's attributes and relationships
186 | ///
187 | /// - returns: A managed object if one could be created.
188 | private func upsertUnique(_ type: Model.Type,
189 | identifiedBy primaryKeyValue: Any,
190 | from json: JSONObject) -> Model? {
191 | if let object = insertUnique(type, identifiedBy: primaryKeyValue) {
192 | ObjectFactory.build(object, from: json)
193 | return object
194 | } else {
195 | log(format("Could not upsert managed object of type \(type), identified by \(primaryKeyValue), json \(json)."))
196 | return nil
197 | }
198 | }
199 |
200 | // MARK: - Fetches -
201 | /// A wrapper around NSFetchRequest.
202 | ///
203 | /// - parameter type: The type of the fetched entity
204 | /// - parameter predicate: The predicate used to filter results
205 | ///
206 | /// - throws: If the ensuing NSManagedObjectContext's executeFetchRequest() throws, the exception will be passed.
207 | ///
208 | /// - returns: If the fetch was successful, the fetched Model.
209 | open func fetch(_ type: Model.Type,
210 | filterBy predicate: NSPredicate? = nil) throws -> [Model]? {
211 | let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: String(describing: type))
212 | fetchRequest.predicate = predicate
213 | return try coordinator.mainContext.fetch(fetchRequest)
214 | }
215 |
216 | /// Attempts to fetch a unique Model with a primary key value matching the passed in parameter.
217 | ///
218 | /// - parameter type: The type of the fetched entity
219 | /// - parameter primaryKeyValue: The value of unique object's primary key
220 | ///
221 | /// - returns: If the fetch was successful, the fetched Model.
222 | open func fetchUnique(_ type: Model.Type,
223 | identifiedBy primaryKeyValue: Any,
224 | emitResultCountWarnings: Bool = false) -> Model? {
225 | if let entityDescription = type.entityDescription() {
226 | if entityDescription.primaryKey == SINGLETON {
227 | if let singleton = singleton(type) {
228 | return singleton
229 | }
230 | } else if let predicate = entityDescription.primaryKeyPredicate(for: primaryKeyValue) {
231 | var results: [NSManagedObject]? = nil
232 | var resultCount = 0
233 | do {
234 | results = try fetch(type, filterBy: predicate)
235 | resultCount = results?.count ?? 0
236 | } catch {
237 | log(format("Your fetch for a unique entity named \(String(describing: type)) with identified by \(primaryKeyValue) raised an exception. This is a serious error that should be resolved immediately."))
238 | }
239 | if resultCount == 0 && emitResultCountWarnings {
240 | log(format("Your fetch for a unique entity named \(String(describing: type)) with identified by \(primaryKeyValue) returned no results."))
241 | } else if resultCount > 1 && emitResultCountWarnings {
242 | log(format("Your fetch for a unique entity named \(String(describing: type)) with identified by \(primaryKeyValue) returned multiple results. This is a serious error that should be resolved immediately."))
243 | }
244 | return results?.first as? Model
245 | } else {
246 | log(format("Failed to produce predicate for \(String(describing: type)) with identified by \(primaryKeyValue)."))
247 | }
248 | }
249 |
250 | log(format("A unique NSManaged for entity named \(String(describing: type)) could not be retrieved for primaryKey \(primaryKeyValue). No object will be returned"))
251 |
252 | return nil
253 | }
254 |
255 | // MARK: - Saves and Deletes -
256 | /// Save the current state of the `NSManagedObjectContext` to disk and optionally receive notice of the save
257 | /// operation's completion.
258 | ///
259 | /// - parameter completion: An optional closure that is invoked when the save operation complete. If the save operation
260 | /// resulted in an error, the error is returned.
261 | open func save(_ completion:((_ error: Error?) -> Void)? = nil) {
262 | /**
263 | Note: http://www.openradar.me/21745663. Currently, there is no way to throw out of performBlock. If one arises,
264 | this code should be refactored to throw.
265 | */
266 | if !coordinator.mainContext.hasChanges && !coordinator.privateContext.hasChanges {
267 | completion?(nil)
268 | return
269 | }
270 | coordinator.mainContext.performAndWait({[unowned self] in
271 | do {
272 | try self.coordinator.mainContext.save()
273 | } catch {
274 | log(format( "Failed to save main context."))
275 | completion?(error)
276 | return
277 | }
278 |
279 | self.coordinator.privateContext.perform({ [unowned self] in
280 | do {
281 | try self.coordinator.privateContext.save()
282 | completion?(nil)
283 | } catch {
284 | log(format( "Failed to save private context."))
285 | completion?(error)
286 | }
287 | })
288 | })
289 | }
290 |
291 | /// Delete a managed object.
292 | ///
293 | /// - parameter object: The object to be deleted.
294 | /// - parameter completion: An optional closure that is invoked when the deletion is complete.
295 | open func delete(_ object: NSManagedObject, completion: (() -> Void)? = nil) {
296 | if let context = object.managedObjectContext {
297 | context.perform({
298 | context.delete(object)
299 | completion?()
300 | })
301 | }
302 | }
303 |
304 | // MARK: - Singletons -
305 | /// Attempts to singleton of a given type.
306 | ///
307 | /// - parameter type: The type of the singleton
308 | ///
309 | /// - returns: The singleton for this type if one could be found.
310 | fileprivate func singleton(_ type: Model.Type) -> Model? {
311 | do {
312 | if let results = try fetch(type) {
313 | if results.count == 1 {
314 | return results.first
315 | } else if results.count == 0 {
316 | return type.inserted() as? Model
317 | } else {
318 | log(format("Failed to fetch unique instance of entity named " + String(describing: type) + "."))
319 | return nil
320 |
321 | }
322 | }
323 | } catch {
324 | log(format("Your singleton fetch for entity named \(String(describing: type)) raised an exception. This is a serious error that should be resolved immediately."))
325 | }
326 |
327 | log(format("Failed to fetch unique instance of entity named " + String(describing: type) + "."))
328 | return nil
329 | }
330 | }
331 |
332 | // MARK: - Convenience accessors -
333 | /// A lazy global for more succinct access to CoreDataDandy's sharedDandy.
334 | public let Dandy: CoreDataDandy = CoreDataDandy.sharedDandy
335 |
--------------------------------------------------------------------------------
/Framework/CoreDataDandy.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 98345A3C1DCCE7C600E8AF88 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A291DCCE7C600E8AF88 /* Constants.swift */; };
11 | 98345A3D1DCCE7C600E8AF88 /* CoreDataDandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A2B1DCCE7C600E8AF88 /* CoreDataDandy.swift */; };
12 | 98345A3E1DCCE7C600E8AF88 /* EntityMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A2C1DCCE7C600E8AF88 /* EntityMapper.swift */; };
13 | 98345A3F1DCCE7C600E8AF88 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A2D1DCCE7C600E8AF88 /* Logger.swift */; };
14 | 98345A401DCCE7C600E8AF88 /* MappingFinalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A2E1DCCE7C600E8AF88 /* MappingFinalizer.swift */; };
15 | 98345A411DCCE7C600E8AF88 /* ObjectFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A2F1DCCE7C600E8AF88 /* ObjectFactory.swift */; };
16 | 98345A421DCCE7C600E8AF88 /* PersistentStackCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A301DCCE7C600E8AF88 /* PersistentStackCoordinator.swift */; };
17 | 98345A431DCCE7C600E8AF88 /* PropertyDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A311DCCE7C600E8AF88 /* PropertyDescription.swift */; };
18 | 98345A441DCCE7C600E8AF88 /* Serializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A321DCCE7C600E8AF88 /* Serializer.swift */; };
19 | 98345A451DCCE7C600E8AF88 /* Dictionary+Dandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A341DCCE7C600E8AF88 /* Dictionary+Dandy.swift */; };
20 | 98345A461DCCE7C600E8AF88 /* NSEntityDescription+Dandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A351DCCE7C600E8AF88 /* NSEntityDescription+Dandy.swift */; };
21 | 98345A471DCCE7C600E8AF88 /* NSFileManager+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A361DCCE7C600E8AF88 /* NSFileManager+Directory.swift */; };
22 | 98345A481DCCE7C600E8AF88 /* NSManagedObject+Dandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A371DCCE7C600E8AF88 /* NSManagedObject+Dandy.swift */; };
23 | 98345A491DCCE7C600E8AF88 /* ConvertibleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A391DCCE7C600E8AF88 /* ConvertibleType.swift */; };
24 | 98345A4A1DCCE7C600E8AF88 /* CoreDataValueConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A3A1DCCE7C600E8AF88 /* CoreDataValueConverter.swift */; };
25 | 98345A4B1DCCE7C600E8AF88 /* ValueConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98345A3B1DCCE7C600E8AF88 /* ValueConverter.swift */; };
26 | 985CD4DC1C617024005EB9E2 /* CoreDataDandy.h in Headers */ = {isa = PBXBuildFile; fileRef = 985CD4DB1C617024005EB9E2 /* CoreDataDandy.h */; settings = {ATTRIBUTES = (Public, ); }; };
27 | /* End PBXBuildFile section */
28 |
29 | /* Begin PBXFileReference section */
30 | 98345A291DCCE7C600E8AF88 /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
31 | 98345A2B1DCCE7C600E8AF88 /* CoreDataDandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataDandy.swift; sourceTree = ""; };
32 | 98345A2C1DCCE7C600E8AF88 /* EntityMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityMapper.swift; sourceTree = ""; };
33 | 98345A2D1DCCE7C600E8AF88 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; };
34 | 98345A2E1DCCE7C600E8AF88 /* MappingFinalizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappingFinalizer.swift; sourceTree = ""; };
35 | 98345A2F1DCCE7C600E8AF88 /* ObjectFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectFactory.swift; sourceTree = ""; };
36 | 98345A301DCCE7C600E8AF88 /* PersistentStackCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentStackCoordinator.swift; sourceTree = ""; };
37 | 98345A311DCCE7C600E8AF88 /* PropertyDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyDescription.swift; sourceTree = ""; };
38 | 98345A321DCCE7C600E8AF88 /* Serializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Serializer.swift; sourceTree = ""; };
39 | 98345A341DCCE7C600E8AF88 /* Dictionary+Dandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Dandy.swift"; sourceTree = ""; };
40 | 98345A351DCCE7C600E8AF88 /* NSEntityDescription+Dandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+Dandy.swift"; sourceTree = ""; };
41 | 98345A361DCCE7C600E8AF88 /* NSFileManager+Directory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFileManager+Directory.swift"; sourceTree = ""; };
42 | 98345A371DCCE7C600E8AF88 /* NSManagedObject+Dandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Dandy.swift"; sourceTree = ""; };
43 | 98345A391DCCE7C600E8AF88 /* ConvertibleType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvertibleType.swift; sourceTree = ""; };
44 | 98345A3A1DCCE7C600E8AF88 /* CoreDataValueConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataValueConverter.swift; sourceTree = ""; };
45 | 98345A3B1DCCE7C600E8AF88 /* ValueConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueConverter.swift; sourceTree = ""; };
46 | 985CD4D81C617024005EB9E2 /* CoreDataDandy.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataDandy.framework; sourceTree = BUILT_PRODUCTS_DIR; };
47 | 985CD4DB1C617024005EB9E2 /* CoreDataDandy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreDataDandy.h; sourceTree = ""; };
48 | 985CD4DD1C617024005EB9E2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
49 | /* End PBXFileReference section */
50 |
51 | /* Begin PBXFrameworksBuildPhase section */
52 | 985CD4D41C617024005EB9E2 /* Frameworks */ = {
53 | isa = PBXFrameworksBuildPhase;
54 | buildActionMask = 2147483647;
55 | files = (
56 | );
57 | runOnlyForDeploymentPostprocessing = 0;
58 | };
59 | /* End PBXFrameworksBuildPhase section */
60 |
61 | /* Begin PBXGroup section */
62 | 98345A281DCCE7C600E8AF88 /* Sources */ = {
63 | isa = PBXGroup;
64 | children = (
65 | 98345A291DCCE7C600E8AF88 /* Constants.swift */,
66 | 98345A2A1DCCE7C600E8AF88 /* Core */,
67 | 98345A331DCCE7C600E8AF88 /* Extensions */,
68 | 98345A381DCCE7C600E8AF88 /* Value Conversion */,
69 | );
70 | name = Sources;
71 | path = ../../Sources;
72 | sourceTree = "";
73 | };
74 | 98345A2A1DCCE7C600E8AF88 /* Core */ = {
75 | isa = PBXGroup;
76 | children = (
77 | 98345A2B1DCCE7C600E8AF88 /* CoreDataDandy.swift */,
78 | 98345A2C1DCCE7C600E8AF88 /* EntityMapper.swift */,
79 | 98345A2D1DCCE7C600E8AF88 /* Logger.swift */,
80 | 98345A2E1DCCE7C600E8AF88 /* MappingFinalizer.swift */,
81 | 98345A2F1DCCE7C600E8AF88 /* ObjectFactory.swift */,
82 | 98345A301DCCE7C600E8AF88 /* PersistentStackCoordinator.swift */,
83 | 98345A311DCCE7C600E8AF88 /* PropertyDescription.swift */,
84 | 98345A321DCCE7C600E8AF88 /* Serializer.swift */,
85 | );
86 | path = Core;
87 | sourceTree = "";
88 | };
89 | 98345A331DCCE7C600E8AF88 /* Extensions */ = {
90 | isa = PBXGroup;
91 | children = (
92 | 98345A341DCCE7C600E8AF88 /* Dictionary+Dandy.swift */,
93 | 98345A351DCCE7C600E8AF88 /* NSEntityDescription+Dandy.swift */,
94 | 98345A361DCCE7C600E8AF88 /* NSFileManager+Directory.swift */,
95 | 98345A371DCCE7C600E8AF88 /* NSManagedObject+Dandy.swift */,
96 | );
97 | path = Extensions;
98 | sourceTree = "";
99 | };
100 | 98345A381DCCE7C600E8AF88 /* Value Conversion */ = {
101 | isa = PBXGroup;
102 | children = (
103 | 98345A391DCCE7C600E8AF88 /* ConvertibleType.swift */,
104 | 98345A3A1DCCE7C600E8AF88 /* CoreDataValueConverter.swift */,
105 | 98345A3B1DCCE7C600E8AF88 /* ValueConverter.swift */,
106 | );
107 | path = "Value Conversion";
108 | sourceTree = "";
109 | };
110 | 985CD4CE1C617024005EB9E2 = {
111 | isa = PBXGroup;
112 | children = (
113 | 985CD4DA1C617024005EB9E2 /* CoreDataDandy */,
114 | 985CD4D91C617024005EB9E2 /* Products */,
115 | );
116 | sourceTree = "";
117 | };
118 | 985CD4D91C617024005EB9E2 /* Products */ = {
119 | isa = PBXGroup;
120 | children = (
121 | 985CD4D81C617024005EB9E2 /* CoreDataDandy.framework */,
122 | );
123 | name = Products;
124 | sourceTree = "";
125 | };
126 | 985CD4DA1C617024005EB9E2 /* CoreDataDandy */ = {
127 | isa = PBXGroup;
128 | children = (
129 | 985CD4DB1C617024005EB9E2 /* CoreDataDandy.h */,
130 | 985CD4DD1C617024005EB9E2 /* Info.plist */,
131 | 98345A281DCCE7C600E8AF88 /* Sources */,
132 | );
133 | path = CoreDataDandy;
134 | sourceTree = "";
135 | };
136 | /* End PBXGroup section */
137 |
138 | /* Begin PBXHeadersBuildPhase section */
139 | 985CD4D51C617024005EB9E2 /* Headers */ = {
140 | isa = PBXHeadersBuildPhase;
141 | buildActionMask = 2147483647;
142 | files = (
143 | 985CD4DC1C617024005EB9E2 /* CoreDataDandy.h in Headers */,
144 | );
145 | runOnlyForDeploymentPostprocessing = 0;
146 | };
147 | /* End PBXHeadersBuildPhase section */
148 |
149 | /* Begin PBXNativeTarget section */
150 | 985CD4D71C617024005EB9E2 /* CoreDataDandy */ = {
151 | isa = PBXNativeTarget;
152 | buildConfigurationList = 985CD4E01C617024005EB9E2 /* Build configuration list for PBXNativeTarget "CoreDataDandy" */;
153 | buildPhases = (
154 | 985CD4D31C617024005EB9E2 /* Sources */,
155 | 985CD4D41C617024005EB9E2 /* Frameworks */,
156 | 985CD4D51C617024005EB9E2 /* Headers */,
157 | 985CD4D61C617024005EB9E2 /* Resources */,
158 | );
159 | buildRules = (
160 | );
161 | dependencies = (
162 | );
163 | name = CoreDataDandy;
164 | productName = CoreDataDandy;
165 | productReference = 985CD4D81C617024005EB9E2 /* CoreDataDandy.framework */;
166 | productType = "com.apple.product-type.framework";
167 | };
168 | /* End PBXNativeTarget section */
169 |
170 | /* Begin PBXProject section */
171 | 985CD4CF1C617024005EB9E2 /* Project object */ = {
172 | isa = PBXProject;
173 | attributes = {
174 | LastUpgradeCheck = 0800;
175 | ORGANIZATIONNAME = "Fuzz Productions";
176 | TargetAttributes = {
177 | 985CD4D71C617024005EB9E2 = {
178 | CreatedOnToolsVersion = 7.2;
179 | LastSwiftMigration = 0800;
180 | };
181 | };
182 | };
183 | buildConfigurationList = 985CD4D21C617024005EB9E2 /* Build configuration list for PBXProject "CoreDataDandy" */;
184 | compatibilityVersion = "Xcode 3.2";
185 | developmentRegion = English;
186 | hasScannedForEncodings = 0;
187 | knownRegions = (
188 | en,
189 | );
190 | mainGroup = 985CD4CE1C617024005EB9E2;
191 | productRefGroup = 985CD4D91C617024005EB9E2 /* Products */;
192 | projectDirPath = "";
193 | projectRoot = "";
194 | targets = (
195 | 985CD4D71C617024005EB9E2 /* CoreDataDandy */,
196 | );
197 | };
198 | /* End PBXProject section */
199 |
200 | /* Begin PBXResourcesBuildPhase section */
201 | 985CD4D61C617024005EB9E2 /* Resources */ = {
202 | isa = PBXResourcesBuildPhase;
203 | buildActionMask = 2147483647;
204 | files = (
205 | );
206 | runOnlyForDeploymentPostprocessing = 0;
207 | };
208 | /* End PBXResourcesBuildPhase section */
209 |
210 | /* Begin PBXSourcesBuildPhase section */
211 | 985CD4D31C617024005EB9E2 /* Sources */ = {
212 | isa = PBXSourcesBuildPhase;
213 | buildActionMask = 2147483647;
214 | files = (
215 | 98345A3C1DCCE7C600E8AF88 /* Constants.swift in Sources */,
216 | 98345A461DCCE7C600E8AF88 /* NSEntityDescription+Dandy.swift in Sources */,
217 | 98345A441DCCE7C600E8AF88 /* Serializer.swift in Sources */,
218 | 98345A411DCCE7C600E8AF88 /* ObjectFactory.swift in Sources */,
219 | 98345A4B1DCCE7C600E8AF88 /* ValueConverter.swift in Sources */,
220 | 98345A471DCCE7C600E8AF88 /* NSFileManager+Directory.swift in Sources */,
221 | 98345A491DCCE7C600E8AF88 /* ConvertibleType.swift in Sources */,
222 | 98345A3E1DCCE7C600E8AF88 /* EntityMapper.swift in Sources */,
223 | 98345A3D1DCCE7C600E8AF88 /* CoreDataDandy.swift in Sources */,
224 | 98345A4A1DCCE7C600E8AF88 /* CoreDataValueConverter.swift in Sources */,
225 | 98345A3F1DCCE7C600E8AF88 /* Logger.swift in Sources */,
226 | 98345A431DCCE7C600E8AF88 /* PropertyDescription.swift in Sources */,
227 | 98345A401DCCE7C600E8AF88 /* MappingFinalizer.swift in Sources */,
228 | 98345A451DCCE7C600E8AF88 /* Dictionary+Dandy.swift in Sources */,
229 | 98345A421DCCE7C600E8AF88 /* PersistentStackCoordinator.swift in Sources */,
230 | 98345A481DCCE7C600E8AF88 /* NSManagedObject+Dandy.swift in Sources */,
231 | );
232 | runOnlyForDeploymentPostprocessing = 0;
233 | };
234 | /* End PBXSourcesBuildPhase section */
235 |
236 | /* Begin XCBuildConfiguration section */
237 | 985CD4DE1C617024005EB9E2 /* Debug */ = {
238 | isa = XCBuildConfiguration;
239 | buildSettings = {
240 | ALWAYS_SEARCH_USER_PATHS = NO;
241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
242 | CLANG_CXX_LIBRARY = "libc++";
243 | CLANG_ENABLE_MODULES = YES;
244 | CLANG_ENABLE_OBJC_ARC = YES;
245 | CLANG_WARN_BOOL_CONVERSION = YES;
246 | CLANG_WARN_CONSTANT_CONVERSION = YES;
247 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
248 | CLANG_WARN_EMPTY_BODY = YES;
249 | CLANG_WARN_ENUM_CONVERSION = YES;
250 | CLANG_WARN_INFINITE_RECURSION = YES;
251 | CLANG_WARN_INT_CONVERSION = YES;
252 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
253 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
254 | CLANG_WARN_UNREACHABLE_CODE = YES;
255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
256 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
257 | COPY_PHASE_STRIP = NO;
258 | CURRENT_PROJECT_VERSION = 1;
259 | DEBUG_INFORMATION_FORMAT = dwarf;
260 | ENABLE_STRICT_OBJC_MSGSEND = YES;
261 | ENABLE_TESTABILITY = YES;
262 | GCC_C_LANGUAGE_STANDARD = gnu99;
263 | GCC_DYNAMIC_NO_PIC = NO;
264 | GCC_NO_COMMON_BLOCKS = YES;
265 | GCC_OPTIMIZATION_LEVEL = 0;
266 | GCC_PREPROCESSOR_DEFINITIONS = (
267 | "DEBUG=1",
268 | "$(inherited)",
269 | );
270 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
271 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
272 | GCC_WARN_UNDECLARED_SELECTOR = YES;
273 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
274 | GCC_WARN_UNUSED_FUNCTION = YES;
275 | GCC_WARN_UNUSED_VARIABLE = YES;
276 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
277 | MTL_ENABLE_DEBUG_INFO = YES;
278 | ONLY_ACTIVE_ARCH = YES;
279 | SDKROOT = iphoneos;
280 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
281 | TARGETED_DEVICE_FAMILY = "1,2";
282 | VERSIONING_SYSTEM = "apple-generic";
283 | VERSION_INFO_PREFIX = "";
284 | };
285 | name = Debug;
286 | };
287 | 985CD4DF1C617024005EB9E2 /* Release */ = {
288 | isa = XCBuildConfiguration;
289 | buildSettings = {
290 | ALWAYS_SEARCH_USER_PATHS = NO;
291 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
292 | CLANG_CXX_LIBRARY = "libc++";
293 | CLANG_ENABLE_MODULES = YES;
294 | CLANG_ENABLE_OBJC_ARC = YES;
295 | CLANG_WARN_BOOL_CONVERSION = YES;
296 | CLANG_WARN_CONSTANT_CONVERSION = YES;
297 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
298 | CLANG_WARN_EMPTY_BODY = YES;
299 | CLANG_WARN_ENUM_CONVERSION = YES;
300 | CLANG_WARN_INFINITE_RECURSION = YES;
301 | CLANG_WARN_INT_CONVERSION = YES;
302 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
303 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
304 | CLANG_WARN_UNREACHABLE_CODE = YES;
305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
306 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
307 | COPY_PHASE_STRIP = NO;
308 | CURRENT_PROJECT_VERSION = 1;
309 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
310 | ENABLE_NS_ASSERTIONS = NO;
311 | ENABLE_STRICT_OBJC_MSGSEND = YES;
312 | GCC_C_LANGUAGE_STANDARD = gnu99;
313 | GCC_NO_COMMON_BLOCKS = YES;
314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
316 | GCC_WARN_UNDECLARED_SELECTOR = YES;
317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
318 | GCC_WARN_UNUSED_FUNCTION = YES;
319 | GCC_WARN_UNUSED_VARIABLE = YES;
320 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
321 | MTL_ENABLE_DEBUG_INFO = NO;
322 | SDKROOT = iphoneos;
323 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
324 | TARGETED_DEVICE_FAMILY = "1,2";
325 | VALIDATE_PRODUCT = YES;
326 | VERSIONING_SYSTEM = "apple-generic";
327 | VERSION_INFO_PREFIX = "";
328 | };
329 | name = Release;
330 | };
331 | 985CD4E11C617024005EB9E2 /* Debug */ = {
332 | isa = XCBuildConfiguration;
333 | buildSettings = {
334 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
335 | DEFINES_MODULE = YES;
336 | DYLIB_COMPATIBILITY_VERSION = 1;
337 | DYLIB_CURRENT_VERSION = 1;
338 | DYLIB_INSTALL_NAME_BASE = "@rpath";
339 | INFOPLIST_FILE = CoreDataDandy/Info.plist;
340 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
341 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
342 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
343 | PRODUCT_BUNDLE_IDENTIFIER = com.fuzz.CoreDataDandy;
344 | PRODUCT_NAME = "$(TARGET_NAME)";
345 | SKIP_INSTALL = YES;
346 | SWIFT_VERSION = 3.0;
347 | };
348 | name = Debug;
349 | };
350 | 985CD4E21C617024005EB9E2 /* Release */ = {
351 | isa = XCBuildConfiguration;
352 | buildSettings = {
353 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
354 | DEFINES_MODULE = YES;
355 | DYLIB_COMPATIBILITY_VERSION = 1;
356 | DYLIB_CURRENT_VERSION = 1;
357 | DYLIB_INSTALL_NAME_BASE = "@rpath";
358 | INFOPLIST_FILE = CoreDataDandy/Info.plist;
359 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
360 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
361 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
362 | PRODUCT_BUNDLE_IDENTIFIER = com.fuzz.CoreDataDandy;
363 | PRODUCT_NAME = "$(TARGET_NAME)";
364 | SKIP_INSTALL = YES;
365 | SWIFT_VERSION = 3.0;
366 | };
367 | name = Release;
368 | };
369 | /* End XCBuildConfiguration section */
370 |
371 | /* Begin XCConfigurationList section */
372 | 985CD4D21C617024005EB9E2 /* Build configuration list for PBXProject "CoreDataDandy" */ = {
373 | isa = XCConfigurationList;
374 | buildConfigurations = (
375 | 985CD4DE1C617024005EB9E2 /* Debug */,
376 | 985CD4DF1C617024005EB9E2 /* Release */,
377 | );
378 | defaultConfigurationIsVisible = 0;
379 | defaultConfigurationName = Release;
380 | };
381 | 985CD4E01C617024005EB9E2 /* Build configuration list for PBXNativeTarget "CoreDataDandy" */ = {
382 | isa = XCConfigurationList;
383 | buildConfigurations = (
384 | 985CD4E11C617024005EB9E2 /* Debug */,
385 | 985CD4E21C617024005EB9E2 /* Release */,
386 | );
387 | defaultConfigurationIsVisible = 0;
388 | defaultConfigurationName = Release;
389 | };
390 | /* End XCConfigurationList section */
391 | };
392 | rootObject = 985CD4CF1C617024005EB9E2 /* Project object */;
393 | }
394 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 981C80231DC919BF003EFB04 /* DandyModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 981C80211DC919BF003EFB04 /* DandyModel.xcdatamodeld */; };
11 | 9836F9021CBEFE2300AD92DF /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8F01CBEFE2300AD92DF /* Constants.swift */; };
12 | 9836F9041CBEFE2300AD92DF /* CoreDataDandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8F31CBEFE2300AD92DF /* CoreDataDandy.swift */; };
13 | 9836F9051CBEFE2300AD92DF /* EntityMapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8F41CBEFE2300AD92DF /* EntityMapper.swift */; };
14 | 9836F9061CBEFE2300AD92DF /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8F51CBEFE2300AD92DF /* Logger.swift */; };
15 | 9836F9071CBEFE2300AD92DF /* MappingFinalizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8F61CBEFE2300AD92DF /* MappingFinalizer.swift */; };
16 | 9836F9081CBEFE2300AD92DF /* ObjectFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8F71CBEFE2300AD92DF /* ObjectFactory.swift */; };
17 | 9836F9091CBEFE2300AD92DF /* PersistentStackCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8F81CBEFE2300AD92DF /* PersistentStackCoordinator.swift */; };
18 | 9836F90A1CBEFE2300AD92DF /* PropertyDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8F91CBEFE2300AD92DF /* PropertyDescription.swift */; };
19 | 9836F90B1CBEFE2300AD92DF /* Serializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8FA1CBEFE2300AD92DF /* Serializer.swift */; };
20 | 9836F90C1CBEFE2300AD92DF /* Dictionary+Dandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8FC1CBEFE2300AD92DF /* Dictionary+Dandy.swift */; };
21 | 9836F90D1CBEFE2300AD92DF /* NSEntityDescription+Dandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8FD1CBEFE2300AD92DF /* NSEntityDescription+Dandy.swift */; };
22 | 9836F90E1CBEFE2300AD92DF /* ConvertibleType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F8FF1CBEFE2300AD92DF /* ConvertibleType.swift */; };
23 | 9836F90F1CBEFE2300AD92DF /* CoreDataValueConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F9001CBEFE2300AD92DF /* CoreDataValueConverter.swift */; };
24 | 9836F9101CBEFE2300AD92DF /* ValueConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9836F9011CBEFE2300AD92DF /* ValueConverter.swift */; };
25 | 9868D5541DC7A2FA000C2E49 /* NSManagedObject+Dandy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9868D5531DC7A2FA000C2E49 /* NSManagedObject+Dandy.swift */; };
26 | 986BAF9F1CD2C3BB00F309C0 /* NSFileManager+Directory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 986BAF9E1CD2C3BB00F309C0 /* NSFileManager+Directory.swift */; };
27 | 986F4AC31DCA7F71009AC3C3 /* Conclusion+Finalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 986F4AC21DCA7F71009AC3C3 /* Conclusion+Finalization.swift */; };
28 | 98713E351DD4E87900986E52 /* Gossip+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E231DD4E87900986E52 /* Gossip+CoreDataClass.swift */; };
29 | 98713E361DD4E87900986E52 /* Gossip+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E241DD4E87900986E52 /* Gossip+CoreDataProperties.swift */; };
30 | 98713E371DD4E87900986E52 /* Slander+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E251DD4E87900986E52 /* Slander+CoreDataClass.swift */; };
31 | 98713E381DD4E87900986E52 /* Slander+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E261DD4E87900986E52 /* Slander+CoreDataProperties.swift */; };
32 | 98713E391DD4E87900986E52 /* Space+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E271DD4E87900986E52 /* Space+CoreDataClass.swift */; };
33 | 98713E3A1DD4E87900986E52 /* Space+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E281DD4E87900986E52 /* Space+CoreDataProperties.swift */; };
34 | 98713E3B1DD4E87900986E52 /* Hat+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E291DD4E87900986E52 /* Hat+CoreDataClass.swift */; };
35 | 98713E3C1DD4E87900986E52 /* Hat+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E2A1DD4E87900986E52 /* Hat+CoreDataProperties.swift */; };
36 | 98713E3D1DD4E87900986E52 /* Flattery+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E2B1DD4E87900986E52 /* Flattery+CoreDataClass.swift */; };
37 | 98713E3E1DD4E87900986E52 /* Flattery+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E2C1DD4E87900986E52 /* Flattery+CoreDataProperties.swift */; };
38 | 98713E3F1DD4E87900986E52 /* Conclusion+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E2D1DD4E87900986E52 /* Conclusion+CoreDataClass.swift */; };
39 | 98713E401DD4E87900986E52 /* Conclusion+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E2E1DD4E87900986E52 /* Conclusion+CoreDataProperties.swift */; };
40 | 98713E411DD4E87900986E52 /* Material+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E2F1DD4E87900986E52 /* Material+CoreDataClass.swift */; };
41 | 98713E421DD4E87900986E52 /* Material+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E301DD4E87900986E52 /* Material+CoreDataProperties.swift */; };
42 | 98713E431DD4E87900986E52 /* Plebian+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E311DD4E87900986E52 /* Plebian+CoreDataClass.swift */; };
43 | 98713E441DD4E87900986E52 /* Plebian+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E321DD4E87900986E52 /* Plebian+CoreDataProperties.swift */; };
44 | 98713E451DD4E87900986E52 /* Dandy_+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E331DD4E87900986E52 /* Dandy_+CoreDataClass.swift */; };
45 | 98713E461DD4E87900986E52 /* Dandy_+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98713E341DD4E87900986E52 /* Dandy_+CoreDataProperties.swift */; };
46 | 988F4A6A1B3632EE00E71046 /* CoreDataDandyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988F4A691B3632EE00E71046 /* CoreDataDandyTests.swift */; };
47 | /* End PBXBuildFile section */
48 |
49 | /* Begin PBXFileReference section */
50 | 981C80221DC919BF003EFB04 /* DandyModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = DandyModel.xcdatamodel; sourceTree = ""; };
51 | 9836F8F01CBEFE2300AD92DF /* Constants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
52 | 9836F8F31CBEFE2300AD92DF /* CoreDataDandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataDandy.swift; sourceTree = ""; };
53 | 9836F8F41CBEFE2300AD92DF /* EntityMapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EntityMapper.swift; sourceTree = ""; };
54 | 9836F8F51CBEFE2300AD92DF /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; };
55 | 9836F8F61CBEFE2300AD92DF /* MappingFinalizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MappingFinalizer.swift; sourceTree = ""; };
56 | 9836F8F71CBEFE2300AD92DF /* ObjectFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectFactory.swift; sourceTree = ""; };
57 | 9836F8F81CBEFE2300AD92DF /* PersistentStackCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentStackCoordinator.swift; sourceTree = ""; };
58 | 9836F8F91CBEFE2300AD92DF /* PropertyDescription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PropertyDescription.swift; sourceTree = ""; };
59 | 9836F8FA1CBEFE2300AD92DF /* Serializer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Serializer.swift; sourceTree = ""; };
60 | 9836F8FC1CBEFE2300AD92DF /* Dictionary+Dandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Dandy.swift"; sourceTree = ""; };
61 | 9836F8FD1CBEFE2300AD92DF /* NSEntityDescription+Dandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSEntityDescription+Dandy.swift"; sourceTree = ""; };
62 | 9836F8FF1CBEFE2300AD92DF /* ConvertibleType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvertibleType.swift; sourceTree = ""; };
63 | 9836F9001CBEFE2300AD92DF /* CoreDataValueConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataValueConverter.swift; sourceTree = ""; };
64 | 9836F9011CBEFE2300AD92DF /* ValueConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueConverter.swift; sourceTree = ""; };
65 | 9868D5531DC7A2FA000C2E49 /* NSManagedObject+Dandy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Dandy.swift"; sourceTree = ""; };
66 | 986BAF9E1CD2C3BB00F309C0 /* NSFileManager+Directory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFileManager+Directory.swift"; sourceTree = ""; };
67 | 986F4AC21DCA7F71009AC3C3 /* Conclusion+Finalization.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Conclusion+Finalization.swift"; sourceTree = ""; };
68 | 98713E231DD4E87900986E52 /* Gossip+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Gossip+CoreDataClass.swift"; sourceTree = ""; };
69 | 98713E241DD4E87900986E52 /* Gossip+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Gossip+CoreDataProperties.swift"; sourceTree = ""; };
70 | 98713E251DD4E87900986E52 /* Slander+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Slander+CoreDataClass.swift"; sourceTree = ""; };
71 | 98713E261DD4E87900986E52 /* Slander+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Slander+CoreDataProperties.swift"; sourceTree = ""; };
72 | 98713E271DD4E87900986E52 /* Space+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Space+CoreDataClass.swift"; sourceTree = ""; };
73 | 98713E281DD4E87900986E52 /* Space+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Space+CoreDataProperties.swift"; sourceTree = ""; };
74 | 98713E291DD4E87900986E52 /* Hat+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Hat+CoreDataClass.swift"; sourceTree = ""; };
75 | 98713E2A1DD4E87900986E52 /* Hat+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Hat+CoreDataProperties.swift"; sourceTree = ""; };
76 | 98713E2B1DD4E87900986E52 /* Flattery+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Flattery+CoreDataClass.swift"; sourceTree = ""; };
77 | 98713E2C1DD4E87900986E52 /* Flattery+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Flattery+CoreDataProperties.swift"; sourceTree = ""; };
78 | 98713E2D1DD4E87900986E52 /* Conclusion+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Conclusion+CoreDataClass.swift"; sourceTree = ""; };
79 | 98713E2E1DD4E87900986E52 /* Conclusion+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Conclusion+CoreDataProperties.swift"; sourceTree = ""; };
80 | 98713E2F1DD4E87900986E52 /* Material+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+CoreDataClass.swift"; sourceTree = ""; };
81 | 98713E301DD4E87900986E52 /* Material+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Material+CoreDataProperties.swift"; sourceTree = ""; };
82 | 98713E311DD4E87900986E52 /* Plebian+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Plebian+CoreDataClass.swift"; sourceTree = ""; };
83 | 98713E321DD4E87900986E52 /* Plebian+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Plebian+CoreDataProperties.swift"; sourceTree = ""; };
84 | 98713E331DD4E87900986E52 /* Dandy_+CoreDataClass.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dandy_+CoreDataClass.swift"; sourceTree = ""; };
85 | 98713E341DD4E87900986E52 /* Dandy_+CoreDataProperties.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dandy_+CoreDataProperties.swift"; sourceTree = ""; };
86 | 988F4A641B3632EE00E71046 /* CoreDataDandyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreDataDandyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
87 | 988F4A681B3632EE00E71046 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
88 | 988F4A691B3632EE00E71046 /* CoreDataDandyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataDandyTests.swift; sourceTree = ""; };
89 | /* End PBXFileReference section */
90 |
91 | /* Begin PBXFrameworksBuildPhase section */
92 | 988F4A611B3632EE00E71046 /* Frameworks */ = {
93 | isa = PBXFrameworksBuildPhase;
94 | buildActionMask = 2147483647;
95 | files = (
96 | );
97 | runOnlyForDeploymentPostprocessing = 0;
98 | };
99 | /* End PBXFrameworksBuildPhase section */
100 |
101 | /* Begin PBXGroup section */
102 | 9836F8EF1CBEFE2300AD92DF /* CoreDataDandy */ = {
103 | isa = PBXGroup;
104 | children = (
105 | 9836F8F01CBEFE2300AD92DF /* Constants.swift */,
106 | 9836F8F11CBEFE2300AD92DF /* Core */,
107 | 9836F8FB1CBEFE2300AD92DF /* Extensions */,
108 | 9836F8FE1CBEFE2300AD92DF /* Value Conversion */,
109 | );
110 | name = CoreDataDandy;
111 | path = ../Sources;
112 | sourceTree = "";
113 | };
114 | 9836F8F11CBEFE2300AD92DF /* Core */ = {
115 | isa = PBXGroup;
116 | children = (
117 | 9836F8F31CBEFE2300AD92DF /* CoreDataDandy.swift */,
118 | 9836F8F41CBEFE2300AD92DF /* EntityMapper.swift */,
119 | 9836F8F51CBEFE2300AD92DF /* Logger.swift */,
120 | 9836F8F61CBEFE2300AD92DF /* MappingFinalizer.swift */,
121 | 9836F8F71CBEFE2300AD92DF /* ObjectFactory.swift */,
122 | 9836F8F81CBEFE2300AD92DF /* PersistentStackCoordinator.swift */,
123 | 9836F8F91CBEFE2300AD92DF /* PropertyDescription.swift */,
124 | 9836F8FA1CBEFE2300AD92DF /* Serializer.swift */,
125 | );
126 | path = Core;
127 | sourceTree = "";
128 | };
129 | 9836F8FB1CBEFE2300AD92DF /* Extensions */ = {
130 | isa = PBXGroup;
131 | children = (
132 | 9836F8FC1CBEFE2300AD92DF /* Dictionary+Dandy.swift */,
133 | 9836F8FD1CBEFE2300AD92DF /* NSEntityDescription+Dandy.swift */,
134 | 9868D5531DC7A2FA000C2E49 /* NSManagedObject+Dandy.swift */,
135 | 986BAF9E1CD2C3BB00F309C0 /* NSFileManager+Directory.swift */,
136 | );
137 | path = Extensions;
138 | sourceTree = "";
139 | };
140 | 9836F8FE1CBEFE2300AD92DF /* Value Conversion */ = {
141 | isa = PBXGroup;
142 | children = (
143 | 9836F8FF1CBEFE2300AD92DF /* ConvertibleType.swift */,
144 | 9836F9001CBEFE2300AD92DF /* CoreDataValueConverter.swift */,
145 | 9836F9011CBEFE2300AD92DF /* ValueConverter.swift */,
146 | );
147 | path = "Value Conversion";
148 | sourceTree = "";
149 | };
150 | 9867478B1CBBFD6400559E35 /* Models */ = {
151 | isa = PBXGroup;
152 | children = (
153 | 98713E231DD4E87900986E52 /* Gossip+CoreDataClass.swift */,
154 | 98713E241DD4E87900986E52 /* Gossip+CoreDataProperties.swift */,
155 | 98713E251DD4E87900986E52 /* Slander+CoreDataClass.swift */,
156 | 98713E261DD4E87900986E52 /* Slander+CoreDataProperties.swift */,
157 | 98713E271DD4E87900986E52 /* Space+CoreDataClass.swift */,
158 | 98713E281DD4E87900986E52 /* Space+CoreDataProperties.swift */,
159 | 98713E291DD4E87900986E52 /* Hat+CoreDataClass.swift */,
160 | 98713E2A1DD4E87900986E52 /* Hat+CoreDataProperties.swift */,
161 | 98713E2B1DD4E87900986E52 /* Flattery+CoreDataClass.swift */,
162 | 98713E2C1DD4E87900986E52 /* Flattery+CoreDataProperties.swift */,
163 | 98713E2D1DD4E87900986E52 /* Conclusion+CoreDataClass.swift */,
164 | 98713E2E1DD4E87900986E52 /* Conclusion+CoreDataProperties.swift */,
165 | 98713E2F1DD4E87900986E52 /* Material+CoreDataClass.swift */,
166 | 98713E301DD4E87900986E52 /* Material+CoreDataProperties.swift */,
167 | 98713E311DD4E87900986E52 /* Plebian+CoreDataClass.swift */,
168 | 98713E321DD4E87900986E52 /* Plebian+CoreDataProperties.swift */,
169 | 98713E331DD4E87900986E52 /* Dandy_+CoreDataClass.swift */,
170 | 98713E341DD4E87900986E52 /* Dandy_+CoreDataProperties.swift */,
171 | 986F4AC21DCA7F71009AC3C3 /* Conclusion+Finalization.swift */,
172 | );
173 | path = Models;
174 | sourceTree = "";
175 | };
176 | 988F4A591B3632DC00E71046 = {
177 | isa = PBXGroup;
178 | children = (
179 | 9836F8EF1CBEFE2300AD92DF /* CoreDataDandy */,
180 | 988F4A661B3632EE00E71046 /* CoreDataDandyTests */,
181 | 988F4A651B3632EE00E71046 /* Products */,
182 | );
183 | sourceTree = "";
184 | };
185 | 988F4A651B3632EE00E71046 /* Products */ = {
186 | isa = PBXGroup;
187 | children = (
188 | 988F4A641B3632EE00E71046 /* CoreDataDandyTests.xctest */,
189 | );
190 | name = Products;
191 | sourceTree = "";
192 | };
193 | 988F4A661B3632EE00E71046 /* CoreDataDandyTests */ = {
194 | isa = PBXGroup;
195 | children = (
196 | 981C80211DC919BF003EFB04 /* DandyModel.xcdatamodeld */,
197 | 9867478B1CBBFD6400559E35 /* Models */,
198 | 988F4A691B3632EE00E71046 /* CoreDataDandyTests.swift */,
199 | 988F4A671B3632EE00E71046 /* Supporting Files */,
200 | );
201 | path = CoreDataDandyTests;
202 | sourceTree = "";
203 | };
204 | 988F4A671B3632EE00E71046 /* Supporting Files */ = {
205 | isa = PBXGroup;
206 | children = (
207 | 988F4A681B3632EE00E71046 /* Info.plist */,
208 | );
209 | name = "Supporting Files";
210 | sourceTree = "";
211 | };
212 | /* End PBXGroup section */
213 |
214 | /* Begin PBXNativeTarget section */
215 | 988F4A631B3632EE00E71046 /* CoreDataDandyTests */ = {
216 | isa = PBXNativeTarget;
217 | buildConfigurationList = 988F4A6B1B3632EE00E71046 /* Build configuration list for PBXNativeTarget "CoreDataDandyTests" */;
218 | buildPhases = (
219 | 988F4A601B3632EE00E71046 /* Sources */,
220 | 988F4A611B3632EE00E71046 /* Frameworks */,
221 | 988F4A621B3632EE00E71046 /* Resources */,
222 | );
223 | buildRules = (
224 | );
225 | dependencies = (
226 | );
227 | name = CoreDataDandyTests;
228 | productName = CoreDataDandyTests;
229 | productReference = 988F4A641B3632EE00E71046 /* CoreDataDandyTests.xctest */;
230 | productType = "com.apple.product-type.bundle.unit-test";
231 | };
232 | /* End PBXNativeTarget section */
233 |
234 | /* Begin PBXProject section */
235 | 988F4A5A1B3632DC00E71046 /* Project object */ = {
236 | isa = PBXProject;
237 | attributes = {
238 | LastSwiftUpdateCheck = 0710;
239 | LastUpgradeCheck = 0800;
240 | TargetAttributes = {
241 | 988F4A631B3632EE00E71046 = {
242 | CreatedOnToolsVersion = 6.3.2;
243 | LastSwiftMigration = 0800;
244 | };
245 | };
246 | };
247 | buildConfigurationList = 988F4A5D1B3632DC00E71046 /* Build configuration list for PBXProject "CoreDataDandyTests" */;
248 | compatibilityVersion = "Xcode 3.2";
249 | developmentRegion = English;
250 | hasScannedForEncodings = 0;
251 | knownRegions = (
252 | en,
253 | );
254 | mainGroup = 988F4A591B3632DC00E71046;
255 | productRefGroup = 988F4A651B3632EE00E71046 /* Products */;
256 | projectDirPath = "";
257 | projectRoot = "";
258 | targets = (
259 | 988F4A631B3632EE00E71046 /* CoreDataDandyTests */,
260 | );
261 | };
262 | /* End PBXProject section */
263 |
264 | /* Begin PBXResourcesBuildPhase section */
265 | 988F4A621B3632EE00E71046 /* Resources */ = {
266 | isa = PBXResourcesBuildPhase;
267 | buildActionMask = 2147483647;
268 | files = (
269 | );
270 | runOnlyForDeploymentPostprocessing = 0;
271 | };
272 | /* End PBXResourcesBuildPhase section */
273 |
274 | /* Begin PBXSourcesBuildPhase section */
275 | 988F4A601B3632EE00E71046 /* Sources */ = {
276 | isa = PBXSourcesBuildPhase;
277 | buildActionMask = 2147483647;
278 | files = (
279 | 9836F90F1CBEFE2300AD92DF /* CoreDataValueConverter.swift in Sources */,
280 | 9868D5541DC7A2FA000C2E49 /* NSManagedObject+Dandy.swift in Sources */,
281 | 9836F90A1CBEFE2300AD92DF /* PropertyDescription.swift in Sources */,
282 | 98713E371DD4E87900986E52 /* Slander+CoreDataClass.swift in Sources */,
283 | 9836F90C1CBEFE2300AD92DF /* Dictionary+Dandy.swift in Sources */,
284 | 9836F9081CBEFE2300AD92DF /* ObjectFactory.swift in Sources */,
285 | 986F4AC31DCA7F71009AC3C3 /* Conclusion+Finalization.swift in Sources */,
286 | 988F4A6A1B3632EE00E71046 /* CoreDataDandyTests.swift in Sources */,
287 | 98713E451DD4E87900986E52 /* Dandy_+CoreDataClass.swift in Sources */,
288 | 9836F9051CBEFE2300AD92DF /* EntityMapper.swift in Sources */,
289 | 9836F9071CBEFE2300AD92DF /* MappingFinalizer.swift in Sources */,
290 | 98713E411DD4E87900986E52 /* Material+CoreDataClass.swift in Sources */,
291 | 986BAF9F1CD2C3BB00F309C0 /* NSFileManager+Directory.swift in Sources */,
292 | 9836F9021CBEFE2300AD92DF /* Constants.swift in Sources */,
293 | 98713E431DD4E87900986E52 /* Plebian+CoreDataClass.swift in Sources */,
294 | 98713E3F1DD4E87900986E52 /* Conclusion+CoreDataClass.swift in Sources */,
295 | 9836F9041CBEFE2300AD92DF /* CoreDataDandy.swift in Sources */,
296 | 98713E3C1DD4E87900986E52 /* Hat+CoreDataProperties.swift in Sources */,
297 | 98713E3D1DD4E87900986E52 /* Flattery+CoreDataClass.swift in Sources */,
298 | 98713E461DD4E87900986E52 /* Dandy_+CoreDataProperties.swift in Sources */,
299 | 9836F90B1CBEFE2300AD92DF /* Serializer.swift in Sources */,
300 | 98713E441DD4E87900986E52 /* Plebian+CoreDataProperties.swift in Sources */,
301 | 98713E381DD4E87900986E52 /* Slander+CoreDataProperties.swift in Sources */,
302 | 9836F9101CBEFE2300AD92DF /* ValueConverter.swift in Sources */,
303 | 98713E3A1DD4E87900986E52 /* Space+CoreDataProperties.swift in Sources */,
304 | 98713E361DD4E87900986E52 /* Gossip+CoreDataProperties.swift in Sources */,
305 | 98713E3E1DD4E87900986E52 /* Flattery+CoreDataProperties.swift in Sources */,
306 | 9836F90D1CBEFE2300AD92DF /* NSEntityDescription+Dandy.swift in Sources */,
307 | 9836F9091CBEFE2300AD92DF /* PersistentStackCoordinator.swift in Sources */,
308 | 98713E401DD4E87900986E52 /* Conclusion+CoreDataProperties.swift in Sources */,
309 | 981C80231DC919BF003EFB04 /* DandyModel.xcdatamodeld in Sources */,
310 | 98713E391DD4E87900986E52 /* Space+CoreDataClass.swift in Sources */,
311 | 9836F9061CBEFE2300AD92DF /* Logger.swift in Sources */,
312 | 98713E421DD4E87900986E52 /* Material+CoreDataProperties.swift in Sources */,
313 | 98713E351DD4E87900986E52 /* Gossip+CoreDataClass.swift in Sources */,
314 | 9836F90E1CBEFE2300AD92DF /* ConvertibleType.swift in Sources */,
315 | 98713E3B1DD4E87900986E52 /* Hat+CoreDataClass.swift in Sources */,
316 | );
317 | runOnlyForDeploymentPostprocessing = 0;
318 | };
319 | /* End PBXSourcesBuildPhase section */
320 |
321 | /* Begin XCBuildConfiguration section */
322 | 988F4A5E1B3632DC00E71046 /* Debug */ = {
323 | isa = XCBuildConfiguration;
324 | buildSettings = {
325 | CLANG_WARN_BOOL_CONVERSION = YES;
326 | CLANG_WARN_CONSTANT_CONVERSION = YES;
327 | CLANG_WARN_EMPTY_BODY = YES;
328 | CLANG_WARN_ENUM_CONVERSION = YES;
329 | CLANG_WARN_INFINITE_RECURSION = YES;
330 | CLANG_WARN_INT_CONVERSION = YES;
331 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
332 | CLANG_WARN_UNREACHABLE_CODE = YES;
333 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
334 | ENABLE_STRICT_OBJC_MSGSEND = YES;
335 | ENABLE_TESTABILITY = YES;
336 | GCC_NO_COMMON_BLOCKS = YES;
337 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
338 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
339 | GCC_WARN_UNDECLARED_SELECTOR = YES;
340 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
341 | GCC_WARN_UNUSED_FUNCTION = YES;
342 | GCC_WARN_UNUSED_VARIABLE = YES;
343 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
344 | ONLY_ACTIVE_ARCH = YES;
345 | PROVISIONING_PROFILE = "";
346 | SDKROOT = iphoneos;
347 | };
348 | name = Debug;
349 | };
350 | 988F4A5F1B3632DC00E71046 /* Release */ = {
351 | isa = XCBuildConfiguration;
352 | buildSettings = {
353 | CLANG_WARN_BOOL_CONVERSION = YES;
354 | CLANG_WARN_CONSTANT_CONVERSION = YES;
355 | CLANG_WARN_EMPTY_BODY = YES;
356 | CLANG_WARN_ENUM_CONVERSION = YES;
357 | CLANG_WARN_INFINITE_RECURSION = YES;
358 | CLANG_WARN_INT_CONVERSION = YES;
359 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
360 | CLANG_WARN_UNREACHABLE_CODE = YES;
361 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
362 | ENABLE_STRICT_OBJC_MSGSEND = YES;
363 | ENABLE_TESTABILITY = YES;
364 | GCC_NO_COMMON_BLOCKS = YES;
365 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
366 | GCC_WARN_ABOUT_RETURN_TYPE = YES;
367 | GCC_WARN_UNDECLARED_SELECTOR = YES;
368 | GCC_WARN_UNINITIALIZED_AUTOS = YES;
369 | GCC_WARN_UNUSED_FUNCTION = YES;
370 | GCC_WARN_UNUSED_VARIABLE = YES;
371 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
372 | PROVISIONING_PROFILE = "";
373 | SDKROOT = iphoneos;
374 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
375 | };
376 | name = Release;
377 | };
378 | 988F4A6C1B3632EE00E71046 /* Debug */ = {
379 | isa = XCBuildConfiguration;
380 | buildSettings = {
381 | ALWAYS_SEARCH_USER_PATHS = NO;
382 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
383 | CLANG_CXX_LIBRARY = "libc++";
384 | CLANG_ENABLE_MODULES = YES;
385 | CLANG_ENABLE_OBJC_ARC = YES;
386 | CLANG_WARN_BOOL_CONVERSION = YES;
387 | CLANG_WARN_CONSTANT_CONVERSION = YES;
388 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
389 | CLANG_WARN_EMPTY_BODY = YES;
390 | CLANG_WARN_ENUM_CONVERSION = YES;
391 | CLANG_WARN_INT_CONVERSION = YES;
392 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
393 | CLANG_WARN_UNREACHABLE_CODE = YES;
394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
395 | COPY_PHASE_STRIP = NO;
396 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
397 | ENABLE_STRICT_OBJC_MSGSEND = YES;
398 | ENABLE_TESTABILITY = NO;
399 | FRAMEWORK_SEARCH_PATHS = "";
400 | GCC_C_LANGUAGE_STANDARD = gnu99;
401 | GCC_DYNAMIC_NO_PIC = NO;
402 | GCC_NO_COMMON_BLOCKS = YES;
403 | GCC_OPTIMIZATION_LEVEL = 0;
404 | GCC_PREPROCESSOR_DEFINITIONS = (
405 | "DEBUG=1",
406 | "$(inherited)",
407 | "TEST=1",
408 | );
409 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
410 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
411 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
412 | GCC_WARN_UNDECLARED_SELECTOR = YES;
413 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
414 | GCC_WARN_UNUSED_FUNCTION = YES;
415 | GCC_WARN_UNUSED_VARIABLE = YES;
416 | INFOPLIST_FILE = CoreDataDandyTests/Info.plist;
417 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
418 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
419 | MTL_ENABLE_DEBUG_INFO = YES;
420 | ONLY_ACTIVE_ARCH = YES;
421 | OTHER_SWIFT_FLAGS = "-D DEBUG";
422 | PRODUCT_BUNDLE_IDENTIFIER = "fuzz.$(PRODUCT_NAME:rfc1034identifier)";
423 | PRODUCT_NAME = "$(TARGET_NAME)";
424 | PROVISIONING_PROFILE = "";
425 | SDKROOT = iphoneos;
426 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
427 | SWIFT_VERSION = 3.0;
428 | };
429 | name = Debug;
430 | };
431 | 988F4A6D1B3632EE00E71046 /* Release */ = {
432 | isa = XCBuildConfiguration;
433 | buildSettings = {
434 | ALWAYS_SEARCH_USER_PATHS = NO;
435 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
436 | CLANG_CXX_LIBRARY = "libc++";
437 | CLANG_ENABLE_MODULES = YES;
438 | CLANG_ENABLE_OBJC_ARC = YES;
439 | CLANG_WARN_BOOL_CONVERSION = YES;
440 | CLANG_WARN_CONSTANT_CONVERSION = YES;
441 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
442 | CLANG_WARN_EMPTY_BODY = YES;
443 | CLANG_WARN_ENUM_CONVERSION = YES;
444 | CLANG_WARN_INT_CONVERSION = YES;
445 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
446 | CLANG_WARN_UNREACHABLE_CODE = YES;
447 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
448 | COPY_PHASE_STRIP = NO;
449 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
450 | ENABLE_NS_ASSERTIONS = NO;
451 | ENABLE_STRICT_OBJC_MSGSEND = YES;
452 | ENABLE_TESTABILITY = NO;
453 | FRAMEWORK_SEARCH_PATHS = "";
454 | GCC_C_LANGUAGE_STANDARD = gnu99;
455 | GCC_NO_COMMON_BLOCKS = YES;
456 | GCC_PREPROCESSOR_DEFINITIONS = "TEST=1";
457 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
458 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
459 | GCC_WARN_UNDECLARED_SELECTOR = YES;
460 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
461 | GCC_WARN_UNUSED_FUNCTION = YES;
462 | GCC_WARN_UNUSED_VARIABLE = YES;
463 | INFOPLIST_FILE = CoreDataDandyTests/Info.plist;
464 | IPHONEOS_DEPLOYMENT_TARGET = 9.2;
465 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
466 | MTL_ENABLE_DEBUG_INFO = NO;
467 | PRODUCT_BUNDLE_IDENTIFIER = "fuzz.$(PRODUCT_NAME:rfc1034identifier)";
468 | PRODUCT_NAME = "$(TARGET_NAME)";
469 | SDKROOT = iphoneos;
470 | SWIFT_VERSION = 3.0;
471 | VALIDATE_PRODUCT = YES;
472 | };
473 | name = Release;
474 | };
475 | /* End XCBuildConfiguration section */
476 |
477 | /* Begin XCConfigurationList section */
478 | 988F4A5D1B3632DC00E71046 /* Build configuration list for PBXProject "CoreDataDandyTests" */ = {
479 | isa = XCConfigurationList;
480 | buildConfigurations = (
481 | 988F4A5E1B3632DC00E71046 /* Debug */,
482 | 988F4A5F1B3632DC00E71046 /* Release */,
483 | );
484 | defaultConfigurationIsVisible = 0;
485 | defaultConfigurationName = Debug;
486 | };
487 | 988F4A6B1B3632EE00E71046 /* Build configuration list for PBXNativeTarget "CoreDataDandyTests" */ = {
488 | isa = XCConfigurationList;
489 | buildConfigurations = (
490 | 988F4A6C1B3632EE00E71046 /* Debug */,
491 | 988F4A6D1B3632EE00E71046 /* Release */,
492 | );
493 | defaultConfigurationIsVisible = 0;
494 | defaultConfigurationName = Debug;
495 | };
496 | /* End XCConfigurationList section */
497 |
498 | /* Begin XCVersionGroup section */
499 | 981C80211DC919BF003EFB04 /* DandyModel.xcdatamodeld */ = {
500 | isa = XCVersionGroup;
501 | children = (
502 | 981C80221DC919BF003EFB04 /* DandyModel.xcdatamodel */,
503 | );
504 | currentVersion = 981C80221DC919BF003EFB04 /* DandyModel.xcdatamodel */;
505 | path = DandyModel.xcdatamodeld;
506 | sourceTree = "";
507 | versionGroupType = wrapper.xcdatamodel;
508 | };
509 | /* End XCVersionGroup section */
510 | };
511 | rootObject = 988F4A5A1B3632DC00E71046 /* Project object */;
512 | }
513 |
--------------------------------------------------------------------------------
/Tests/CoreDataDandyTests/CoreDataDandyTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CoreDataDandyTests.swift
3 | // CoreDataDandyTests
4 | //
5 | // Created by Noah Blake on 6/20/15.
6 | // Copyright © 2015 Fuzz Productions, LLC. All rights reserved.
7 | //
8 | // This code is distributed under the terms and conditions of the MIT license.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a copy
11 | // of this software and associated documentation files (the "Software"), to
12 | // deal in the Software without restriction, including without limitation the
13 | // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
14 | // sell copies of the Software, and to permit persons to whom the Software is
15 | // furnished to do so, subject to the following conditions:
16 | //
17 | // The above copyright notice and this permission notice shall be included in
18 | // all copies or substantial portions of the Software.
19 | //
20 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 | // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
26 | // IN THE SOFTWARE.
27 |
28 | import XCTest
29 | import CoreData
30 |
31 | class CoreDataDandyTests: XCTestCase {
32 |
33 | override func setUp() {
34 | CoreDataDandy.wake("DandyModel")
35 | CoreDataValueConverter.dateFormatter.dateStyle = .long
36 | CoreDataValueConverter.dateFormatter.timeStyle = .short
37 | super.setUp()
38 | }
39 | override func tearDown() {
40 | Dandy.tearDown()
41 | super.tearDown()
42 | }
43 | // MARK: - Initialization and deinitialization -
44 |
45 | /// When the clean up completes, no data should remain.
46 | func testCleanUp() {
47 | Dandy.insert(Dandy_.self)
48 | Dandy.save()
49 | let initialResultsCount = try! Dandy.fetch(Dandy_.self)?.count
50 | Dandy.tearDown()
51 | let finalResultsCount = try! Dandy.fetch(Dandy_.self)?.count
52 | XCTAssert(initialResultsCount == 1 && finalResultsCount == 0, "Pass")
53 | }
54 |
55 | // MARK: - Saves -
56 |
57 | /// After a save, the size of the persistent store should increase
58 | func testSave() {
59 | let expectation = self.expectation(description: "save")
60 | do {
61 | let fileSizeKey = FileAttributeKey("NSFileSize")
62 | let unsavedData = try FileManager.default.attributesOfItem(atPath: PersistentStackCoordinator.persistentStoreURL.path)[fileSizeKey] as! Int
63 | for i in 0...100000 {
64 | let dandy = Dandy.insert(Dandy_.self)
65 | dandy?.setValue("\(i)", forKey: "dandyID")
66 | }
67 | Dandy.save({ (error) in
68 | do {
69 | let savedData = try FileManager.default.attributesOfItem(atPath: PersistentStackCoordinator.persistentStoreURL.path)[fileSizeKey] as! Int
70 | XCTAssert(savedData > unsavedData, "Pass")
71 | expectation.fulfill()
72 | } catch {
73 | XCTAssert(false, "Failure to retrieive file attributes.")
74 | expectation.fulfill()
75 | }
76 | })
77 | } catch {
78 | XCTAssert(false, "Failure to retrieive file attributes.")
79 | expectation.fulfill()
80 | }
81 | self.waitForExpectations(timeout: 20, handler: { (error) -> Void in })
82 | }
83 |
84 | // MARK: - Object insertions, deletions, and fetches -
85 |
86 | /// Objects should be insertable.
87 | func testObjectInsertion() {
88 | let dandy = Dandy.insert(Dandy_.self)
89 | XCTAssert(dandy != nil, "Pass")
90 | XCTAssert(try! Dandy.fetch(Dandy_.self)?.count == 1, "Pass")
91 | }
92 |
93 | /// Objects should be insertable in multiples.
94 | func testMultipleObjectInsertion() {
95 | for _ in 0...2 {
96 | Dandy.insert(Dandy_.self)
97 | }
98 | XCTAssert(try! Dandy.fetch(Dandy_.self)?.count == 3, "Pass")
99 | }
100 |
101 | /// Objects marked with the `@unique` primaryKey should not be inserted more than once.
102 | func testUniqueObjectInsertion() {
103 | Dandy.insert(Space.self)
104 | Dandy.insert(Space.self)
105 | XCTAssert(try! Dandy.fetch(Space.self)?.count == 1, "Pass")
106 | }
107 |
108 | /// Passing an invalid entity name should result in warning emission and a nil return
109 | func testInvalidObjectInsertion() {
110 | class ZZZ: NSManagedObject {}
111 |
112 | let object = Dandy.insert(ZZZ.self)
113 | XCTAssert(object == nil, "Pass")
114 | }
115 |
116 | /// After a value has been inserted with a primary key, the next fetch for it should return it and it alone.
117 | func testUniqueObjectMaintenance() {
118 | let dandy = Dandy.insertUnique(Dandy_.self, identifiedBy: "WILDE")
119 | dandy?.setValue("An author, let's say", forKey: "bio")
120 | let repeatedDandy = Dandy.insertUnique(Dandy_.self, identifiedBy: "WILDE")
121 | let dandies = try! Dandy.fetch(Dandy_.self)?.count
122 | XCTAssert(dandies == 1 && (repeatedDandy!.value(forKey: "bio") as! String == "An author, let's say"), "Pass")
123 | }
124 |
125 | /// Objects should be fetchable via typical NSPredicate configured NSFetchRequests.
126 | func testPredicateFetch() {
127 | let wilde = Dandy.insertUnique(Dandy_.self, identifiedBy: "WILDE")!
128 | wilde.setValue("An author, let's say", forKey: "bio")
129 | let byron = Dandy.insertUnique(Dandy_.self, identifiedBy: "BYRON")!
130 | byron.setValue("A poet, let's say", forKey: "bio")
131 | let dandies = try! Dandy.fetch(Dandy_.self)?.count
132 | let byrons = try! Dandy.fetch(Dandy_.self, filterBy: NSPredicate(format: "bio == %@", "A poet, let's say"))?.count
133 | XCTAssert(dandies == 2 && byrons == 1, "Pass")
134 | }
135 |
136 | /// After a fetch for an object with a primaryKey of the wrong type should undergo type conversion and
137 | /// resolve correctly..
138 | func testPrimaryKeyTypeConversion() {
139 | let dandy = Dandy.insertUnique(Dandy_.self, identifiedBy: 1)
140 | dandy?.setValue("A poet, let's say", forKey: "bio")
141 | let repeatedDandy = Dandy.insertUnique(Dandy_.self, identifiedBy: "1")
142 | let dandies = try! Dandy.fetch(Dandy_.self)?.count
143 | XCTAssert(dandies == 1 && (repeatedDandy!.value(forKey: "bio") as! String == "A poet, let's say"), "Pass")
144 | }
145 |
146 | /// Mistaken use of a primaryKey identifying function for singleton objects should not lead to unexpected
147 | /// behavior.
148 | func testSingletonsIgnorePrimaryKey() {
149 | let space = Dandy.insertUnique(Space.self, identifiedBy: "name")
150 | space?.setValue("The Gogol Empire, let's say", forKey: "name")
151 | let repeatedSpace = Dandy.insertUnique(Space.self, identifiedBy: "void")
152 | let spaces = try! Dandy.fetch(Space.self)?.count
153 | XCTAssert(spaces == 1 && (repeatedSpace!.value(forKey: "name") as! String == "The Gogol Empire, let's say"), "Pass")
154 | }
155 |
156 | /// The convenience function for fetching objects by primary key should return a unique object that has been inserted.
157 | func testUniqueObjectFetch() {
158 | let dandy = Dandy.insertUnique(Dandy_.self, identifiedBy: "WILDE")
159 | dandy?.setValue("An author, let's say", forKey: "bio")
160 | let fetchedDandy = Dandy.fetchUnique(Dandy_.self, identifiedBy: "WILDE")!
161 | XCTAssert((fetchedDandy.value(forKey: "bio") as! String == "An author, let's say"), "Pass")
162 | }
163 |
164 | /// If a primary key is not specified for an object, the fetch should fail and emit a warning.
165 | func testUnspecifiedPrimaryKeyValueUniqueObjectFetch() {
166 | let plebian = Dandy.insertUnique(Plebian.self, identifiedBy: "plebianID")
167 | XCTAssert(plebian == nil, "Pass")
168 | }
169 |
170 | /// A deleted object should not be represented in the database
171 | func testObjectDeletion() {
172 | let space = Dandy.insertUnique(Space.self, identifiedBy: "name")
173 | let previousSpaceCount = try! Dandy.fetch(Space.self)?.count
174 | let expectation = self.expectation(description: "Object deletion")
175 | Dandy.delete(space!) {
176 | let newSpaceCount = try! Dandy.fetch(Space.self)?.count
177 | XCTAssert(previousSpaceCount == 1 && newSpaceCount == 0, "Pass")
178 | expectation.fulfill()
179 | }
180 | self.waitForExpectations(timeout: 0.5, handler: { (error) -> Void in })
181 | }
182 |
183 | // MARK: - Persistent Stack -
184 |
185 | /// The managed object model associated with the stack coordinator should be consistent with DandyModel.xcdatamodel.
186 | func testPersistentStackManagerObjectModelConstruction() {
187 | let persistentStackCoordinator = PersistentStackCoordinator(managedObjectModelName: "DandyModel")
188 | XCTAssert(persistentStackCoordinator.managedObjectModel.entities.count == 9, "Pass")
189 | }
190 |
191 | /// The parentContext of the mainContext should be the privateContext. Changes to the structure of
192 | /// Dandy's persistent stack will be revealed with this test.
193 | func testPersistentStackManagerObjectContextConstruction() {
194 | let persistentStackCoordinator = PersistentStackCoordinator(managedObjectModelName: "DandyModel")
195 | XCTAssert(persistentStackCoordinator.mainContext.parent === persistentStackCoordinator.privateContext, "Pass")
196 | }
197 |
198 | /// The privateContext should share a reference to the `DandyStackCoordinator's` persistentStoreCoordinator.
199 | func testPersistentStoreCoordinatorConnection() {
200 | let persistentStackCoordinator = PersistentStackCoordinator(managedObjectModelName: "DandyModel")
201 | persistentStackCoordinator.connectPrivateContextToPersistentStoreCoordinator()
202 | XCTAssert(persistentStackCoordinator.privateContext.persistentStoreCoordinator! === persistentStackCoordinator.persistentStoreCoordinator, "Pass")
203 | }
204 | /// Resetting `DandyStackCoordinator's` should remove pre-existing persistent stores and create a new one.
205 | func testPersistentStoreReset() {
206 | let persistentStackCoordinator = PersistentStackCoordinator(managedObjectModelName: "DandyModel")
207 | let oldPersistentStore = persistentStackCoordinator.persistentStoreCoordinator.persistentStores.first!
208 | persistentStackCoordinator.resetPersistentStore()
209 | let newPersistentStore = persistentStackCoordinator.persistentStoreCoordinator.persistentStores.first!
210 | XCTAssert((newPersistentStore !== oldPersistentStore), "Pass")
211 | }
212 |
213 | /// When initialization completes, the completion closure should execute.
214 | func testPersistentStackManagerConnectionClosureExecution() {
215 | let expectation = self.expectation(description: "initialization")
216 | let persistentStackCoordinator = PersistentStackCoordinator(managedObjectModelName: "DandyModel", persistentStoreConnectionCompletion: {
217 | XCTAssert(true, "Pass.")
218 | expectation.fulfill()
219 | })
220 | persistentStackCoordinator.connectPrivateContextToPersistentStoreCoordinator()
221 | self.waitForExpectations(timeout: 5, handler: { (error) -> Void in })
222 | }
223 |
224 | // MARK: - Value conversions -
225 |
226 | /// Conversions to undefined types should not occur
227 | func testUndefinedTypeConversion() {
228 | let result: Any? = CoreDataValueConverter.convert("For us, life is five minutes of introspection", to: .undefinedAttributeType)
229 | XCTAssert(result == nil, "Pass")
230 | }
231 |
232 | /// Test non-conforming protocol type conversion
233 | func testNonConformingProtocolTypeConversion() {
234 | let result: Any? = CoreDataValueConverter.convert(["life", "is", "five", "minutes", "of", "introspection"], to: .stringAttributeType)
235 | XCTAssert(result == nil, "Pass")
236 | }
237 |
238 | /// A type converts to the same type should undergo no changes
239 | func testSameTypeConversion() {
240 | let string: Any? = CoreDataValueConverter.convert("For us, life is five minutes of introspection", to: .stringAttributeType)
241 | XCTAssert(string is String, "Pass")
242 |
243 | let integer: Any? = CoreDataValueConverter.convert(Int(1), to: .integer64AttributeType)
244 | XCTAssert(integer is Int, "Pass")
245 |
246 | let float: Any? = CoreDataValueConverter.convert(Float(1), to: .floatAttributeType)
247 | XCTAssert(float is Float, "Pass")
248 |
249 | let double: Any? = CoreDataValueConverter.convert(Double(1), to: .doubleAttributeType)
250 | XCTAssert(double is Double, "Pass")
251 |
252 | let date: Any? = CoreDataValueConverter.convert(Date(), to: .dateAttributeType)
253 | XCTAssert(date is Date, "Pass")
254 |
255 | let encodedString = "suave".data(using: String.Encoding.utf8, allowLossyConversion: true)
256 | let data: Any? = CoreDataValueConverter.convert(encodedString!, to: .binaryDataAttributeType)
257 | XCTAssert(data is Data, "Pass")
258 | }
259 |
260 | /// NSData objects should be convertible to Strings.
261 | func testDataToStringConversion() {
262 | let expectation: NSString = "testing string"
263 | let input: Data? = expectation.data(using: String.Encoding.utf8.rawValue)
264 | let result = CoreDataValueConverter.convert(input!, to: .stringAttributeType) as? NSString
265 | XCTAssert(result == expectation, "")
266 | }
267 |
268 | /// Numbers should be convertible to Strings.
269 | func testNumberToStringConversion() {
270 | let input = 123455
271 | let result = CoreDataValueConverter.convert(input, to: .stringAttributeType) as? String
272 | XCTAssert(result == "123455", "")
273 | }
274 |
275 | /// Numbers should be convertible to NSDecimalNumbers
276 | func testNumberToDecimalConversion() {
277 | let integer = Int(7)
278 | var result = CoreDataValueConverter.convert(integer, to: .decimalAttributeType) as? NSDecimalNumber
279 | XCTAssert(result == NSDecimalNumber(value: integer), "Pass")
280 |
281 | let float = Float(7.070000171661375488)
282 | result = CoreDataValueConverter.convert(float, to: .decimalAttributeType) as? NSDecimalNumber
283 | XCTAssert(result == NSDecimalNumber(value: float), "Pass")
284 |
285 | let double = Double(7.070000171661375488)
286 | result = CoreDataValueConverter.convert(double, to: .decimalAttributeType) as? NSDecimalNumber
287 | XCTAssert(result == NSDecimalNumber(value: double), "Pass")
288 | }
289 |
290 | /// Numbers should be convertible to Doubles
291 | func testNumberToDoubleConversion() {
292 | let integer = Int(7)
293 | var result = CoreDataValueConverter.convert(integer, to: .doubleAttributeType) as? Double
294 | XCTAssert(result == Double(integer), "Pass")
295 |
296 | let float = Float(7)
297 | result = CoreDataValueConverter.convert(float, to: .doubleAttributeType) as? Double
298 | XCTAssert(result == Double(float), "Pass")
299 | }
300 |
301 | /// Numbers should be convertible to Floats
302 | func testNumberToFloatConversion() {
303 | let integer = Int(7)
304 | var result = CoreDataValueConverter.convert(integer, to: .floatAttributeType) as? Float
305 | XCTAssert(result == Float(integer), "Pass")
306 |
307 | let double = Double(7.07)
308 | result = CoreDataValueConverter.convert(double, to: .floatAttributeType) as? Float
309 | XCTAssert(result == Float(double), "Pass")
310 | }
311 |
312 | /// Numbers should be convertible to NSData
313 | func testNumberToDataConversion() {
314 | let input = 7
315 |
316 | var integer = Int(input)
317 | var result = CoreDataValueConverter.convert(integer, to: .binaryDataAttributeType) as? Data
318 | XCTAssert(result == Data(bytes: &integer, count: MemoryLayout.size), "Pass")
319 |
320 | var float = Float(input)
321 | result = CoreDataValueConverter.convert(float, to: .binaryDataAttributeType) as? Data
322 | XCTAssert(result == Data(bytes: &float, count: MemoryLayout.size), "Pass")
323 |
324 | var double = Double(input)
325 | result = CoreDataValueConverter.convert(double, to: .binaryDataAttributeType) as? Data
326 | XCTAssert(result == Data(bytes: &double, count: MemoryLayout.size), "Pass")
327 |
328 | }
329 |
330 | /// Numbers should be convertible to NSDates.
331 | func testNumberToDateConversion() {
332 | let now = Date()
333 | let expectation = Double(now.timeIntervalSince1970)
334 | let result = CoreDataValueConverter.convert(expectation, to: .dateAttributeType) as? Date
335 | let resultAsDouble = Double(result!.timeIntervalSince1970)
336 | XCTAssert(resultAsDouble == expectation, "")
337 | }
338 |
339 | /// Numbers should be convertible to Booleans.
340 | func testNumberToBooleanConversion() {
341 | var input = -1
342 | var result = CoreDataValueConverter.convert(input, to: .booleanAttributeType) as? NSNumber
343 | XCTAssert(result?.boolValue == nil, "")
344 |
345 | input = 1
346 | result = CoreDataValueConverter.convert(input, to: .booleanAttributeType) as? NSNumber
347 | XCTAssert(result?.boolValue == true, "")
348 |
349 | input = 0
350 | result = CoreDataValueConverter.convert(input, to: .booleanAttributeType) as? NSNumber
351 | XCTAssert(result?.boolValue == false, "")
352 |
353 | input = 99
354 | result = CoreDataValueConverter.convert(input, to: .booleanAttributeType) as? NSNumber
355 | XCTAssert(result == true, "Pass")
356 | }
357 |
358 | /// NSDates should be convertible to Strings.
359 | func testDateToStringConversion() {
360 | let now = Date()
361 | let expectation = CoreDataValueConverter.dateFormatter.string(from: now)
362 | let result = CoreDataValueConverter.convert(now, to: .stringAttributeType) as? String
363 | XCTAssert(result == expectation, "")
364 | }
365 |
366 | /// NSDates should be convertible to Decimals.
367 | func testDateToDecimalConversion() {
368 | let now = Date()
369 | let expectation = NSDecimalNumber(value: now.timeIntervalSince(Date(timeIntervalSince1970: 0)) as Double)
370 | let result = CoreDataValueConverter.convert(now, to: .decimalAttributeType) as! NSDecimalNumber
371 | XCTAssert(result.floatValue - expectation.floatValue < 5, "")
372 | }
373 |
374 | /// NSDates should be convertible to Doubles.
375 | func testDateToDoubleConversion() {
376 | let now = Date()
377 | let expectation = NSNumber(value: now.timeIntervalSince(Date(timeIntervalSince1970: 0)) as Double)
378 | let result = CoreDataValueConverter.convert(now, to: .doubleAttributeType) as! NSNumber
379 | XCTAssert(result.floatValue - expectation.floatValue < 5, "")
380 | }
381 |
382 | /// NSDates should be convertible to Floats.
383 | func testDateToFloatConversion() {
384 | let now = Date()
385 | let expectation = NSNumber(value: Float(now.timeIntervalSince(Date(timeIntervalSince1970: 0))) as Float)
386 | let result = CoreDataValueConverter.convert(now, to: .floatAttributeType) as! NSNumber
387 | XCTAssert(result.floatValue - expectation.floatValue < 5, "")
388 | }
389 |
390 | /// NSDates should be convertible to Ints.
391 | func testDateToIntConversion() {
392 | let now = Date()
393 | let expectation = NSNumber(value: Int(now.timeIntervalSince(Date(timeIntervalSince1970: 0))) as Int)
394 | let result = CoreDataValueConverter.convert(now, to: .integer32AttributeType) as! NSNumber
395 | XCTAssert(result.floatValue - expectation.floatValue < 5, "")
396 | }
397 |
398 | /// A variety of strings should be convertible to Booleans.
399 | func testStringToBooleanConversion() {
400 | var testString = "Yes"
401 | var result: NSNumber?
402 |
403 | result = CoreDataValueConverter.convert(testString, to: .booleanAttributeType) as? NSNumber
404 | XCTAssert(result?.boolValue == true, "")
405 |
406 | testString = "trUe"
407 | result = CoreDataValueConverter.convert(testString, to: .booleanAttributeType) as? NSNumber
408 | XCTAssert(result?.boolValue == true, "")
409 |
410 | testString = "1"
411 | result = CoreDataValueConverter.convert(testString, to: .booleanAttributeType) as? NSNumber
412 | XCTAssert(result?.boolValue == true, "")
413 |
414 | testString = "NO"
415 | result = CoreDataValueConverter.convert(testString, to: .booleanAttributeType) as? NSNumber
416 | XCTAssert(result?.boolValue == false, "")
417 |
418 | testString = "false"
419 | result = CoreDataValueConverter.convert(testString, to: .booleanAttributeType) as? NSNumber
420 | XCTAssert(result?.boolValue == false, "")
421 |
422 | testString = "0"
423 | result = CoreDataValueConverter.convert(testString, to: .booleanAttributeType) as? NSNumber
424 | XCTAssert(result?.boolValue == false, "")
425 |
426 |
427 | testString = "undefined"
428 | result = CoreDataValueConverter.convert(testString, to: .booleanAttributeType) as? NSNumber
429 | XCTAssert(result == nil, "")
430 | }
431 |
432 | /// Strings should be convertible to Integers.
433 | func testStringToIntConversion() {
434 | var input = "123"
435 | var result = CoreDataValueConverter.convert(input, to: .integer64AttributeType) as? Int
436 | XCTAssert(result == 123, "")
437 |
438 | input = "not an int"
439 | result = CoreDataValueConverter.convert(input, to: .integer64AttributeType) as? Int
440 | XCTAssert(result == nil, "")
441 | }
442 |
443 | /// NSStrings should be convertible to NSDecimalNumbers
444 | func testStringToDecimalConversion() {
445 | let expectation = NSDecimalNumber(value: 7.070000171661375488 as Float)
446 | let result = CoreDataValueConverter.convert("7.070000171661375488", to: .decimalAttributeType) as? NSDecimalNumber
447 | XCTAssert(result == expectation, "Pass")
448 | }
449 |
450 | /// NSStrings should be convertible to Doubles
451 | func testStringToDoubleConversion() {
452 | let expectation = Double(7.07)
453 | let result = CoreDataValueConverter.convert("7.07", to: .doubleAttributeType) as? Double
454 | XCTAssert(result == expectation, "Pass")
455 | }
456 |
457 | /// NSStrings should be convertible to Floats
458 | func testStringToFloatConversion() {
459 | let expectation = Float(7.07)
460 | let result = CoreDataValueConverter.convert("7.07", to: .floatAttributeType) as? Float
461 | XCTAssert(result == expectation, "Pass")
462 | }
463 |
464 | /// Strings should be convertible to Data objects.
465 | func testStringToDataConversion() {
466 | let input = "Long long Time ago"
467 | let expectedResult = input.data(using: String.Encoding.utf8)!
468 | let result = CoreDataValueConverter.convert(input, to: .binaryDataAttributeType) as? Data
469 | XCTAssert((result! == expectedResult) == true, "")
470 | }
471 |
472 | /// Strings should be convertible to NSDates.
473 | func testStringToDateConversion() {
474 | let now = Date()
475 | let nowAsString = CoreDataValueConverter.dateFormatter.string(from: now)
476 | let result = CoreDataValueConverter.convert(nowAsString, to: .dateAttributeType) as? Date
477 | let resultAsString = CoreDataValueConverter.dateFormatter.string(from: result!)
478 | XCTAssert(resultAsString == nowAsString, "")
479 | }
480 |
481 | // MARK: - Mapping -
482 |
483 | /// NSEntityDescriptions should be retrievable by name.
484 | func testEntityDescriptionFromString() {
485 | let expected = NSEntityDescription.entity(forEntityName: "Dandy_", in: Dandy.coordinator.mainContext)
486 | let result = NSEntityDescription.forEntity("Dandy_")!
487 | XCTAssert(expected == result, "Pass")
488 | }
489 |
490 | /// NSEntityDescriptions should be retrievable their model's underlying type.
491 | func testEntityDescriptionFromType() {
492 | let expected = NSEntityDescription.entity(forEntityName: "Dandy_", in: Dandy.coordinator.mainContext)
493 | let result = NSEntityDescription.forType(Dandy_.self)!
494 | XCTAssert(expected == result, "Pass")
495 | }
496 |
497 | /// NSEntityDescriptions should correctly report whether they represent unique models or not.
498 | func testUniquenessIdentification() {
499 | let dandy = NSEntityDescription.forType(Dandy_.self)!
500 | let plebian = NSEntityDescription.forType(Plebian.self)!
501 | XCTAssert(dandy.isUnique == true && plebian.isUnique == false,
502 | "Failed to correctly identify whether an NSEntityDescription describes a unique model.")
503 | }
504 |
505 | /// NSEntityDescriptions should correctly report the property that makes an object unique.
506 | func testPrimaryKeyIdentification() {
507 | let expected = "dandyID"
508 | let dandy = NSEntityDescription.forType(Dandy_.self)!
509 | let result = dandy.primaryKey!
510 | XCTAssert(expected == result, "Pass")
511 | }
512 |
513 | /// Primary keys should be inheritable. In this case, Flattery's primaryKey should be inherited from
514 | /// its parent, Gossip.
515 | func testPrimaryKeyInheritance() {
516 | let flattery = Dandy.insert(Flattery.self)!
517 | XCTAssert(flattery.entity.primaryKey == "secret", "Pass")
518 | }
519 |
520 | /// Children should override the primaryKey of their parents. In this case, Slander's uniqueConstraint should
521 | /// override its parent's, Gossip.
522 | func testPrimaryKeyOverride() {
523 | let slander = Dandy.insert(Slander.self)!
524 | XCTAssert(slander.entity.primaryKey == "statement", "Pass")
525 | }
526 |
527 | /// Children's userInfo should contain the userInfo of their parents. In this case, Slander's userInfo should
528 | /// contain a value from its parent, Gossip.
529 | func testUserInfoHierarchyCollection() {
530 | let slanderDescription = NSEntityDescription.forType(Slander.self)!
531 | let userInfo = slanderDescription.allUserInfo!
532 | XCTAssert((userInfo["testValue"] as! String) == "testKey", "Pass")
533 | }
534 |
535 | /// Children should override userInfo of their parents. In this case, Slander's mapping decoration should
536 | /// override its parent's, Gossip.
537 | func testUserInfoOverride() {
538 | let slanderDescription = NSEntityDescription.forType(Slander.self)!
539 | let userInfo = slanderDescription.allUserInfo!
540 | XCTAssert((userInfo[PRIMARY_KEY] as! String) == "statement", "Pass")
541 | }
542 |
543 | /// Entity descriptions with no specified mapping should read into mapping dictionaries with all "same name" mapping
544 | func testSameNameMap() {
545 | let entity = NSEntityDescription.forType(Material.self)!
546 | let expectedMap = [
547 | "name": PropertyDescription(description: entity.allAttributes!["name"]!),
548 | "origin": PropertyDescription(description: entity.allAttributes!["origin"]!),
549 | "hats": PropertyDescription(description: entity.allRelationships!["hats"]!)
550 | ]
551 | let result = EntityMapper.map(entity)
552 | XCTAssert(result! == expectedMap, "Pass")
553 | }
554 |
555 | /// @mapping: @false should result in an exclusion from the map. Gossip's "secret" attribute has been specified
556 | /// as such.
557 | func testNOMappingKeywordResponse() {
558 | let entity = NSEntityDescription.forType(Gossip.self)!
559 | let expectedMap = [
560 | "details": PropertyDescription(description: entity.allAttributes!["details"]!),
561 | "topic": PropertyDescription(description: entity.allAttributes!["topic"]!),
562 | // "secret": "unmapped"
563 | "purveyor": PropertyDescription(description: entity.allRelationships!["purveyor"]!)
564 | ]
565 | let result = EntityMapper.map(entity)!
566 | XCTAssert(result == expectedMap, "Pass")
567 | }
568 |
569 | /// If an alternate keypath is specified, that keypath should appear as a key in the map. Space's "spaceState" has been specified
570 | /// to map from "state."
571 | func testAlternateKeypathMappingResponse() {
572 | let entity = NSEntityDescription.forType(Space.self)!
573 | let expectedMap = [
574 | "name": PropertyDescription(description: entity.allAttributes!["name"]!),
575 | "state": PropertyDescription(description: entity.allAttributes!["spaceState"]!)
576 | ]
577 | let result = EntityMapper.map(entity)!
578 | XCTAssert(result == expectedMap, "Pass")
579 | }
580 |
581 | // MARK: - Property description comparisons and caching -
582 |
583 | /// Comparisons of property descriptions should evaluate correctly.
584 | func testPropertyDescriptionComparison() {
585 | let entity = NSEntityDescription.forType(Space.self)!
586 | let name = PropertyDescription(description: entity.allAttributes!["name"]!)
587 | let secondName = PropertyDescription(description: entity.allAttributes!["name"]!)
588 | let state = PropertyDescription(description: entity.allAttributes!["spaceState"]!)
589 | XCTAssert(name == secondName, "Pass")
590 | XCTAssert(name != state, "Pass")
591 | }
592 |
593 | /// Simple initializations and initializations from NSCoding should yield valid objects.
594 | func testPropertyDescriptionInitialization() {
595 | let _ = PropertyDescription()
596 |
597 | let entity = NSEntityDescription.forType(Space.self)!
598 | let propertyDescription = PropertyDescription(description: entity.allAttributes!["name"]!)
599 | let pathArray = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
600 | let documentPath = pathArray.first!
601 | let archivePath = NSString(string: documentPath).appendingPathComponent("SPACE_NAME")
602 | NSKeyedArchiver.archiveRootObject(propertyDescription, toFile: archivePath)
603 | let unarchivedPropertyDescription = NSKeyedUnarchiver.unarchiveObject(withFile: archivePath) as! PropertyDescription
604 | XCTAssert(unarchivedPropertyDescription == propertyDescription, "Pass")
605 | }
606 |
607 | // MARK: - Map caching -
608 |
609 | /// The first access to an entity's map should result in that map's caching
610 | func testMapCaching() {
611 | if let entityDescription = NSEntityDescription.forType(Material.self) {
612 | EntityMapper.map(entityDescription)
613 | let entityCacheMap = EntityMapper.cachedEntityMap[String(describing: Material.self)]!
614 | XCTAssert(entityCacheMap.count > 0, "")
615 | }
616 | }
617 |
618 | /// When clean up is called, no cached maps should remain
619 | func testMapCacheCleanUp() {
620 | if let entityDescription = NSEntityDescription.forType(Material.self) {
621 | EntityMapper.map(entityDescription)
622 | let initialCacheCount = EntityMapper.cachedEntityMap.count
623 | EntityMapper.clearCache()
624 | let finalCacheCount = EntityMapper.cachedEntityMap.count
625 | XCTAssert(initialCacheCount == 1 && finalCacheCount == 0, "Pass")
626 | }
627 | }
628 |
629 | /// The creation of a new map should be performant
630 | func testPerformanceOfNewMapCreation() {
631 | self.measure {
632 | let entityDescription = NSEntityDescription.forType(Material.self)
633 | EntityMapper.map(entityDescription!)
634 | }
635 | }
636 |
637 | /// The fetching of a cached map should be performant, and more performant than the creation of a new map
638 | func testPerformanceOfCachedMapRetrieval() {
639 | let entityDescription = NSEntityDescription.forType(Material.self)!
640 | EntityMapper.map(entityDescription)
641 | self.measure {
642 | EntityMapper.map(entityDescription)
643 | }
644 | }
645 | // MARK: - Object building -
646 |
647 | /// Values should be mapped from json to an object's attributes.
648 | func testAttributeBuilding() {
649 | let space = Dandy.insert(Space.self)!
650 | let json: JSONObject = ["name": "nebulous", "state": "moderately cool"]
651 | ObjectFactory.build(space, from: json)
652 | XCTAssert(space.name == "nebulous"
653 | && space.spaceState == "moderately cool",
654 | "Pass")
655 | }
656 |
657 | /// Values should be mapped from json an object's relationships.
658 | func testRelationshipBuilding() {
659 | let gossip = Dandy.insert(Gossip.self)!
660 | let json = [
661 | "details": "At Bo Peep, unusually cool towards Isabella Brown.",
662 | "topic": "John Keats",
663 | "purveyor": [
664 | "id": 1,
665 | "name": "Lord Byron",
666 | ]
667 | ] as JSONObject
668 | ObjectFactory.build(gossip, from: json)
669 |
670 | let byron = gossip.purveyor!
671 | XCTAssert(gossip.details == "At Bo Peep, unusually cool towards Isabella Brown."
672 | && gossip.topic == "John Keats"
673 | && byron.dandyID == "1"
674 | && byron.name == "Lord Byron",
675 | "Pass")
676 | }
677 |
678 | /// Values should be recursively mapped from nested json objects.
679 | func testRecursiveObjectBuilding() {
680 | let gossip = Dandy.insert(Gossip.self)!
681 | let json = [
682 | "details": "At Bo Peep, unusually cool towards Isabella Brown.",
683 | "topic": "John Keats",
684 | "purveyor": [
685 | "id": "1",
686 | "name": "Lord Byron",
687 | "hats": [[
688 | "name": "bowler",
689 | "style": "billycock",
690 | "material": [
691 | "name": "felt",
692 | "origin": "Rome"
693 | ]
694 | ]]
695 | ]
696 | ] as JSONObject
697 | ObjectFactory.build(gossip, from: json)
698 | let byron = gossip.purveyor!
699 | let bowler = byron.hats!.anyObject() as! Hat
700 | let felt = bowler.primaryMaterial!
701 | XCTAssert(gossip.details == "At Bo Peep, unusually cool towards Isabella Brown."
702 | && gossip.topic == "John Keats"
703 | && byron.dandyID == "1"
704 | && byron.name == "Lord Byron"
705 | && bowler.name == "bowler"
706 | && bowler.styleDescription == "billycock"
707 | && felt.name == "felt"
708 | && felt.origin == "Rome",
709 | "Pass")
710 | }
711 |
712 | /// @mapping values that contain a keypath should allow access to json values via a keypath
713 | func testKeyPathBuilding() {
714 | let dandy = Dandy.insert(Dandy_.self)!
715 | let json = [
716 | "id": "BAUD",
717 | "relatedDandies": [
718 | "predecessor": [
719 | "id": "BALZ",
720 | "name": "Honoré de Balzac"
721 | ]
722 | ]
723 | ] as JSONObject
724 | ObjectFactory.build(dandy, from: json)
725 | let balzac = dandy.predecessor!
726 | XCTAssert(balzac.dandyID == "BALZ"
727 | && balzac.name == "Honoré de Balzac"
728 | && balzac.successor!.dandyID == dandy.dandyID,
729 | "Pass")
730 | }
731 |
732 | /// Property values on an object should not be overwritten if no new values are specified.
733 | func testIgnoreUnkeyedAttributesWhenBuilding() {
734 | let space = Dandy.insert(Space.self)!
735 | space.setValue("exceptionally relaxed", forKey: "spaceState")
736 | let json: JSONObject = ["name": "nebulous" as AnyObject]
737 | ObjectFactory.build(space, from: json)
738 | XCTAssert(space.value(forKey: "spaceState") as! String == "exceptionally relaxed", "Pass")
739 | }
740 |
741 | /// Property values on an object should be overwritten if new values are specified.
742 | func testOverwritesKeyedAttributesWhenBuilding() {
743 | let space = Dandy.insert(Space.self)!
744 | space.setValue("exceptionally relaxed", forKey: "spaceState")
745 | let json: JSONObject = ["state": "significant excitement"]
746 | ObjectFactory.build(space, from: json)
747 | XCTAssert(space.value(forKey: "spaceState") as! String == "significant excitement", "Pass")
748 | }
749 |
750 | /// If a single json object is passed when attempting to build a toMany relationship, it should be
751 | /// rejected.
752 | func testSingleObjectToManyRelationshipRejection() {
753 | let dandy = Dandy.insert(Dandy_.self)!
754 | let json = [
755 | "name": "bowler",
756 | "style": "billycock",
757 | "material": [
758 | "name": "felt",
759 | "origin": "Rome"
760 | ]
761 | ] as JSONObject
762 | ObjectFactory.make(PropertyDescription(description: dandy.entity.allRelationships!["hats"]!), to: dandy, from: json)
763 | XCTAssert((dandy.value(forKey: "hats") as! NSSet).count == 0, "Pass")
764 | }
765 |
766 | /// Uniqueness should play no role in whether an object can be made or not.
767 | func testNonUniqueObjectMaking() {
768 | let json: JSONObject = ["name": "Passerby" as AnyObject]
769 | let plebian = ObjectFactory.make(Plebian.self, from: json)
770 | XCTAssert(plebian != nil, "Test failed: a non-unique object could not be made.")
771 | }
772 |
773 | /// If a json array is passed when attempting to build a toOne relationship, it should be
774 | /// rejected.
775 | func testArrayOfObjectToOneRelationshipRejection() {
776 | let gossip = Dandy.insert(Gossip.self)!
777 | let json = [
778 | [
779 | "id": "1",
780 | "name": "Lord Byron"],
781 | [
782 | "id": "2",
783 | "name": "Oscar Wilde"],
784 | [
785 | "id": "3",
786 | "name": "Andre 3000"]
787 | ]
788 | ObjectFactory.make(PropertyDescription(description: gossip.entity.allRelationships!["purveyor"]!), to: gossip, from: json)
789 | XCTAssert(gossip.value(forKey: "purveyor") == nil, "Pass")
790 | }
791 |
792 | /// NSOrderedSets should be created for ordered relationships. NSSets should be created for
793 | /// unordered relationships.
794 | func testOrderedRelationshipsBuilding() {
795 | let hat = Dandy.insert(Hat.self)!
796 | let json = [
797 | [
798 | "id": "1",
799 | "name": "Lord Byron"],
800 | [
801 | "id": "2",
802 | "name": "Oscar Wilde"],
803 | [
804 | "id": "3",
805 | "name": "Andre 3000"]
806 | ]
807 | ObjectFactory.make(PropertyDescription(description: hat.entity.allRelationships!["dandies"]!), to: hat, from: json)
808 | XCTAssert(hat.value(forKey: "dandies") is NSOrderedSet && hat.dandies!.count == 3, "Pass")
809 | }
810 |
811 | /// Nil or inconvertible json values should lead to nilled out relationships.
812 | func testRelationshipNilling() {
813 | let dandy = Dandy.insert(Dandy_.self)!
814 | let json = [
815 | "id": 1,
816 | "name": "Lord Byron",
817 | "hats": [
818 | [
819 | "name": "bowler",
820 | "style": "billycock",
821 | "material": [
822 | "name": "felt",
823 | "origin": "Rome"
824 | ]
825 | ]
826 | ]
827 | ] as JSONObject
828 |
829 | let nullNilling = [
830 | "id": 1,
831 | "hats": "NULL"
832 | ] as JSONObject
833 |
834 | ObjectFactory.build(dandy, from: json)
835 | XCTAssert(dandy.hats!.count == 1, "After building from \(json), Lord Byron should have a single hat.")
836 | ObjectFactory.build(dandy, from: nullNilling)
837 | XCTAssert(dandy.hats?.count == 0, "After building hats from NULL values, Lord Byron should have no hats.")
838 |
839 | let nsNullNilling = [
840 | "id": 1,
841 | "hats": NSNull()
842 | ] as JSONObject
843 |
844 | ObjectFactory.build(dandy, from: json)
845 | XCTAssert(dandy.hats!.count == 1, "After building from \(json), Lord Byron should have a single hat.")
846 | ObjectFactory.build(dandy, from: nsNullNilling)
847 | XCTAssert(dandy.hats?.count == 0, "After building hats from NULL values, Lord Byron should have no hats.")
848 |
849 | XCTAssert((dandy.value(forKey: "hats") as! NSSet).count == 0, "Pass")
850 | }
851 |
852 | // MARK: - Object factory via CoreDataDandy -
853 |
854 | /// json containing a valid primary key should result in unique, mapped objects.
855 | func testSimpleObjectConstructionFromJSON() {
856 | let json = ["name": "Passerby"]
857 | let plebian = Dandy.upsert(Plebian.self, from: json)!
858 | XCTAssert(plebian.value(forKey: "name") as! String == "Passerby")
859 | }
860 |
861 | /// json lacking a primary key should be rejected. A nil value should be returned and a warning
862 | /// emitted.
863 | func testUniqueObjectConstructionFromJSON() {
864 | let json = ["name": "Lord Byron"]
865 | let byron = Dandy.upsert(Dandy_.self, from: json)
866 | XCTAssert(byron == nil, "Pass")
867 | }
868 |
869 | /// json lacking a primary key should be rejected. A nil value should be returned and a warning
870 | /// emitted.
871 | func testRejectionOfJSONWithoutPrimaryKeyForUniqueObject() {
872 | let json = ["name": "Lord Byron"]
873 | let byron = Dandy.upsert(Dandy_.self, from: json)
874 | XCTAssert(byron == nil, "Pass")
875 | }
876 |
877 | /// An array of objects should be returned from a json array containing mappable objects.
878 | func testObjectArrayConstruction() {
879 | var json = [JSONObject]()
880 | for i in 0...9 {
881 | json.append(["id": String(i), "name": "Morty"])
882 | }
883 | let dandies = Dandy.batchUpsert(Dandy_.self, from: json)!
884 | let countIsCorrect = dandies.count == 10
885 | var dandiesAreCorrect = true
886 | for i in 0...9 {
887 | let matchingDandies = (dandies.filter {$0.value(forKey: "dandyID")! as! String == String(i)})
888 | if matchingDandies.count != 1 {
889 | dandiesAreCorrect = false
890 | break
891 | }
892 | }
893 | XCTAssert(countIsCorrect && dandiesAreCorrect, "Pass")
894 | }
895 |
896 | /// Objects that adopt `MappingFinalizer` should invoke `finalizeMappingForJSON(_:)` at the conclusion of its
897 | /// construction.
898 | ///
899 | /// Gossip's map appends "_FINALIZE" to its content.
900 | func testMappingFinalization() {
901 | let input = "A decisively excellent affair, if a bit tawdry."
902 | let expected = "\(input)_FINALIZED"
903 | let json: JSONObject = [
904 | "id": "1" as AnyObject,
905 | "content": input as AnyObject
906 | ]
907 | let conclusion = ObjectFactory.make(Conclusion.self, from: json)!
908 | XCTAssert(conclusion.content == expected, "Pass")
909 | }
910 |
911 | // MARK: - Serialization tests -
912 |
913 | /// An object's attributes should be serializable into json.
914 | func testAttributeSerialization() {
915 | let hat = Dandy.insert(Hat.self)!
916 | hat.setValue("bowler", forKey: "name")
917 | hat.setValue("billycock", forKey: "styleDescription")
918 | let expected = [
919 | "name": "bowler",
920 | "style": "billycock"
921 | ]
922 | let result = Serializer.serialize(hat) as! [String: String]
923 | XCTAssert(result == expected, "Pass")
924 | }
925 |
926 | /// Test nil attribute exclusion from serialized json.
927 | func testNilAttributeSerializationExclusion() {
928 | let hat = Dandy.insert(Hat.self)!
929 | hat.setValue("bowler", forKey: "name")
930 | hat.setValue(nil, forKey: "styleDescription")
931 | let expected = ["name": "bowler"]
932 | let result = Serializer.serialize(hat) as! [String: String]
933 | XCTAssert(result == expected, "Pass")
934 | }
935 |
936 | /// Relationships targeted for serialization should not be mapped to a helper array unless thay are nested.
937 | func testNestedRelationshipSerializationExclusion() {
938 | let relationships = ["hats", "gossip", "predecessor"]
939 | let result = Serializer.nestedSerializationTargets(for: "hats", including: relationships)
940 | XCTAssert(result == nil, "\(result) should have been nil: no nested keypaths were included in \(relationships).")
941 | }
942 |
943 | /// Nested relationships targeted for serialization should be correctly mapped to a helper array.
944 | func testNestedRelationshipSerializationTargeting() {
945 | let relationships = ["purveyor.successor", "purveyor.hats.material", "anomaly"]
946 | let expected = ["successor", "hats.material"]
947 | let result = Serializer.nestedSerializationTargets(for: "purveyor", including: relationships)!
948 | XCTAssert(result == expected, "Pass")
949 | }
950 |
951 | /// Unspecified relationships should return no result.
952 | func testNoMatchingRelationshipsSerializationTargeting() {
953 | let relationships = ["purveyor.successor", "purveyor.hats.material"]
954 | let result = Serializer.nestedSerializationTargets(for: "anomaly", including: relationships)
955 | XCTAssert(result == nil, "Pass")
956 | }
957 |
958 | /// An object's attributes and to-one relationships should be serializaable into json.
959 | func testToOneRelationshipSerialization() {
960 | let hat = Dandy.insert(Hat.self)!
961 | hat.setValue("bowler", forKey: "name")
962 | hat.setValue("billycock", forKey: "styleDescription")
963 | let felt = Dandy.insert(Material.self)!
964 | felt.setValue("felt", forKey: "name")
965 | felt.setValue("Rome", forKey: "origin")
966 | hat.setValue(felt, forKey: "primaryMaterial")
967 | let expected = [
968 | "name": "bowler",
969 | "style": "billycock",
970 | "material": [
971 | "name": "felt",
972 | "origin": "Rome"
973 | ]
974 | ] as JSONObject
975 | let result = Serializer.serialize(hat, including:["primaryMaterial"])!
976 | XCTAssert(result == expected, "Pass")
977 | }
978 |
979 | /// An array of NSManagedObject should be serializable into json.
980 | func testObjectArraySerialization() {
981 | let byron = Dandy.insert(Dandy_.self)!
982 | byron.name = "Lord Byron"
983 | byron.dandyID = "1"
984 | let wilde = Dandy.insert(Dandy_.self)!
985 | wilde.name = "Oscar Wilde"
986 | wilde.dandyID = "2"
987 | let andre = Dandy.insert(Dandy_.self)!
988 | andre.name = "Andre 3000"
989 | andre.dandyID = "3"
990 | let expected: [JSONObject] = [[
991 | "id": "1",
992 | "name": "Lord Byron"
993 | ], [
994 | "id": "2",
995 | "name": "Oscar Wilde"], [
996 | "id": "3",
997 | "name": "Andre 3000"
998 | ]
999 | ]
1000 | let result = Serializer.serialize([byron, wilde, andre])!
1001 | XCTAssert(result == expected, "Pass")
1002 | }
1003 |
1004 | /// An object's attributes and to-many relationships should be serializaable into json.
1005 | func testToManyRelationshipSerialization() {
1006 | let byron = Dandy.insert(Dandy_.self)!
1007 | byron.name = "Lord Byron"
1008 | byron.dandyID = "1"
1009 | let bowler = Dandy.insert(Hat.self)!
1010 | bowler.name = "bowler"
1011 | bowler.styleDescription = "billycock"
1012 | let tyrolean = Dandy.insert(Hat.self)!
1013 | tyrolean.name = "tyrolean"
1014 | tyrolean.styleDescription = "alpine"
1015 | byron.hats = Set([bowler, tyrolean]) as NSSet
1016 | let expected = [
1017 | "id": "1",
1018 | "name": "Lord Byron",
1019 | "hats": [
1020 | [
1021 | "name": "bowler",
1022 | "style": "billycock"
1023 | ], [
1024 | "name": "tyrolean",
1025 | "style": "alpine"
1026 | ]
1027 | ]
1028 | ] as JSONObject
1029 | var result = Serializer.serialize(byron, including:["hats"])!
1030 | result["hats"] = (result["hats"] as! NSArray).sortedArray(using: [NSSortDescriptor(key: "name", ascending: true)])
1031 | XCTAssert(result == expected, "Pass")
1032 | }
1033 |
1034 | /// An object's attributes and relationship tree should be serializaable into json.
1035 | func testNestedRelationshipSerialization() {
1036 | let gossip = Dandy.insert(Gossip.self)!
1037 | gossip.details = "At Bo Peep, unusually cool towards Isabella Brown."
1038 | gossip.topic = "John Keats"
1039 | let byron = Dandy.insert(Dandy_.self)!
1040 | byron.name = "Lord Byron"
1041 | byron.dandyID = "1"
1042 | let bowler = Dandy.insert(Hat.self)!
1043 | bowler.name = "bowler"
1044 | bowler.styleDescription = "billycock"
1045 | byron.hats = Set([bowler]) as NSSet
1046 | gossip.purveyor = byron
1047 | let expected = [
1048 | "details": "At Bo Peep, unusually cool towards Isabella Brown.",
1049 | "topic": "John Keats",
1050 | "purveyor": [
1051 | "id": "1",
1052 | "name": "Lord Byron",
1053 | "hats": [
1054 | [
1055 | "name": "bowler",
1056 | "style": "billycock",
1057 | ]
1058 | ]
1059 | ]
1060 | ] as JSONObject
1061 | let result = Serializer.serialize(gossip, including: ["purveyor.hats"]) as! [String: NSObject]
1062 | XCTAssert(result == expected, "Pass")
1063 | }
1064 |
1065 | // MARK: - Extension tests -
1066 |
1067 | /// Entries from one dictionary should add correctly to another dictionary of the same type
1068 | func testDictionaryEntryAddition() {
1069 | var balzac = ["name": "Honoré de Balzac"]
1070 | let profession = ["profession": "author"]
1071 | balzac.addEntriesFrom(profession)
1072 | XCTAssert(balzac["name"] == "Honoré de Balzac" && balzac["profession"] == "author", "Pass")
1073 | }
1074 |
1075 | /// Values in a dictionary should be accessible via keypath
1076 | func testValueForKeyPathExtraction() {
1077 | let hats = [[
1078 | "name": "bowler",
1079 | "style": "billycock",
1080 | "material": [
1081 | "name": "felt",
1082 | "origin": "Rome"
1083 | ]
1084 | ]]
1085 |
1086 | let gossip = [
1087 | "details": "At Bo Peep, unusually cool towards Isabella Brown.",
1088 | "topic": "John Keats",
1089 | "purveyor": [
1090 | "id": "1",
1091 | "name": "Lord Byron",
1092 | "hats": hats
1093 | ]
1094 | ] as JSONObject
1095 |
1096 | let value: [JSONObject]? = _value(at: "purveyor.hats", of: gossip)
1097 | XCTAssert(value! == hats, "Pass")
1098 | }
1099 |
1100 | /// Directories that exist should be reported as existing.
1101 | func testDirectoryExistenceEvaluation() {
1102 | let applications = FileManager.default.urls(for: .applicationDirectory, in: .userDomainMask).last!
1103 | var components = applications.pathComponents
1104 | components.removeLast()
1105 | let root = URL(string: NSString.path(withComponents: components))!
1106 | XCTAssert(FileManager.directoryExists(at: root) == true, "Incorrectly evaluated existence of Application directory")
1107 | }
1108 |
1109 | /// Directories that do not exists should be reported as non-existent.
1110 | func testDirectoryInexistenceEvaluation() {
1111 | let url = URL(string: "file://lord-byron/diary/screeds/creditors")!
1112 | XCTAssert(FileManager.directoryExists(at: url) == false, "Incorrectly evaluated existence of nonsense directory")
1113 | }
1114 |
1115 | /// One should be able to create directories.
1116 | func testDirectoryCreation() {
1117 | let applications = FileManager.default.urls(for: .applicationDirectory, in: .userDomainMask).last!
1118 | var components = applications.pathComponents
1119 | components.removeLast()
1120 | let root = URL(string: "file://" + NSString.path(withComponents: components))!
1121 |
1122 | let directory = root.appendingPathComponent("lord-byron", isDirectory: true)
1123 |
1124 | if FileManager.directoryExists(at: directory) {
1125 | // Clean up previous executions of the test if trace of them remains.
1126 | try! FileManager.default.removeItem(at: directory)
1127 | }
1128 |
1129 | try! FileManager.createDirectory(at: directory)
1130 | XCTAssert(FileManager.directoryExists(at: directory) == true, "Failed to create directory")
1131 | }
1132 |
1133 | // MARK: - Warning emission tests -
1134 |
1135 | /// Dandy should format log messages consistently.
1136 | func testMessageFormatting() {
1137 | let warning = "Failed to serialize object Dandy including relationships hats"
1138 | let log = format(warning)
1139 | XCTAssert(log == "(CoreDataDandy) warning: " + warning, "Pass")
1140 | }
1141 |
1142 | /// Dandy should format NSErrors into log messages consistently.
1143 | func testWarningErrorEmission() {
1144 | let error = NSError(domain: "DANDY_FETCH_ERROR", code: 1, userInfo: nil)
1145 | let warning = "Failed to serialize object Dandy including relationships hats"
1146 | let log = format(warning, with: error)
1147 | XCTAssert(log == "(CoreDataDandy) warning: " + warning + " Error:\n" + error.description, "Pass")
1148 | }
1149 | }
1150 |
1151 | //
1152 | /// For testing purposes only, json comparators.
1153 | func ==(lhs: JSONObject, rhs: JSONObject) -> Bool {
1154 | // Dictionaries of unequal counts are not equal
1155 | if lhs.count != rhs.count { return false }
1156 | // Dictionaries that are equal must share all keys and paired values
1157 | for (key, lhValue) in lhs {
1158 | if let rhValue = rhs[key] {
1159 | switch (lhValue, rhValue) {
1160 | case let (l, r) where l is String && r is String:
1161 | return (l as! String) == (r as! String)
1162 | case let (l, r) where l is Bool && r is Bool:
1163 | return (l as! Bool) == (r as! Bool)
1164 | case let (l, r) where l is Double && r is Double:
1165 | return (l as! Double) == (r as! Double)
1166 | case let (l, r) where l is JSONObject && r is JSONObject:
1167 | return (l as! JSONObject) == (r as! JSONObject)
1168 | case let (l, r) where l is NSObject && r is NSObject:
1169 | return (l as! NSObject) == (r as! NSObject)
1170 | default:
1171 | return false
1172 | }
1173 | } else {
1174 | return false
1175 | }
1176 | }
1177 | return true
1178 | }
1179 |
1180 | fileprivate func !=(lhs: JSONObject, rhs: JSONObject) -> Bool {
1181 | return !(lhs == rhs)
1182 | }
1183 |
1184 |
1185 | fileprivate func ==(lhs: [JSONObject], rhs: [JSONObject]) -> Bool {
1186 | if lhs.count != rhs.count {
1187 | return false
1188 | }
1189 |
1190 | let paired = zip(lhs, rhs)
1191 | for (l, r) in paired {
1192 | if l != r {
1193 | return false
1194 | }
1195 | }
1196 |
1197 | return true
1198 | }
1199 |
--------------------------------------------------------------------------------