ObjectSchemaInitializationTests.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. ////////////////////////////////////////////////////////////////////////////
  2. //
  3. // Copyright 2015 Realm Inc.
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. //
  17. ////////////////////////////////////////////////////////////////////////////
  18. import XCTest
  19. import RealmSwift
  20. import Realm.Dynamic
  21. import Foundation
  22. class ObjectSchemaInitializationTests: TestCase {
  23. func testAllValidTypes() {
  24. let object = SwiftObject()
  25. let objectSchema = object.objectSchema
  26. let noSuchCol = objectSchema["noSuchCol"]
  27. XCTAssertNil(noSuchCol)
  28. let boolCol = objectSchema["boolCol"]
  29. XCTAssertNotNil(boolCol)
  30. XCTAssertEqual(boolCol!.name, "boolCol")
  31. XCTAssertEqual(boolCol!.type, PropertyType.bool)
  32. XCTAssertFalse(boolCol!.isIndexed)
  33. XCTAssertFalse(boolCol!.isOptional)
  34. XCTAssertNil(boolCol!.objectClassName)
  35. let intCol = objectSchema["intCol"]
  36. XCTAssertNotNil(intCol)
  37. XCTAssertEqual(intCol!.name, "intCol")
  38. XCTAssertEqual(intCol!.type, PropertyType.int)
  39. XCTAssertFalse(intCol!.isIndexed)
  40. XCTAssertFalse(intCol!.isOptional)
  41. XCTAssertNil(intCol!.objectClassName)
  42. let floatCol = objectSchema["floatCol"]
  43. XCTAssertNotNil(floatCol)
  44. XCTAssertEqual(floatCol!.name, "floatCol")
  45. XCTAssertEqual(floatCol!.type, PropertyType.float)
  46. XCTAssertFalse(floatCol!.isIndexed)
  47. XCTAssertFalse(floatCol!.isOptional)
  48. XCTAssertNil(floatCol!.objectClassName)
  49. let doubleCol = objectSchema["doubleCol"]
  50. XCTAssertNotNil(doubleCol)
  51. XCTAssertEqual(doubleCol!.name, "doubleCol")
  52. XCTAssertEqual(doubleCol!.type, PropertyType.double)
  53. XCTAssertFalse(doubleCol!.isIndexed)
  54. XCTAssertFalse(doubleCol!.isOptional)
  55. XCTAssertNil(doubleCol!.objectClassName)
  56. let stringCol = objectSchema["stringCol"]
  57. XCTAssertNotNil(stringCol)
  58. XCTAssertEqual(stringCol!.name, "stringCol")
  59. XCTAssertEqual(stringCol!.type, PropertyType.string)
  60. XCTAssertFalse(stringCol!.isIndexed)
  61. XCTAssertFalse(stringCol!.isOptional)
  62. XCTAssertNil(stringCol!.objectClassName)
  63. let binaryCol = objectSchema["binaryCol"]
  64. XCTAssertNotNil(binaryCol)
  65. XCTAssertEqual(binaryCol!.name, "binaryCol")
  66. XCTAssertEqual(binaryCol!.type, PropertyType.data)
  67. XCTAssertFalse(binaryCol!.isIndexed)
  68. XCTAssertFalse(binaryCol!.isOptional)
  69. XCTAssertNil(binaryCol!.objectClassName)
  70. let dateCol = objectSchema["dateCol"]
  71. XCTAssertNotNil(dateCol)
  72. XCTAssertEqual(dateCol!.name, "dateCol")
  73. XCTAssertEqual(dateCol!.type, PropertyType.date)
  74. XCTAssertFalse(dateCol!.isIndexed)
  75. XCTAssertFalse(dateCol!.isOptional)
  76. XCTAssertNil(dateCol!.objectClassName)
  77. let objectCol = objectSchema["objectCol"]
  78. XCTAssertNotNil(objectCol)
  79. XCTAssertEqual(objectCol!.name, "objectCol")
  80. XCTAssertEqual(objectCol!.type, PropertyType.object)
  81. XCTAssertFalse(objectCol!.isIndexed)
  82. XCTAssertTrue(objectCol!.isOptional)
  83. XCTAssertEqual(objectCol!.objectClassName!, "SwiftBoolObject")
  84. let arrayCol = objectSchema["arrayCol"]
  85. XCTAssertNotNil(arrayCol)
  86. XCTAssertEqual(arrayCol!.name, "arrayCol")
  87. XCTAssertEqual(arrayCol!.type, PropertyType.object)
  88. XCTAssertTrue(arrayCol!.isArray)
  89. XCTAssertFalse(arrayCol!.isIndexed)
  90. XCTAssertFalse(arrayCol!.isOptional)
  91. XCTAssertEqual(objectCol!.objectClassName!, "SwiftBoolObject")
  92. let dynamicArrayCol = SwiftCompanyObject().objectSchema["employees"]
  93. XCTAssertNotNil(dynamicArrayCol)
  94. XCTAssertEqual(dynamicArrayCol!.name, "employees")
  95. XCTAssertEqual(dynamicArrayCol!.type, PropertyType.object)
  96. XCTAssertTrue(dynamicArrayCol!.isArray)
  97. XCTAssertFalse(dynamicArrayCol!.isIndexed)
  98. XCTAssertFalse(arrayCol!.isOptional)
  99. XCTAssertEqual(dynamicArrayCol!.objectClassName!, "SwiftEmployeeObject")
  100. }
  101. func testInvalidObjects() {
  102. // Should be able to get a schema for a non-RLMObjectBase subclass
  103. let schema = RLMObjectSchema(forObjectClass: SwiftFakeObjectSubclass.self)
  104. XCTAssertEqual(schema.properties.count, 1)
  105. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithAnyObject.self),
  106. "Should throw when not ignoring a property of a type we can't persist")
  107. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithStringArray.self),
  108. "Should throw when not ignoring a property of a type we can't persist")
  109. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithOptionalStringArray.self),
  110. "Should throw when not ignoring a property of a type we can't persist")
  111. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithBadPropertyName.self),
  112. "Should throw when not ignoring a property with a name we don't support")
  113. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithManagedLazyProperty.self),
  114. "Should throw when not ignoring a lazy property")
  115. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithDynamicManagedLazyProperty.self),
  116. "Should throw when not ignoring a lazy property")
  117. // Shouldn't throw when not ignoring a property of a type we can't persist if it's not dynamic
  118. _ = RLMObjectSchema(forObjectClass: SwiftObjectWithEnum.self)
  119. // Shouldn't throw when not ignoring a property of a type we can't persist if it's not dynamic
  120. _ = RLMObjectSchema(forObjectClass: SwiftObjectWithStruct.self)
  121. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithDatePrimaryKey.self),
  122. "Should throw when setting a non int/string primary key")
  123. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithNSURL.self),
  124. "Should throw when not ignoring a property of a type we can't persist")
  125. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithNonOptionalLinkProperty.self),
  126. "Should throw when not marking a link property as optional")
  127. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithNSNumber.self),
  128. reason: "Can't persist NSNumber without default value: use a Swift-native number type " +
  129. "or provide a default value.",
  130. "Should throw when using not providing default value for NSNumber property on Swift model")
  131. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithOptionalNSNumber.self),
  132. reason: "Can't persist NSNumber without default value: use a Swift-native number type " +
  133. "or provide a default value.",
  134. "Should throw when using not providing default value for NSNumber property on Swift model")
  135. }
  136. func testPrimaryKey() {
  137. XCTAssertNil(SwiftObject().objectSchema.primaryKeyProperty,
  138. "Object should default to having no primary key property")
  139. XCTAssertEqual(SwiftPrimaryStringObject().objectSchema.primaryKeyProperty!.name, "stringCol")
  140. }
  141. func testIgnoredProperties() {
  142. let schema = SwiftIgnoredPropertiesObject().objectSchema
  143. XCTAssertNil(schema["runtimeProperty"], "The object schema shouldn't contain ignored properties")
  144. XCTAssertNil(schema["runtimeDefaultProperty"], "The object schema shouldn't contain ignored properties")
  145. XCTAssertNil(schema["readOnlyProperty"], "The object schema shouldn't contain read-only properties")
  146. }
  147. func testIndexedProperties() {
  148. XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["stringCol"]!.isIndexed)
  149. XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["intCol"]!.isIndexed)
  150. XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["int8Col"]!.isIndexed)
  151. XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["int16Col"]!.isIndexed)
  152. XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["int32Col"]!.isIndexed)
  153. XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["int64Col"]!.isIndexed)
  154. XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["boolCol"]!.isIndexed)
  155. XCTAssertTrue(SwiftIndexedPropertiesObject().objectSchema["dateCol"]!.isIndexed)
  156. XCTAssertFalse(SwiftIndexedPropertiesObject().objectSchema["floatCol"]!.isIndexed)
  157. XCTAssertFalse(SwiftIndexedPropertiesObject().objectSchema["doubleCol"]!.isIndexed)
  158. XCTAssertFalse(SwiftIndexedPropertiesObject().objectSchema["dataCol"]!.isIndexed)
  159. let unindexibleSchema = RLMObjectSchema(forObjectClass: SwiftObjectWithUnindexibleProperties.self)
  160. for propName in SwiftObjectWithUnindexibleProperties.indexedProperties() {
  161. XCTAssertFalse(unindexibleSchema[propName]!.indexed,
  162. "Shouldn't mark unindexible property '\(propName)' as indexed")
  163. }
  164. }
  165. func testOptionalProperties() {
  166. let schema = RLMObjectSchema(forObjectClass: SwiftOptionalObject.self)
  167. for prop in schema.properties {
  168. XCTAssertTrue(prop.optional)
  169. }
  170. let types = Set(schema.properties.map { $0.type })
  171. XCTAssertEqual(types, Set([.string, .string, .data, .date, .object, .int, .float, .double, .bool]))
  172. }
  173. func testImplicitlyUnwrappedOptionalsAreParsedAsOptionals() {
  174. let schema = SwiftImplicitlyUnwrappedOptionalObject().objectSchema
  175. XCTAssertTrue(schema["optObjectCol"]!.isOptional)
  176. XCTAssertTrue(schema["optNSStringCol"]!.isOptional)
  177. XCTAssertTrue(schema["optStringCol"]!.isOptional)
  178. XCTAssertTrue(schema["optBinaryCol"]!.isOptional)
  179. XCTAssertTrue(schema["optDateCol"]!.isOptional)
  180. }
  181. func testNonRealmOptionalTypesDeclaredAsRealmOptional() {
  182. assertThrows(RLMObjectSchema(forObjectClass: SwiftObjectWithNonRealmOptionalType.self))
  183. }
  184. func testNotExplicitlyIgnoredComputedProperties() {
  185. let schema = SwiftComputedPropertyNotIgnoredObject().objectSchema
  186. // The two computed properties should not appear on the schema.
  187. XCTAssertEqual(schema.properties.count, 1)
  188. XCTAssertNotNil(schema["_urlBacking"])
  189. }
  190. }
  191. class SwiftFakeObject: NSObject {
  192. @objc class func objectUtilClass(_ isSwift: Bool) -> AnyClass { return ObjectUtil.self }
  193. @objc class func primaryKey() -> String? { return nil }
  194. @objc class func ignoredProperties() -> [String] { return [] }
  195. @objc class func indexedProperties() -> [String] { return [] }
  196. @objc class func _realmObjectName() -> String? { return nil }
  197. @objc class func _realmColumnNames() -> [String: String]? { return nil }
  198. }
  199. class SwiftObjectWithNSURL: SwiftFakeObject {
  200. @objc dynamic var url = NSURL(string: "http://realm.io")!
  201. }
  202. class SwiftObjectWithAnyObject: SwiftFakeObject {
  203. @objc dynamic var anyObject: AnyObject = NSObject()
  204. }
  205. class SwiftObjectWithStringArray: SwiftFakeObject {
  206. @objc dynamic var stringArray = [String]()
  207. }
  208. class SwiftObjectWithOptionalStringArray: SwiftFakeObject {
  209. @objc dynamic var stringArray: [String]?
  210. }
  211. enum SwiftEnum {
  212. case case1
  213. case case2
  214. }
  215. class SwiftObjectWithEnum: SwiftFakeObject {
  216. var swiftEnum = SwiftEnum.case1
  217. }
  218. class SwiftObjectWithStruct: SwiftFakeObject {
  219. var swiftStruct = SortDescriptor(keyPath: "prop")
  220. }
  221. class SwiftObjectWithDatePrimaryKey: SwiftFakeObject {
  222. @objc dynamic var date = Date()
  223. override class func primaryKey() -> String? {
  224. return "date"
  225. }
  226. }
  227. class SwiftObjectWithNSNumber: SwiftFakeObject {
  228. @objc dynamic var number = NSNumber()
  229. }
  230. class SwiftObjectWithOptionalNSNumber: SwiftFakeObject {
  231. @objc dynamic var number: NSNumber? = NSNumber()
  232. }
  233. class SwiftFakeObjectSubclass: SwiftFakeObject {
  234. @objc dynamic var dateCol = Date()
  235. }
  236. class SwiftObjectWithUnindexibleProperties: SwiftFakeObject {
  237. @objc dynamic var boolCol = false
  238. @objc dynamic var intCol = 123
  239. @objc dynamic var floatCol = 1.23 as Float
  240. @objc dynamic var doubleCol = 12.3
  241. @objc dynamic var binaryCol = "a".data(using: String.Encoding.utf8)!
  242. @objc dynamic var dateCol = Date(timeIntervalSince1970: 1)
  243. @objc dynamic var objectCol: SwiftBoolObject? = SwiftBoolObject()
  244. let arrayCol = List<SwiftBoolObject>()
  245. dynamic override class func indexedProperties() -> [String] {
  246. return ["boolCol", "intCol", "floatCol", "doubleCol", "binaryCol", "dateCol", "objectCol", "arrayCol"]
  247. }
  248. }
  249. // swiftlint:disable:next type_name
  250. class SwiftObjectWithNonNullableOptionalProperties: SwiftFakeObject {
  251. @objc dynamic var optDateCol: Date?
  252. }
  253. class SwiftObjectWithNonOptionalLinkProperty: SwiftFakeObject {
  254. @objc dynamic var objectCol = SwiftBoolObject()
  255. }
  256. extension Set: RealmOptionalType { }
  257. class SwiftObjectWithNonRealmOptionalType: SwiftFakeObject {
  258. let set = RealmOptional<Set<Int>>()
  259. }
  260. class SwiftObjectWithBadPropertyName: SwiftFakeObject {
  261. @objc dynamic var newValue = false
  262. }
  263. class SwiftObjectWithManagedLazyProperty: SwiftFakeObject {
  264. lazy var foobar: String = "foo"
  265. }
  266. // swiftlint:disable:next type_name
  267. class SwiftObjectWithDynamicManagedLazyProperty: SwiftFakeObject {
  268. @objc dynamic lazy var foobar: String = "foo"
  269. }