User info keys documentation

March 15, 2018 ยท View on GitHub

Below are details about how to annotate your .xcdatamodel entities and attributes to be able to leverage each Realm features when generating your Realm models with Gyro.


Summary :


Primary key

To tell which attribute will be used as a primary key, add the following 'user info' to the entity:

KeyValue
identityAttributename_of_the_attribute

Example: On the "FidelityCard" entity:

Primary Key

๐Ÿ“‘ Sample of the generated code in Java (Android)
package com.gyro.tests;

import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;

/* DO NOT EDIT | Generated by gyro */

public class FidelityCard extends RealmObject {
	
	[...]
    @PrimaryKey
    private short identifier;
	[...]
}
๐Ÿ“‘ Sample of the generated code in Swift (iOS)
/* DO NOT EDIT | Generated by gyro */

import RealmSwift
import Foundation

final class FidelityCard: Object {

  [...]
  dynamic var identifier: Int16 = 0
  
  override static func primaryKey() -> String? {
    return "identifier"
  }

}

Ignore attribute

You can decide to ignore some attributes of the .xcdatamodel file. They will not be persisted to Realm. To do so, add the following 'user info' to the attribute:

KeyValue
realmIgnoredvalue

Example: on the attribute ignored of the entity Shop:

Ignored Attribute

๐Ÿ“‘ Sample of the generated code in Java (Android)
package com.gyro.tests;

import io.realm.RealmList;
import io.realm.RealmObject;
import io.realm.annotations.Ignore;

/* DO NOT EDIT | Generated by gyro */

public class Shop extends RealmObject {

	[...]
    @Ignore
    private String ignored;
    [...]
}
๐Ÿ“‘ Sample of the generated code in Swift (iOS)
/* DO NOT EDIT | Generated by gyro */

import RealmSwift
import Foundation

final class Shop: Object {

  dynamic var ignored: String = ""

  // Specify properties to ignore (Realm won't persist these)
  override static func ignoredProperties() -> [String] {
    return ["ignored"]
  }

}


Read only (DEPRECATED)

Information about read only 'user info' On iOS/macOS, you can define attributes which are not persisted and whose value is computed dynamically. To do so, add the following 'user info' to **the attribute**
KeyValue
realmReadOnlythe_code_source_to_generate

Example: On the readOnly attribute of the Shop entity:

Read Only

๐Ÿ“‘ Sample of the generated code in Objective-C (iOS)
// DO NOT EDIT | Generated by gyro

////////////////////////////////////////////////////////////////////////////////

#pragma mark - Imports

#import "RLMShop.h"

////////////////////////////////////////////////////////////////////////////////

#pragma mark - Implementation

@implementation RLMShop

#pragma mark - Superclass Overrides

- (NSString *)readOnly
{
    return self.name;
}

@end

Inverse Relationships

In realm, when you have both A -> B and B -> A relationships, you have to choose one of those relationships to be the primary one (e.g. A -> B) โ€” that will be stored in Realm โ€” and the other inverse relationship will then be computed by code. For more information, see the related RealmSwift documentation on Inverse Relationships.

To mark a relationship as being an inverse relationship (the B -> A relationship and not the primary A -> B one), the convention in gyro is to suffix the name of the relationship with an underscore _ .

This will then generate the following code in Swift for that inverse relationship:

LinkingObjects(fromType: A.self, property: "b")`

If your inverse relationship is defined to point to a unique object (inverse of a 1-* relationship for exemple, and not a *-* one), the generated code will contain both the plural form of the computed variable and a singular form returning its first element, for convenience:

let owners = LinkingObjects(fromType: Person.self, property: "dogs")
var owner: Person? { return owners.first }

Optionnals fields and wrapper types

On Android, the -p use_wrappers flag allows you to use wrapper types (Double, Short, โ€ฆ) for optional fields instead of primitive types (double, short, โ€ฆ).

๐Ÿ“‘ Sample of the generated code in Java (Android)
package com.gyro.tests;

import io.realm.RealmObject;

/* DO NOT EDIT | Generated by gyro */

public class FidelityCard extends RealmObject {
	
  @PrimaryKey
  private short identifier;   // "optional" checkbox not checked in the xcdatamodel
  private Integer points;     // "optional" checkbox checked in the xcdatamodel

}


Support Annotations

On Android, the flag -p support_annotations allows you to annotate class attributes' getters & setters with @Nullable (if the attribute is optional) or @NonNull (if it isn't) attributes.
This option can be combined with the -p use_wrappers wrapper flag to generate a safer and more secure code in Android Studio, generating proper warnings if misused.

๐Ÿ“‘ Sample of the generated code in Java (Android)
package com.gyro.tests;

import io.realm.RealmObject;

/* DO NOT EDIT | Generated by gyro */

public class FidelityCard extends RealmObject {

    private short identifier;
    @android.support.annotation.IntRange(from=0,to=255)
    private Integer points;
    private User user;
	
	[...]
	
    @android.support.annotation.Nullable
    @android.support.annotation.IntRange(from=0,to=255)
    public Integer getPoints() {
        return points;
    }

    public void setPoints(@android.support.annotation.Nullable @android.support.annotation.IntRange(from=0,to=255) final Integer points) {
        this.points = points;
    }
    [...]
}

Furthermore, it's possible to add custom annotations to your fields. To do that, simply add the key/value pair to the UserInfos of the attribute to annotate:

KeyValue
supportAnnotationAnnotationToAdd

Example: If you wish to add the IntRange(from=0,to=255) annotation to an attribute, use the following:

Support Annotation

๐Ÿ“‘ Sample of the generated code in Java (Android)
package com.gyro.tests;

import io.realm.RealmObject;

/* DO NOT EDIT | Generated by gyro */

public class FidelityCard extends RealmObject {

    public interface Attributes {
        String IDENTIFIER = "identifier";
        String POINTS = "points";
    }

    private short identifier;
    @android.support.annotation.IntRange(from=0,to=255)
    private int points;

    public short getIdentifier() {
        return identifier;
    }

    public void setIdentifier(final short identifier) {
        this.identifier = identifier;
    }

    @android.support.annotation.IntRange(from=0,to=255)
    public int getPoints() {
        return points;
    }

    public void setPoints(@android.support.annotation.IntRange(from=0,to=255) final int points) {
        this.points = points;
    }
}

Handling enums

Sometimes, an Int attribute in the model actually represents an enum member in your model. To deal with this case, you can add the following two key/value pairs to this attribute:

KeyValue
enumTypemy_type
enumValuesmy_value_1, my_value_2, my_value_3

Note: If you also add the JSONKeyPath User Info key to your attribute in addition to enums, you'll have to add the JSONValues to also tell the mapping between the enumValues and the matching possible values found in the JSON. See the JSON Mapping below for more details.

Example: On the attribute type of the Shop entity.

enum

๐Ÿ“‘ Sample of the generated code in Java (Android)

Shop.java:

package com.gyro.tests;

/* DO NOT EDIT | Generated by gyro */

import io.realm.RealmObject;

public class Shop extends RealmObject {
    private String name;
    private String type;
    
    public String getType() {
        return type;
    }
    
    public Type getTypeEnum() {
        return Type.get(getType());
    }

    public void setTypeEnum(final Type type) {
        this.type = type.getJsonValue();
    }
	[...]
}


Type.java:

package com.gyro.tests;

/* DO NOT EDIT | Generated by gyro */

public enum Type {

    TYPE_ONE("TypeOne"),
    TYPE_TWO("TypeTwo"),
    TYPE_THREE("TypeThree");
    [...]
}
๐Ÿ“‘ Sample of the generated code in Swift (iOS)

Shop.swift:

/* DO NOT EDIT | Generated by gyro */

import RealmSwift
import Foundation

final class Shop: Object {
  [...]
  dynamic var type: String = ""
  var typeEnum: Type? {
    get {
      guard let enumValue = Type(rawValue: type) else { return nil }
      return enumValue
    }
    set { type = newValue?.rawValue ?? "" }
  }
}

Type.swift:

/* DO NOT EDIT | Generated by gyro */

enum Type: String {
  case typeOne = "TypeOne"
  case typeTwo = "TypeTwo"
  case typeThree = "TypeThree"
}

Note: For Android and Swift, each enum is created in a separate file.


Add comments to the generated classes

To make the generated code more readable, it's possible to add comments on an entity/attribute/relationship โ€” e.g. to provide a short description of what this entity/attribute/relationship is supposed to represent.

To do so, simply add the following key/value pair to your entity/attribute/relationship in your .xcdatamodel:

KeyValue
commentthe_comment_text_here

A code comment will then be generated (.swift (Swift) or .java (Android)) just before the declaration, e.g. to help the developer understand what this class/attribute/relationship is for.


JSON Mapping

You can also add the json mapping for each attribute or relationship with the following key/value pair:

KeyValue
JSONKeyPathjson_field_name

This key is only used when using the ObjectMapper or Decodable template.

Currently, this will then generate:

  • Code for ObjectMapper or Decodable on iOS (in the future we plan to generate Sourcery annotations instead so that people can choose whatever JSON library they prefer).
  • GSON annotations (@SerializedName(โ€ฆ)) for Android

Example: On the 'name' attribute of the 'Shop' entity:

JSONKeyPath

๐Ÿ“‘ Sample of the generated code in Java (Android)

Sur Android, nous utilisons la librairie GSON

package com.gyro.tests;

/* DO NOT EDIT | Generated by gyro */

import com.google.gson.annotations.SerializedName;

import io.realm.RealmList;
import io.realm.RealmObject;

public class Shop extends RealmObject {

    @SerializedName("json_name")
    private String name;
    private RealmList<Product> products;
	[...]
}
๐Ÿ“‘ Sample of the generated code in Swift (iOS)

On iOS, we support Decodable and Object Mapper templates for parsing/mapping.

Shop+Decodable.swift:

/* DO NOT EDIT | Generated by gyro */

import protocol Decodable.Decodable
import Decodable

extension Shop: Decodable {

  static func decode(_ json: Any) throws -> Shop {
    let shop = Shop()
      shop.name = try json => "name"
      let productsSandbox: [Product] = try json => "products"
    shop.products.append(objectsIn: productsSandbox)
    return shop
  }
}

ShopMapper.swift:

/* DO NOT EDIT | Generated by gyro */

import ObjectMapper

extension Shop: Mappable {

  // MARK: Initializers

  convenience init?(map: Map) {
    self.init()
  }

  // MARK: Mappable

  func mapping(map: Map) {

    // MARK: Attributes
    self.name <- map["name"]

    // MARK: Relationships
    self.products <- (map["products"], ListTransform<Product>())
  }
}

Combine JSONKeyPath and enums

Note that you can combine that JSONKeyPath key with enums (see Handling enums above). If you declared the User Info keys to make your attribute an enum (enumType + enumValues) in addition to JSONKeyPath, you'll have to also add the JSONValues key to list the corresponding values in the JSON for those enumValues.

KeyValue
JSONValuesvaleur_json_1,valeur_json_2,valeur_json_3

The number of items listed for that JSONValues key must be the same as the number of items listed for the enumValues keys, obviously.

Example:

enum_json

๐Ÿ“‘ Sample of the generated code in Java (Android)

Type.java:

package com.gyro.tests;

/* DO NOT EDIT | Generated by gyro */

public enum Type {

    TYPE_ONE("json_type_one"),
    TYPE_TWO("json_type_two"),
    TYPE_THREE("json_type_three");
	[...]
}
๐Ÿ“‘ Sample of the generated code in Swift (iOS)

Type.swift:

/* DO NOT EDIT | Generated by gyro */

enum Type: String {
  case typeOne = "json_type_one"
  case typeTwo = "json_type_two"
  case typeThree = "json_type_three"
}


Custom ValueTransformers

Only available on iOS (as Android uses the GSON library), custom ValueTransformers allows you to e.g. convrt a String into an Int or a Date when parsing the JSON. They are only used when using ObjectMapper or Decodable template.

To create a specific ValueTransformer for a field:

  • Create your ValueTransformer custom class inheriting NSValueTransformer and add it to your project
  • Select the attribute that will need this transformer, and in the UserInfo field, add a pair for the transformer key whose value should be the name of the ValueTransformer class to use:
KeyValue
transformerNameOfTheTransformerClass

Example:

transformer

๐Ÿ“‘ Sample of the generated code in Swift (iOS)

gyro will produce the following code. (In this example, attributes attrDouble and attrInteger32 don't have a transformer key set in their UserInfo).

Shop+Decodable.swift:

/* DO NOT EDIT | Generated by gyro */

import protocol Decodable.Decodable
import Decodable

extension Shop: Decodable {

  static func decode(_ json: Any) throws -> Shop {
    let shop = Shop()
      shop.attrDate = try Date.decode(json => "attrDate")
      shop.attrDateCustom = try Date.decode(json => "attrDateCustom")
      shop.attrDouble = try json => "attrDouble"
      shop.attrInteger16 = try Int.decode(json => "attrInteger16")
      shop.attrInteger32 = try json => "attrInteger32"
      shop.attrInteger64 = try Int.decode(json => "attrInteger64")
    return shop
  }
}

ShopMapper.swift:

/* DO NOT EDIT | Generated by gyro */

import ObjectMapper

extension Shop: Mappable {

  // MARK: Initializers
  convenience init?(_ map: Map) {
    self.init()
  }

  // MARK: Mappable
  func mapping(map: Map) {
    // MARK: Attributes
    self.attrDate <- (map["attrDate"], ISO8601DateTransform())
    self.attrDateCustom <- (map["attrDateCustom"], CustomDateTransformer())
    self.attrDecimal <- (map["attrDecimal"], MPDecimalTransformer())
    self.attrDouble <- map["attrDouble"]
    self.attrFloat <- (map["attrFloat"], MPDecimalTransformer())
    self.attrInteger16 <- (map["attrInteger16"], MPIntegerTransformer())
    self.attrInteger32 <- map["attrInteger32"]
    self.attrInteger64 <- (map["attrInteger64"], MPIntegerTransformer())
  }
}