{"id":23152448,"url":"https://github.com/1amageek/pring","last_synced_at":"2025-04-07T13:09:07.454Z","repository":{"id":56921474,"uuid":"106388342","full_name":"1amageek/Pring","owner":"1amageek","description":"Cloud Firestore model framework for iOS - Google","archived":false,"fork":false,"pushed_at":"2019-04-25T08:59:10.000Z","size":1603,"stargazers_count":259,"open_issues_count":9,"forks_count":28,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-03-31T12:05:44.979Z","etag":null,"topics":["cloud-firestore","firebase","firebase-realtime-database","firebase-storage","firestore","swift"],"latest_commit_sha":null,"homepage":"https://firebase.google.com/docs/firestore/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/1amageek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-10-10T08:16:27.000Z","updated_at":"2023-05-03T07:36:18.000Z","dependencies_parsed_at":"2022-08-21T04:50:45.121Z","dependency_job_id":null,"html_url":"https://github.com/1amageek/Pring","commit_stats":null,"previous_names":[],"tags_count":129,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FPring","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FPring/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FPring/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FPring/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/1amageek","download_url":"https://codeload.github.com/1amageek/Pring/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247657281,"owners_count":20974345,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloud-firestore","firebase","firebase-realtime-database","firebase-storage","firestore","swift"],"created_at":"2024-12-17T19:14:46.800Z","updated_at":"2025-04-07T13:09:07.416Z","avatar_url":"https://github.com/1amageek.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n### ⚠️ Pring is deprecated \u003cbr\u003e\n\n### Please use 🧢 [Ballcap](https://github.com/1amageek/Ballcap-iOS) 🧢\u003cbr\u003e\nBallcap is new Cloud Firestore framework\n\n---\n\u003cdiv style=\"text-align: center; width: 100%\"\u003e\n\u003cimg src=\"https://github.com/1amageek/Pring/blob/master/Pring.png\" width=\"100%\"\u003e\n\n [![Version](http://img.shields.io/cocoapods/v/Pring.svg)](http://cocoapods.org/?q=Pring)\n [![Platform](http://img.shields.io/cocoapods/p/Pring.svg)](http://cocoapods.org/?q=Pring)\n [![Downloads](https://img.shields.io/cocoapods/dt/Pring.svg?label=Total%20Downloads\u0026colorB=28B9FE)](https://cocoapods.org/pods/Pring)\n\n\u003c/div\u003e\n\n[Please donate to continue development.](https://gum.co/lNNIn)\n\n\u003cimg src=\"https://github.com/1amageek/pls_donate/blob/master/kyash.jpg\" width=\"180\"\u003e\n\nhttps://github.com/1amageek/pring.ts\n\n# Pring \u003cβ\u003e\nFirestore model framework.\nThe concept of Document and Collection has been added to Firestore. Pring defines the Scheme of the Document and enables type - safe programming. SubCollection can also be defined in Scheme.\n\n[Deep Dive into the Firebase](https://github.com/1amageek/Pring/wiki/Deep-Dive-into-the-Firebase)\n\nPlease report issues [here](https://github.com/1amageek/Pring/issues/new)\n\n\n## Requirements ❗️\n- iOS 10 or later\n- Swift 4.0 or later\n- [Firebase firestore](https://firebase.google.com/docs/firestore/quickstart)\n- [Firebase storage](https://firebase.google.com/docs/storage/ios/start)\n- [Cocoapods](https://github.com/CocoaPods/CocoaPods/milestone/32) 1.4 ❗️  ` gem install cocoapods --pre `\n\n## Installation ⚙\n#### [CocoaPods](https://github.com/cocoapods/cocoapods)\n\n- Insert `pod 'Pring' ` to your Podfile.\n- Run `pod install`.\n\n\n## Feature 🎊\n\n☑️ You can define Firestore's Document scheme.\u003cbr\u003e\n☑️ Of course type safety.\u003cbr\u003e\n☑️ It seamlessly works with Firestore and Storage.\u003cbr\u003e\n☑️ You can easily associate subcollections.\u003cbr\u003e\n☑️ Support GeoPoint.\u003cbr\u003e\n\n## Design 💻\n\n[Firestore Database Design](https://speakerdeck.com/1amageek/firestore-database-design)\n\nIf you are going to use `Firestore` and make products, I recommend you to read it.\n\n## TODO ✅\n\n### \n\n### Implementation\n- [x] Implement DataType that Firestore can handle\n- [x] Implement data management\n- [x] Implement custom DataType (Specification under consideration)\n- [x] Implement linkage with Firestorage\n- [x] Implement the NestedCollection feature\n- [x] Implement the ReferenceCollection feature\n- [x] Implement DataSource\n- [x] Implement Query-enabled DataSource (Specification under consideration)\n\n### Verification (Running Unit test)\n- [x] Verify the implementation of DataType that Firestore can handle\n- [x] Verify the implementation of data management\n- [x] Verify the implementation of custom DataType\n- [x] Verify cooperation with Firestorage\n- [x] Verify the implementation of the NestedCollection feature\n- [x] Verify the implementation of the ReferenceCollection feature\n- [x] Verify the implementation of Query-enabled DataSource\n\nIf you have a Feature Request, please post an [issue](https://github.com/1amageek/Pring/issues/new).\n\n## Usage\n\nFor example..\n\n``` swift\n@objcMembers\nclass User: Object {\n    @objc enum UserType: Int {\n        case normal\n        case gold\n        case premium        \n    }\n    dynamic var type: UserType = .normal\n    dynamic var name: String?\n    dynamic var thumbnail: File?\n    dynamic var followers: ReferenceCollection\u003cUser\u003e = []\n    dynamic var items: NestedCollection\u003cItem\u003e = []\n    \n    // Custom property\n    override func encode(_ key: String, value: Any?) -\u003e Any? {\n        if key == \"type\" {\n            return self.type.rawValue\n        }\n        return nil\n    }\n\n    override func decode(_ key: String, value: Any?) -\u003e Bool {\n        if key == \"type\" {\n            self.type = UserType(rawValue: value as! Int)\n            return true\n        }\n        return false\n    }\n}\n```\n\n``` swift\n@objcMembers\nclass Item: Object {\n    dynamic var thumbnail: File?\n    dynamic var name: String? = \"OWABIISHI\"\n}\n```\n\n``` swift\n// Set an arbitrary ID\nlet user: User = User(id: \"ID\")\nuser.save()\n```\n\n``` swift\nlet userA: User = User()\nuserA.name = \"userA\"\nuserA.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)\n\nlet userB: User = User()\nuserB.name = \"userB\"\nuserB.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)\n\nlet item: Item = Item()\nitem.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)\n\nuserA.followers.insert(userB)\nuserA.items.insert(item)\nuserA.save()\n```\n\n__Important❗️__\n\nPring clearly separates save and update. This is to prevent unexpected overwriting.\nPring provides three methods of initializing Object.\n\n#### Initialization giving AutoID to Object\n```swift\nlet user: User = User()\n```\n\n#### Initialization giving arbitrary ID\n```swift\nlet user: User = User(id: \"YOUR_ID\") // isSaved false\n```\n\n#### Initialization when dealing with already saved Object\nIf you are dealing with an Object that has already been saved, please perform the following initialization.\nIn case of this initialization can not save Please update.\n```swift\nlet user: User = User(id: \"YOUR_ID\", value: [:]) // isSaved true\n```\n\nIt is the developer's responsibility to manage the saved state of the Object.\n\n\n### Scheme \n\nPring inherits Object class and defines the Model. Pring supports many data types.\n\n``` swift\n@objcMembers\nclass User: Object {\n    dynamic var array: [String]                     = [\"array\"]\n    dynamic var set: Set\u003cString\u003e                    = [\"set\"]\n    dynamic var bool: Bool                          = true\n    dynamic var binary: Data                        = \"data\".data(using: .utf8)!\n    dynamic var file: File                          = File(data: UIImageJPEGRepresentation(UIImage(named: \"\")!, 1))\n    dynamic var url: URL                            = URL(string: \"https://firebase.google.com/\")!\n    dynamic var int: Int                            = Int.max\n    dynamic var float: Double                       = Double.infinity\n    dynamic var date: Date                          = Date(timeIntervalSince1970: 100)\n    dynamic var geoPoint: GeoPoint                  = GeoPoint(latitude: 0, longitude: 0)\n    dynamic var list: List\u003cGroup\u003e                   = []    \n    dynamic var dictionary: [String: Any]           = [\"key\": \"value\"]    \n    dynamic var string: String                      = \"string\"\n    \n    let group: Reference\u003cGroup\u003e                         = .init()\n    let nestedCollection: NestedCollection\u003cItem\u003e   　　　　　　　　　　= []\n    let referenceCollection: ReferenceCollection\u003cUser\u003e  = []\n}\n```\n\n| DataType | Description |\n|---|---|\n|Array|It is Array type.|\n|Set|It is Set type.In Firestore it is expressed as `{\"value\": true}`.|\n|Bool|It is a boolean value.|\n|File|It is File type. You can save large data files.|\n|URL|It is URL type. It is saved as string in Firestore.|\n|Int|It is Int type.|\n|Float|It is Float type. In iOS, it will be a 64 bit Double type.|\n|Date|It is Date type.|\n|GeoPoint|It is GeoPoint type.|\n|List|It is Object array type.|\n|Dictionary|It is a Dictionary type. Save the structural data.|\n|nestedCollection or referenceCollection|It is SubCollection type.|\n|String|It is String type.|\n|Reference|It is Reference type. It hold `DocumentReference`|\n|Null|It is Null type.|\n|Any|It is custom type. You can specify it as a custom type if it is a class that inherits from NSObject.|\n\n⚠️ `Bool` `Int` `Float` `Double` are not supported optional type. \n\n\n### ⚙️ Manage data\n\n#### Save\nDocument can be saved only once.\n\n``` swift\nlet object: MyObject = MyObject()\nobject.save { (ref, error) in\n   // completion\n}\n```\n\n#### Retrieve\nRetrieve document with ID.\n\n``` swift\nMyObject.get(document!.id, block: { (document, error) in\n    // do something\n})\n```\n\n#### Update\nDocument has an update method.\nBe careful as it is different from [Salada](https://github.com/1amageek/Salada).\n\n``` swift\nMyObject.get(document!.id, block: { (document, error) in\n    document.string = \"newString\"\n    document.update { error in\n       // update\n    }\n})\n```\n\n#### Delete\nDelete document with ID.\n\n``` swift\nMyObject.get(document!.id, block: { (document, error) in\n    document.delete()\n})\n```\n\n#### Batched writes\n\n``` swift\nlet batch: WriteBatch = Firestore.firestore().batch()\nbatch.add(.save, object: userA)    //  ** File is not saved.\nbatch.add(.update, object: userB)\nbatch.add(.delete, object: userC)\nbatch.commit(completion: { (error) in\n  // error handling\n})\n```\n\n### List\nList can access the Object faster than NestedCollection.\nList holds data in Document, not SubCollection.\n\n```swift\n// save\nlet order: Order = Order()\ndo {\n    let orderItem: OrderItem = OrderItem()\n    orderItem.name = \"aaaa\"\n    orderItem.price = 39\n    order.items.append(orderItem)\n}\ndo {\n    let orderItem: OrderItem = OrderItem()\n    orderItem.name = \"bbb\"\n    orderItem.price = 21\n    order.items.append(orderItem)\n}\norder.save()\n```\n\nBe sure to update the parent's object when updating data.\n```swift\n// update\nOrder.get(\"ORDER_ID\") { (order, error) in\n    order.items.first.name = \"hoge\"\n    order.update()\n}\n```\n\n### 📄 File\n**Pring** has a File class because it seamlessly works with Firebase Storage.\n\n#### Save\nFile is saved with Document Save at the same time.\n\n``` swift\nlet object: MyObject = MyObject()\nobject.thumbnailImage = File(data: PNG_DATA, mimeType: .png)\nlet tasks: [String: StorageUploadTask] = object.save { (ref, error) in\n\n}\n```\n\n`save` method returns the StorageUploadTask that is set with the key.\nFor details on how to use StorageUploadTask, refer to [Firebase docs](https://firebase.google.com/docs/storage/ios/upload-files?authuser=0).\n\n``` swift\nlet task: StorageUploadTask = tasks[\"thumbnailImage\"]\n```\n\n#### Get data\nGet data with size.\n\n``` swift\nlet task: StorageDownloadTask = object.thumbnail.getData(100000, block: { (data, error) in\n    // do something\n})\n```\n\n#### Update\nIf the Document is already saved, please use update method.\n`update` method also returns StorageUploadTask.\nRunning update method automatically deletes old files.\n\n``` swift\nlet newFile: File = File(data: PNG_DATA, mimeType: .png)\nobject.thumbnailImage = newFile\nobject.update()\n```\n\n#### Delete\nDelete it with `delete` method.\n\n``` swift\nobject.thumbnailImage = File.delete()\nobject.update()\n```\n\nIf it is held in an array, automatic file deletion is done by deleting from the array and updating it.\n``` swift\nobject.files.remove(at: 0)\nobject.update()\n```\n\n### Nested Collection \u0026 Reference Collection\n\n`NestedCollection` and `ReferenceCollection` are classes that define SubCollection.\n\nWhen holding `File` in SubCollection, saving of `File` will be executed first. When many `File`s are stored in SubCollection at once, the performance deteriorates.\n\n#### Nested Collection\n- NestedCollection nests data and saves it under the document.\n- The destination path of File is nested path.\n\n#### Reference Collection\n- ReferenceCollection saves the documentID under the document.\n- Data is saved separately.\n\n``` swift\n@objcMembers\nclass User: Object {\n    dynamic var name: String?\n    dynamic var followers: ReferenceCollection\u003cUser\u003e = []\n    dynamic var items: NestedCollection\u003cItem\u003e = []\n}\n\n@objcMembers\nclass Item: Object {\n    dynamic var thumbnail: File?\n}\n\nlet userA: User = User()\nuserA.name = \"userA\"\n\nlet userB: User = User()\nuserB.name = \"userB\"\n\nlet item: Item = Item()\nitem.thumbnail = File(data: JPEG_DATA, mimeType: .jpeg)\n\nuserA.followers.insert(userB)\nuserA.items.insert(item)\nuserA.save()\n```\n\n```swift\nlet item: Item = Item()\nuserA.items.insert(item)\nuserA.update() { error in\n  if let error = error {\n    // error handling\n    return\n  }\n  // do something\n}\n```\n\n### DataSource\n\nDataSource is a class for easy handling of data retrieval from Collection.\n``` swift\nclass DataSourceViewController: UITableViewController {\n\n    var dataSource: DataSource\u003cUser\u003e?\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        self.dataSource = User.order(by: \\User.createdAt).limit(to: 30).dataSource()\n            .on({ [weak self] (snapshot, changes) in\n                guard let tableView: UITableView = self?.tableView else { return }\n                switch changes {\n                case .initial:\n                    tableView.reloadData()\n                case .update(let deletions, let insertions, let modifications):\n                    tableView.beginUpdates()\n                    tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n                    tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n                    tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n                    tableView.endUpdates()\n                case .error(let error):\n                    print(error)\n                }\n            }).listen()\n    }\n\n    // MARK: - Table view data source\n\n    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -\u003e Int {\n        return self.dataSource?.count ?? 0\n    }\n\n    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -\u003e UITableViewCell {\n        let cell: DataSourceViewCell = tableView.dequeueReusableCell(withIdentifier: \"DataSourceViewCell\", for: indexPath) as! DataSourceViewCell\n        configure(cell, atIndexPath: indexPath)\n        return cell\n    }\n\n    func configure(_ cell: DataSourceViewCell, atIndexPath indexPath: IndexPath) {\n        guard let user: User = self.dataSource?[indexPath.item] else { return }\n        cell.textLabel?.text = user.name\n        cell.disposer = user.listen { (user, error) in\n            cell.textLabel?.text = user?.name\n        }\n    }\n\n    func tableView(_ tableView: UITableView, didEndDisplaying cell: DataSourceViewCell, forRowAt indexPath: IndexPath) {\n        cell.disposer?.dispose()\n    }\n\n    override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -\u003e Bool {\n        return true\n    }\n\n    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {\n        if editingStyle == .delete {\n            self.dataSource?.removeDocument(at: indexPath.item)\n        }\n    }\n}\n```\n\n#### SubCollection DataSource\n``` swift\nUser.get(\"USER_ID\") { (user, error) in\n    guard let user: User = user else { return }\n    self.dataSource = user.followers.order(by: \\User.createdAt).dataSource()\n        .on { (snapshot, changes) in\n            // something\n        }.listen()\n}\n```\n\n#### Synchronous Client Side Join\n```swift\n@objcMembers\nclass User: Object {\n\n    let group: Reference\u003cGroup\u003e = Reference()\n}\n```\n\nPlease add `on(parse:)` to DataSource.\n\n```swift\nself.dataSource = User.order(by: \\User.updatedAt).dataSource()\n    .on({ [weak self] (snapshot, changes) in\n        guard let tableView: UITableView = self?.tableView else { return }\n        debugPrint(\"On\")\n        switch changes {\n        case .initial:\n            tableView.reloadData()\n        case .update(let deletions, let insertions, let modifications):\n            tableView.beginUpdates()\n            tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n            tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n            tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)\n            tableView.endUpdates()\n        case .error(let error):\n            print(error)\n        }\n    })\n    .on(parse: { (snapshot, user, done) in\n        user.group.get({ (group, error) in\n            done(user)\n        })\n    })\n    .onCompleted({ (snapshot, users) in\n        debugPrint(\"completed\")\n    })\n    .listen()\n```\n\n### Query\n\n#### Get documents\n\n```swift\nUser.where(\\User.name, isEqualTo: \"name\").get { (snapshot, error) in\n    print(snapshot?.documents)\n}\n```\n\n#### Get SubCollections\n\n__WHERE__\n```swift\nlet user: User = User(id: \"user_id\")\nuser.items.where(\\Item.name, isEqualTo: \"item_name\").get { (snapshot, error) in\n    print(snapshot?.documents)\n}\n```\n\n__ORDER__\n```swift\nlet user: User = User(id: \"user_id\")\nuser.items.order(by: \\Item.updatedAt).get { (snapshot, error) in\n    print(snapshot?.documents)\n}\n```\n\n**Create DataSource from Query**\n\n```swift\nlet user: User = User(id: \"user_id\")\nuser.items\n    .where(\\Item.name, isEqualTo: \"item_name\")\n    .dataSource()\n    .on({ (snapshot, change) in\n        // do something\n    })\n    .onCompleted { (snapshot, items) in\n        print(items)\n}\n```\n\n\n## Full-text search\n\nPlease use ElasticSearch or Algolia when performing full-text search on Firebase.\nThere is a library when implementing with Swift.\n\nhttps://github.com/miuP/Algent\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1amageek%2Fpring","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F1amageek%2Fpring","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1amageek%2Fpring/lists"}