ActiveRealm
ActiveRealm is Active Record library using Realm for Objective-C/Swift, inspired by ActiveRecord of Ruby on Rails.
Features
- Made in Objective-C and supports Swift
- Connects to Realm DB
- Many READ operations
- Cascade Delete by One-to-One or One-to-Many relationships
- An ActiveRealm object can be used on multi-threads
Installation
CocoaPods
ActiveRealm is available through CocoaPods. To install it, simply add the following line to your Podfile:
pod "ActiveRealm"
Manual Installation
- Install Realm
- Download latest ActiveRealm
- Drag & Drop ActiveRealm.framework into your Xcode project
Getting Started
-
Import
import ActiveRealm
-
Setup Realm
First, you configure your Realm configuration as default Realm. For detail, see this.
// Configure your Realm configuration as default Realm. let configuration = RLMRealmConfiguration.default() // Something to do RLMRealmConfiguration.setDefault(configuration)
-
Implement Models
Implement Realm models using ARMObject class. ARMObject is the subclass of RLMObject. ARMObject has the following three properties by default:
uid
,createdAt
,updatedAt
. The uid property is the primary key for an ActiveRealm model. You must use ARMObject, not RLMObject.A class name inheriting ARMObject must have the prefix:
ActiveRealm
.Your subclass of ARMObject has just properties as data saved to DB.
class ActiveRealmAuthor: ARMObject { @objc dynamic var name = "" @objc dynamic var age: NSNumber = 0 }
Next, you implement models of ARMActiveRealm's subclass. A class name inheriting ARMActiveRealm must be the same as the string excluding the
ActiveRealm
prefix of the class name inheriting ARMObject.e.g.) ActiveRealmAuthor -> Author
class Author: ARMActiveRealm { @objc var name = "" @objc var age: NSNumber = 0 }
[IMPORTANT]
Now, ActiveRealm does NOT support primitive type properties. e.g.) Int, Float, Double, Bool, etc... Please use NSNumber instead.
Relationships
You can make a relationship between two ActiveRealm subclasses by override definedRelationships
method. By making a relationship, cascade delete is possible. Use ARMRelationship class and ARMInverseRelationship class for making the relationship.
One-to-One
For example, an Author object has one UserSettings object.
First, you set related child class and .hasOne
type to ARMRelationship object in Author class.
class ActiveRealmAuthor: ARMObject {
@objc dynamic var name = ""
@objc dynamic var age: NSNumber = 0
}
// Parent
class Author: ARMActiveRealm {
@objc var name = ""
@objc var age: NSNumber = 0
override class func definedRelationships() -> [String: ARMRelationship] {
return ["userSettings": ARMRelationship(with: UserSettings.self, type: .hasOne)]
}
}
Next, you set related parent class and .belongsTo
type to ARMInverseRelationship object in UserSettings class. Then, you implement an authorID
property as the foreign key. The property name as the foreign key must be the parent class name with lower case prefix and ID added to the end.
class ActiveRealmUserSettings: ARMObject {
@objc dynamic var authorID = ""
@objc dynamic var notificationEnabled: NSNumber = false
}
// Child
class UserSettings: ARMActiveRealm {
@objc var authorID = ""
@objc var notificationEnabled: NSNumber = false
override class func definedRelationships() -> [String: ARMRelationship] {
return ["author": ARMInverseRelationship(with: Author.self, type: .belongsTo)]
}
}
One-to-Many
For example, an Article object has many Tag objects.
First, you set related child class and .hasMany
type to ARMRelationship object in Article class.
class ActiveRealmArticle: ARMObject {
@objc dynamic var title = ""
@objc dynamic var text = ""
}
class Article: ARMActiveRealm {
@objc var title = ""
@objc var text = ""
override class func definedRelationships() -> [String: ARMRelationship] {
return ["tags": ARMRelationship(with: Tag.self, type: .hasMany)]
}
}
Next, you set related parent class and .belongsTo
type to ARMInverseRelationship object in Tag class. Then, you implement an articleID
property as the foreign key. The property name as the foreign key must be the parent class name with lower case prefix and ID added to the end.
class ActiveRealmTag: ARMObject {
@objc dynamic var articleID = ""
@objc dynamic var name = ""
}
class Tag: ARMActiveRealm {
@objc var articleID = ""
@objc var name = ""
override class func definedRelationships() -> [String: ARMRelationship] {
return ["article": ARMInverseRelationship(with: Article.self, type: .belongsTo)]
}
}
Access related object(s)
You can access related object(s) through relations
property. The relations
is the dictionary, so you specify the key that is same key of the dictionary definedRelationships
method returns.
let alice = Author.findOrCreate(["name": "Alice", "age": 28])
UserSettings.findOrCreate(["authorID": alice.uid, "notificationEnabled": true])
// One-to-One
// Use relations[key].object
if let settings = alice.relations["userSettings"]?.object as? UserSettings {
// Something to do.
}
let article = Article.findOrCreate(["title": "ActiveRealm User Guide",
"text": "ActiveRealm is a library for iOS."])
Tag.findOrCreate(["articleID": article.uid, "name": "Programming"])
Tag.findOrCreate(["articleID": article.uid, "name": "iOS"])
// One-to-Many
// Use relations[key].objects
if let tags = article.relations["tags"]?.objects as? [Tag] {
// Something to do.
}
// Inverse relationship
let tag = Tag.find(["articleID": article.uid])
if let article = tag.relations["article"]?.object as? Article {
// Something to do.
}
Recommend that you implement alias of the relation property.
class Author: ARMActiveRealm {
...
// The relation property. This property is just alias.
var userSettings: UserSettings? {
guard let userSettings = relations["userSettings"]?.object as? UserSettings else { return nil }
return userSettings
}
override class func definedRelationships() -> [String: ARMRelationship] {
return ["userSettings": ARMRelationship(with: UserSettings.self, type: .hasOne)]
}
class UserSettings: ARMActiveRealm {
...
// The relation property. This property is just alias.
var author: Author {
return relations["author"]?.object as! Author
}
override class func definedRelationships() -> [String: ARMRelationship] {
return ["author": ARMInverseRelationship(with: Author.self, type: .belongsTo)]
}
}
CRUD
Create
save()
save
means INSERT if the record does not exists in the DB, otherwise UPDATE it.
// Initialize an instance.
let alice = Author()
alice.name = "Alice"
alice.age = 28
// Insert to Realm DB.
alice.save()
findOrInitialize(_:)
Find an object if exists in Realm DB. Otherwise, initialize it with specified parameters. NOT save yet.
let author = Author.findOrInitialize(["name": "Bob", "age": 55])
author.save()
findOrCreate(_:)
Find an object if exists in Realm DB. Otherwise, initialize it with specified parameters and insert it to the DB.
let author = Author.findOrCreate(["name": "Bob", "age": 55])
Read
ActiveRealm has many READ operations.
all()
Select all objects.
let authors = Author.all()
first()
Select first created object.
let tag = Tag.first()
first(limit:)
Select specified number of objects from the head.
let tags = Tag.first(limit: 10)
last()
Select last created object.
let tag = Tag.last()
last(limit:)
Select specified number of objects from the tail.
let tags = Tag.last(limit: 10)
find(ID:)
Find an object by specified ID.
let author = Author.find(ID: "XXXXXXXX-XXXX-4XXX-XXXX-XXXXXXXXXXXX")
find(_:)
Find an object by specified parameters. When multiple objects are found, select first object.
let author = Author.find(["name": "Alice", "age": 28])
find(with:)
Find an object by specified parameters. When multiple objects are found, select first object.
let author = Author.find(with: NSPredicate(format: "age > %d", 28))
findLast(_:)
Find an object by specified parameters. When multiple objects are found, select last object.
let tag = Tag.findLast(["articleID": "XXXXXXXX-XXXX-4XXX-XXXX-XXXXXXXXXXXX"])
findLast(with:)
Find an object by specified parameters. When multiple objects are found, select last object.
let tag = Tag.findLast(with: NSPredicate(format: "articleID=%@", "XXXXXXXX-XXXX-4XXX-XXXX-XXXXXXXXXXXX"))
Update
save()
let alice = Author.find(["name": "Alice"])
alice.age = 29
// Update.
alice.save()
Delete
destroy()
destroy
method performs cascade delete by default. In other words, related data are also deleted collectively.
alice.destroy()
destroy(_:)
Deletes objects and related objects searched by specified parameters.
Author.destroy(["name": "Alice"])
Author.destroy(with: NSPredicate(format: "name=%@", "Alice"))
destroyAll()
Deletes all objects and related objects at the same time.
Author.destroyAll()
If not cascade deleting, use following methods. Specify false to cascade
argument.
alice.destroy(cascade: false)
Author.destroy(["name": "Alice"], cascade: false)
Author.destroy(with: NSPredicate(format: "name=%@", "Alice"), cascade: false)
Author.destroyAll(cascade: false)
Query
all
Returns all objects of the model.
let collection = Author.query.all
where(_:)
Returns objects searched by specified parameters.
let collection = Author.query.where(["name": "Alice"])
where(predicate:)
Returns objects searched by specified searching condition with a predicate.
let collection = Author.query.where(predicate: NSPredicate(format: "age > %d", 40))
Collection
toArray
The models in the collection. Converts to a NSArray.
let collection = Author.query.all
let authors = collection.toArray
count
The number of models in the collection.
let collection = Author.query.all
let count = collection.count
first
The first model in the collection.
let collection = Author.query.all
let author = collection.first
first(limit:)
Returns specified number of the models from the head.
let collection = Author.query.all
let authors = collection.first(limit: 5)
last
The last model in the collection.
let collection = Author.query.all
let author = collection.last
last(limit:)
Returns specified number of the models from the tail.
let collection = Author.query.all
let authors = collection.last(limit: 5)
order(_:ascending:)
Sorts objects in the collection by specified property.
let collection = Author.query.all
collection.order("age", ascending: true)
at(_:)
Retrieves an object at given index.
let collection = Author.query.all
let author = collection.at(1)
pluck(_:)
Plucks properties of the model.
let collection = Author.query.all
let names = collection.pluck(["name"])
Count
Count objects.
// The number of all objects.
let count = Author.count
// The number of objects matched given condition.
let count1 = Author.query.where(["name": "Alice"]).count
let count2 = Author.query.where(predicate: NSPredicate(format: "age > %d", 40)).count
Ignored properties
ActiveRealm saves all properties in your model to the DB by default. If you don’t want to save a property, override ignoredProperties
method.
class Author: ARMActiveRealm {
@objc var name = ""
@objc var age: NSNumber = 0
// A property ignored by ActiveRealm.
@objc var shortID: String {
return String(uid.split(separator: "-").first!)
}
override class func ignoredProperties() -> [String] {
return ["shortID"]
}
}
Validation
ActiveRealm can validate data before saving a model. By default, the validation is always successful. If you want to validate data, override validateBeforeSaving
method. When validateBeforeSaving
method returns false, the data isn't saved.
class Author: ARMActiveRealm {
@objc var name = ""
@objc var age: NSNumber = 0
override class func validateBeforeSaving(_ obj: Any) -> Bool {
let author = obj as! Author
// The name must not be empty.
return !author.name.isEmpty
}
}
Conversion methods
ActiveRealm can convert properties in your model to the dictionary or JSON easily.
Converting to Dictionary
asDictionary()
class Author: ARMActiveRealm {
@objc var name = ""
@objc var age: NSNumber = 0
// A property ignored by ActiveRealm.
@objc var shortID: String {
return String(uid.split(separator: "-").first!)
}
override class func ignoredProperties() -> [String] {
return ["shortID"]
}
@objc func generation(_ obj: Author) -> NSNumber {
return NSNumber(integerLiteral: Int(age.doubleValue / 10.0) * 10)
}
}
let chris = Author.findOrCreate(["name": "Chris", "age": 32])
// Convert to a dictionary.
let dict = chris.asDictionary()
// => {
// age = 32;
// createdAt = "2019-06-07 07:45:05 +0000";
// name = Chris;
// uid = "D56F60E1-96C1-4083-A7D4-E216FF072DEA";
// updatedAt = "2019-06-07 07:45:05 +0000";
// }
asDictionary(excepted:)
Converts to a dictionary using a black list of property names.
let dict = chris.asDictionary(excepted: ["uid", "createdAt", "updatedAt"])
// => {
// age = 32;
// name = Chris;
// }
asDictionary(included:)
Converts to a dictionary using a white list of property names. This method also includes ignored properties.
let dict = chris.asDictionary(included: ["name", "age", "shortID"])
// => {
// age = 32;
// name = Chris;
// shortUID = D56F60E1;
// }
asDictionary { prop, value in }
asDictionary(excepted:) { prop, value in }
asDictionary(included:) { prop, value in }
Converts to a dictionary using a conversion logic.
// Using a conversion logic.
let dict = chris.asDictionary(included: ["uid"]) { prop, value in
if prop == "uid" {
let uuid = value as! String
return uuid.split(separator: "-").first!
}
return value
}
// => {
// uid = D56F60E1;
// }
asDictionary(addingPropertiesWith:methods:)
asDictionary(excepted:addingPropertiesWith:methods:)
asDictionary(included:addingPropertiesWith:methods:)
Converts to a dictionary adding properties with a conversion method of a target. The method name is specified by Objective-C representation.
let dict = chris.asDictionary(included: ["name"],
addingPropertiesWith: chris,
methods: ["generation": "generation:"])
// => {
// generation = 30;
// name = Chris;
// }
Converting to JSON
asJSON(), asJSONString()
The asJSON
method returns a Data type value. On the other hand, the asJSONString
method returns a String type value.
class Author: ARMActiveRealm {
@objc var name = ""
@objc var age: NSNumber = 0
// A property ignored by ActiveRealm.
@objc var shortID: String {
return String(uid.split(separator: "-").first!)
}
override class func ignoredProperties() -> [String] {
return ["shortID"]
}
@objc func generation(_ obj: Author) -> NSNumber {
return NSNumber(integerLiteral: Int(age.doubleValue / 10.0) * 10)
}
}
let chris = Author.findOrCreate(["name": "Chris", "age": 32])
// Convert to a JSON.
let json = chris.asJSONString()
// => {
// "age" : 32,
// "uid" : "66703A0B-5712-4631-83C4-DF52E1CCE15F",
// "updatedAt" : "2019-06-07 09:15:15 +0000",
// "name" : "Chris",
// "createdAt" : "2019-06-07 09:15:15 +0000"
// }
asJSON(excepted:), asJSONString(excepted:)
Converts to a JSON using a black list of property names.
let json = chris.asJSONString(excepted: ["uid", "createdAt", "updatedAt"])
// => {
// "age" : 32,
// "name" : "Chris"
// }
asJSON(included:), asJSONString(included:)
Converts to a JSON using a white list of property names. This method also includes ignored properties.
let json = chris.asJSONString(included: ["name", "age", "shortID"])
// => {
// "age" : 32,
// "name" : "Chris",
// "shortUID" : "66703A0B"
// }
asJSON { prop, value in }, asJSONString { prop, value in }
asJSON(excepted:) { prop, value in }, asJSONString(excepted:) { prop, value in }
asJSON(included:) { prop, value in }, asJSONString(included:) { prop, value in }
Converts to a JSON using a conversion logic.
let json = chris.asJSONString(included: ["uid"]) { prop, value in
if prop == "uid" {
let uuid = value as! String
return uuid.split(separator: "-").first!
}
return value
}
// => {
// "uid" : "66703A0B"
// }
asJSON(addingPropertiesWith:methods:), asJSONString(addingPropertiesWith:methods:)
asJSON(excepted:addingPropertiesWith:methods:), asJSONString(excepted:addingPropertiesWith:methods:)
asJSON(included:addingPropertiesWith:methods:), asJSONString(included:addingPropertiesWith:methods:)
Converts to a JSON adding properties with a conversion method of a target. The method name is specified by Objective-C representation.
let json = chris.asJSONString(excepted: ["uid", "createdAt", "updatedAt", "age"],
addingPropertiesWith: chris,
methods: ["generation": "generation:"])
// => {
// "generation" : 30,
// "name" : "Chris"
// }
Usage in Objective-C
Usage in Objective-C, see my sample code.
TODO
- Supports Many-to-Many relationship
- Supports NOT NULL constraint
- Supports primitive types. e.g.) NSInteger, float, double, BOOL, etc...
- Property names mapping