Giter VIP home page Giter VIP logo

core-data-candy's Introduction

CoreDataCandy

Extensions and mapping on CoreData entities to provide easy to use data models and fetching.

Why ?

Core Data is a great tool. Although, with the years, we felt some features were missing. You know, something more "Swifty", more declarative, easier to use. For instance, you do not want to write key paths as string to specify a property when fetching, you do not want to handle validation errors with NSValidation errors. This is what this library tries to solve: making Core Data even better by providing a range of tools to hide the inherent complexity of the framework.

Mapping

The first tool is a way to map a Core Data entity class, a NSManagedObject into a friendlier object - a DatabaseModel. That' the mapping part. This protocol can be implemented by structures or classes and offers a way to declare the mapping: a field with validation, a relationship one to many with conversion (bye-bye NSSet!) or even publishers for an entity (holding a NSFetchResultsController). The goal is really to work with friendly faces, and to use the model editor as little as possible.

Fetching

The second tool this library has to offer is easy fetching. NSPredicates and NSSortDescriptors are very powerful, though not safe. You cannot know whether a request will fail and potentially cause a program exit before executing it. And mistakes happen, especially with strings. That is why the library will make it easier and safer to specify a predicate based on key paths, to specify a target when fetching, or to specify a way to sort the data.

In a word, Core Data is powerful yet sometimes complex, so let's make it as sweet as candy.

Overview

Before you dive into the wiki, you might to know what the words above mean. Here is a short overview.

Given an entity in a Core Data model:

class PlayerEntity: NSManagedObject {
    @NSManaged var name: String?
    @NSManaged var score: Int32
    @NSManaged var age: Int16
    @NSManaged var lastGame: Date?
    @NSManaged var avatar: Data?
}

It's possible to declare a mapping model Player to interface the entity with more friendly types, after having declared the conformance of PlayerEntity to DatabaseEntity.

extension PlayerEntity: DatabaseEntity {}

struct Player: DatabaseModel {

    public let _entityWrapper: EntityWrapper<PlayerEntity>
    
    let name = UniqueField(\.name, validations: .notEmpty)
    let score = Field(\.score) // Int
    let age = Field(\.age, validations: .isIn(18...24))
    let lastGame = Field(\.lastGame) //  Date?
    let avatar = Field(\.avatar, as: UIImage.self) // UIImage?
    
    init(entity: PlayerEntity) {
        _entityWrapper = .init(entity: entity)
        // perform additional code on the entity if needed
    }
}

Also, it's possible to use advanced fetching on both a Core Data NSManagedObject class or its associated DatabaseModel (here on the model).

Player.request()
    .first()
    .where(\.name == "Zerator")
    .feth(in: context) // output: Player?

Player.request()
    .all(after: 10)
    .where(\.age, .isIn(18...70))
    .sorted(by: .ascending(\.age), .descending(\.name))
    .fetch(in: context) // output: [Player]
    
Player.request()
    .first(nth: 3)
    .where(\.name, .isIn("Zerator", "Mister MV", "Maghla")).or(\.age == 20)
    .setting(\.returnsDistinctResults, to: true) // set additional properties
    .fetch(in: context) // output: [Player]

Notes

  • Storing a UIImage is done by making UIImage conform to CodableConvertible (which requires only the declaration). You can learn more in the advanced mapping section.
  • The _entityrapper property is a protocol requirement and is used to hide the entity once set, making it visible only internally in the API. That said, you are free to capture the entity reference in the initaliser to perform additional code.

Wiki

We prefer a wiki over a long Readme.

Provided

core-data-candy's People

Contributors

abridoux avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

Forkers

abridoux

core-data-candy's Issues

Add a nested compound predicate feature when fetching

Currently, the fetch request predicate specification only allows flat compound predicates:

Player.request()
    .all()
    .where(\.name == "Toto")
    .and(\.age, .isIn(20, 30, 40))
    .or(\.score > 400)

Doing so, the predicate is name == "Toto && age IN {20,30,40} OR score > 400.
There is no way to specify this predicate: name == "Toto && (age IN {20,30,40} OR score > 400).
The goal is to add nested compound predicates, either by adding the possibility for an and predicate function to have another compound operator, or with another way.

Offer multiple sorts on a relationship publisher

Currently, it's possible to specify a sort on the received values for an entity relationship:

player.publisher(for: \.games, sortedBy: .ascending(\.name))

Additional sorts should be allowed, sorting the data if the previous one returns an equalComparison.

Allow optional RawRepresentable Field

For now, if we declare a Field that is mapped to a RawRepresentable, optional values are supported for reading (eg. initial value can be nil) but it isn't supported for writing.

This has to be handled in FieldInterfaceProtocol.swift#74

Fetch an enum stored by its raw value by specifying the case

It is possible currently to fetch on an attribute with is destined to be converted an an enum value:

Animal.request().all().where(\.type == AnimalType.duck.rawValue)

It would be nice to be able to remove the boilerplate.

Animal.request().all().where(\.type == .duck)

To do so, maybe the Predicate struct should hold another generic DatabaseModel type since the mapping from a raw value to a enum is known in the model.

Prepare for release 0.2.0

  • Change the example with UIColor with CodableConvertible
  • CodableConvertible with NSObject
  • Change the architecture diagram
  • Add a MIT license
  • Change the file header

Offer a way to get a model parent

It should be possible to access a model current parent.

func parent<P: ParentInterfaceProtocol>(_ keyPath: KeyPath<Self, P>) -> P.ParentModel?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.