{"id":23152504,"url":"https://github.com/1amageek/salada","last_synced_at":"2025-08-04T04:35:20.249Z","repository":{"id":62454872,"uuid":"65707258","full_name":"1amageek/Salada","owner":"1amageek","description":"Firebase model framework Salada. Salada is the best Firebase framework.","archived":false,"fork":false,"pushed_at":"2018-04-17T04:16:24.000Z","size":8784,"stargazers_count":226,"open_issues_count":3,"forks_count":33,"subscribers_count":14,"default_branch":"master","last_synced_at":"2024-11-18T04:43:27.015Z","etag":null,"topics":["database","firebase","firebase-database","firebase-relationship","ios","salada","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/1amageek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-08-15T05:40:15.000Z","updated_at":"2024-10-10T16:55:27.000Z","dependencies_parsed_at":"2022-11-02T00:00:37.369Z","dependency_job_id":null,"html_url":"https://github.com/1amageek/Salada","commit_stats":null,"previous_names":[],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FSalada","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FSalada/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FSalada/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/1amageek%2FSalada/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/1amageek","download_url":"https://codeload.github.com/1amageek/Salada/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":230167887,"owners_count":18183846,"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":["database","firebase","firebase-database","firebase-relationship","ios","salada","swift"],"created_at":"2024-12-17T19:15:26.724Z","updated_at":"2024-12-17T19:15:27.578Z","avatar_url":"https://github.com/1amageek.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv style=\"text-align: center; width: 100%\"\u003e\n\u003cimg src=\"https://github.com/1amageek/Salada/blob/master/logo.png\", width=\"100%\"\u003e\n\n [![Version](http://img.shields.io/cocoapods/v/Salada.svg)](http://cocoapods.org/?q=Salada)\n [![Platform](http://img.shields.io/cocoapods/p/Salada.svg)](http://cocoapods.org/?q=Salada)\n [![Downloads](https://img.shields.io/cocoapods/dt/Salada.svg?label=Total%20Downloads\u0026colorB=28B9FE)](https://cocoapods.org/pods/Salada)\n\n\u003c/div\u003e\n\n# Salada 🍐\n\u003c!--\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)\n--\u003e\nSalad is a Model for Firebase database. It can handle Snapshot of Firebase easily.\n\n[Make a Model with Salada](https://medium.com/@1amageek/make-a-user-model-using-firebase-model-framework-salada-8c343fbe8800)\n\n - [x] You no longer need to create a server.  \n - [x] You no longer need to make a mock.  \n - [x] It operates in real time.  \n - [x] You can create a reactive UI.  \n\n## Requirements ❗️\n- iOS 10 or later\n- Swift 4.0 or later\n- [Firebase firestore](https://firebase.google.com/docs/database/ios/start)\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 'Salada' ` to your Podfile.\n- Run `pod install`.\n\n\n## Usage 👀\n\n### Model\n\nModel of the definition is very simple.\nTo inherit the `Object`.\n\n``` Swift\nclass User: Object {\n\n    dynamic var name: String?\n    dynamic var age: Int = 0\n    dynamic var gender: String?\n    dynamic var groups: Set\u003cString\u003e = []\n    dynamic var items: [String] = []\n    dynamic var url: URL?\n    dynamic var birth: Date?\n    dynamic var thumbnail: File?\n    dynamic var followers: Relation\u003cUser\u003e = []\n}\n```\n\nWhen you want to create a property that you want to ignore.\n\n``` Swift\n// Group\nclass Group: Object {\n    dynamic var name: String?\n    dynamic var users: Set\u003cString\u003e = []\n}\n```\n\n### Property\n\nProperty are four that can be specified in Salada.\n\n| Property | Description |\n| --- | --- |\n| String | Simple string. |\n| Number\\(Int, UInt, Double ...\\) | Simple number. |\n| URL | URL |\n| Date | date |\n| Array\\\u003cString\\\u003e | Array of strings. |\n| Set \\\u003cString\\\u003e| Array of strings. Set is used in relationships. |\n| Reation\\\u003cObject\\\u003e| Reference |\n| [String: Any] | Object |\n| AnyObject | Use encode, decode function. |\n\n⚠️ `Bool`, `Int`, `Float`, and `Double` are not supported optional types. \n\n### Save and Update\n\n**Do not forget to change the database rules.**\n\n```\n{\n  \"rules\": {\n    \".read\": true,\n    \".write\": true\n  }\n}\n// This rule is dangerous. Please change the rules according to the model\n```\n\nhttps://firebase.google.com/docs/database/security/\n\nThe new model is stored in the `save()` or `save(completion: ((NSError?, FIRDatabaseReference) -\u003e Void)?)`.\nIt is updated automatically when you change the property Model that has already been saved.\n\n``` Swift\nlet group: Group = Group()\ngroup.name = \"iOS Development Team\"\ngroup.save { (error, ref) in\n\n    do {\n        let user: User = User()\n        user.name = \"john appleseed\"\n        user.gender = \"man\"\n        user.age = 22\n        user.items = [\"Book\", \"Pen\"]\n        user.groups.insert(ref.key)\n        user.save({ (error, ref) in\n            group.users.insert(ref.key) // It is updated automatically\n        })\n    }\n\n    do {\n        let user: User = User()\n        user.name = \"Marilyn Monroe\"\n        user.gender = \"woman\"\n        user.age = 34\n        user.items = [\"Rip\"]\n        user.groups.insert(ref.key)\n        user.save({ (error, ref) in\n            group.users.insert(ref.key) // It is updated automatically\n        })\n    }\n\n}\n```\n\n\u003cimg src=\"https://github.com/1amageek/Salada/blob/master/SaladBar/sample_code_0.png\" width=\"400\"\u003e\n\n### Retrieving Data\n\n- `observeSingle(eventType: FIRDataEventType, block: ([Tsp]) -\u003e Void)`\n- `observeSingle(id: String, eventType: FIRDataEventType, block: (Tsp) -\u003e Void)`\n\n\n``` Swift\nUser.observeSingle(FIRDataEventType.Value) { (users) in\n    users.forEach({ (user) in\n        // do samething\n        if let groupId: String = user.groups.first {\n            Group.observeSingle(groupId, eventType: .Value, block: { (group) in\n                // do samething\n            })\n        }\n    })\n}\n```\n\n### Remove Data\n``` Swift\n\nif let groupId: String = user.groups.first {\n    Group.observeSingle(groupId, eventType: .Value, block: { (group) in\n        group.remove()\n    })\n}\n\n```\n\n### Custom Property\n``` Swift\nclass User: Salada.Object {\n\n    override class var _version: String {\n        return \"v1\"\n    }\n\n    dynamic var name: String?\n    dynamic var age: Int = 0\n    dynamic var gender: String?\n    dynamic var groups: Set\u003cString\u003e = []\n    dynamic var items: [String] = []\n    dynamic var location: CLLocation?\n    dynamic var url: URL?\n    dynamic var birth: Date?\n    dynamic var thumbnail: File?\n    dynamic var cover: File?\n    dynamic var type: UserType = .first\n\n    var tempName: String?\n\n    override var ignore: [String] {\n        return [\"tempName\"]\n    }\n\n    override func encode(_ key: String, value: Any?) -\u003e Any? {\n        if key == \"location\" {\n            if let location = self.location {\n                return [\"latitude\": location.coordinate.latitude, \"longitude\": location.coordinate.longitude]\n            }\n        } else if key == \"type\" {\n            return self.type.rawValue as AnyObject?\n        }\n        return nil\n    }\n\n    override func decode(_ key: String, value: Any?) -\u003e Any? {\n        if key == \"location\" {\n            if let location: [String: Double] = value as? [String: Double] {\n                self.location = CLLocation(latitude: location[\"latitude\"]!, longitude: location[\"longitude\"]!)\n                return self.location\n            }\n        } else if key == \"type\" {\n            if let type: Int = value as? Int {\n                self.type = UserType(rawValue: type)!\n                return self.type\n            }\n        }\n        return nil\n    }\n}\n```\n\n#### Upload file\n\nYou can easily save the file if you use the File.\nFile saves the File in FirebaseStorage.\n\n**Do not forget to change the storage rules.**\n\n``` Swift\nlet user: User = User()\nlet image: UIImage = UIImage(named: \"Salada\")!\nlet data: NSData = UIImagePNGRepresentation(image)!\nlet thumbnail: File = File(data: data, mimeType: .jpeg)\nuser.thumbnail = thumbnail\nuser.save({ (error, ref) in\n    // do something\n})\n```\n\n``` Swift\nlet image: UIImage = #imageLiteral(resourceName: \"salada\")\nlet data: Data = UIImageJPEGRepresentation(image, 1)!\nlet file: File = File(data: data, mimeType: .jpeg)\nitem.file = file\nlet task: FIRStorageUploadTask = item.file?.save(completion: { (metadata, error) in\n    if let error = error {\n        print(error)\n        return\n    }\n})\n```\n\n\n\n#### Download file\n\nDownload of File is also available through the File.\n\n``` Swift\nguard let user: User = self.datasource?.objectAtIndex(indexPath.item) else { return }\nuser.thumbnail?.dataWithMaxSize(1 * 2000 * 2000, completion: { (data, error) in\n    if let error: NSError = error {\n        print(error)\n        return\n    }\n    cell.imageView?.image = UIImage(data: data!)\n    cell.setNeedsLayout()\n})\n```\n\nFirebaseUI makes it even easier to access.\n\n``` Ruby\n# Only pull in FirebaseUI Storage features\npod 'FirebaseUI/Storage', '~\u003e 3.0'\n```\n\n``` Swift\nUser.observeSingle(friend, eventType: .value, block: { (user) in\n    if let user: User = user as? User {\n        if let ref: FIRStorageReference = user.thumbnail?.ref {\n            cell.imageView.sd_setImage(with: ref, placeholderImage: #imageLiteral(resourceName: \"account_placeholder\"))\n        }\n    }\n })\n```\n\n# Relationship\n\nPlease use `Relation` to create a relationship between models.\nIt can be defined by inheriting Relation class.\n\n``` swift\nclass Follower: Relation\u003cUser\u003e {\n    override class var _name: String {\n        return \"follower\"\n    }\n}\n```\n\n``` swift\nclass User: Object {\n    let followers: Follower = []\n}\n```\n\u003cimg src=\"https://github.com/1amageek/Salada/blob/master/SaladBar/sample_code_1.png\" width=\"400\"\u003e\n\n# Data Source\n\nSee SaladBar.\n\nFor example\n\n``` Swift \n// ViewController Sample\n\nvar dataSource: DataSource\u003cUser\u003e?\n\noverride func viewDidLoad() {\n    super.viewDidLoad()\n    \n    let options: Options = Options()\n    options.limit = 10\n    options.sortDescirptors = [NSSortDescriptor(key: \"age\", ascending: false)]\n    self.dataSource = DataSource(reference: User.databaseRef, options: options, block: { [weak self](changes) in\n        guard let tableView: UITableView = self?.tableView else { return }\n        \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}\n\n```\n\n``` Swift\n// TableViewDatasource\nfunc tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -\u003e Int {\n    return self.dataSource?.count ?? 0\n}\n\nfunc tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -\u003e UITableViewCell {\n    let cell: TableViewCell = tableView.dequeueReusableCell(withIdentifier: \"TableViewCell\", for: indexPath) as! TableViewCell\n    configure(cell, atIndexPath: indexPath)\n    return cell\n}\n\nfunc configure(_ cell: TableViewCell, atIndexPath indexPath: IndexPath) {\n    cell.disposer = self.dataSource?.observeObject(at: indexPath.item, block: { (user) in\n        cell.imageView?.contentMode = .scaleAspectFill\n        cell.textLabel?.text = user?.name\n    })\n}\n\nprivate func tableView(_ tableView: UITableView, didEndDisplaying cell: TableViewCell, forRowAt indexPath: IndexPath) {\n    cell.disposer?.dispose()\n}\n\nfunc tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -\u003e Bool {\n    return true\n}\n\nfunc tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {\n    if editingStyle == .delete {\n        self.dataSource?.removeObject(at: indexPath.item, cascade: true, block: { (key, error) in\n            if let error: Error = error {\n                print(error)\n            }\n        })\n    }\n}\n```\n\n## Observe\nYou can receive data changes through observation.  \nAnd easy to manage observation using `Disposer`.\n\n```swift\nclass ViewController: UIViewController {\n    private var disposer: Disposer\u003cUser\u003e?\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        disposer = User.observe(userID, eventType: .value) { user in\n             //...\n        }\n    }\n\n    deinit {\n        // ... auto remove observe internal disposer when it deinitialized.\n        // or manually and clearly dispose\n        // disposer?.dispose()\n    }\n}\n```\n\nSalada has `Disposer`, `AnyDisposer` and `NoDisposer`.  \nSee details: `Disposer.swift`\n\n\n# Reference\n\n- [Salada](https://github.com/1amageek/Salada) Firebase model framework.\n- [Tong](https://github.com/1amageek/Tong) Tong is library for using ElasticSearch with Swift.\n- [dressing](https://github.com/1amageek/dressing) Dressing provides the functionality of CloudFunctions to connect Firebase and ElasticSearch.\n\n\n# Contributing\n\nWe welcome any contributions. See the [CONTRIBUTING](https://github.com/1amageek/Salada/blob/master/CONTRIBUTING.md) file for how to get involved.  \n\nSaladaは日本製です。日本人のコントリビューター大歓迎🎉\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1amageek%2Fsalada","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F1amageek%2Fsalada","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F1amageek%2Fsalada/lists"}