OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. |
| 4 |
| 5 part of gcloud.db; |
| 6 |
| 7 /// Annotation used to mark dart classes which can be stored into datastore. |
| 8 /// |
| 9 /// The `Kind` annotation on a class as well as other `Property` annotations on |
| 10 /// fields or getters of the class itself (and any of it's superclasses) up to |
| 11 /// the [Model] class describe the *mapping* of *dart objects* to datastore |
| 12 /// *entities*. |
| 13 /// |
| 14 /// An "entity" is an object which can be stored into Google Cloud Datastore. |
| 15 /// It contains a number of named "properties", some of them might get indexed, |
| 16 /// others are not. A "property" value can be of a limited set of supported |
| 17 /// types (such as `int` and `String`). |
| 18 /// |
| 19 /// Here is an example of a dart model class which can be stored into datastore: |
| 20 /// @Kind() |
| 21 /// class Person extends db.Model { |
| 22 /// @StringProperty() |
| 23 /// String name; |
| 24 /// |
| 25 /// @IntProperty() |
| 26 /// int age; |
| 27 /// |
| 28 /// @DateTimeProperty() |
| 29 /// DateTime dateOfBirth; |
| 30 /// } |
| 31 class Kind { |
| 32 /// The kind name used when saving objects to datastore. |
| 33 /// |
| 34 /// If `null` the name will be the same as the class name at which the |
| 35 /// annotation is placed. |
| 36 final String name; |
| 37 |
| 38 /// The type, either [ID_TYPE_INTEGER] or [ID_TYPE_STRING]. |
| 39 final IdType idType; |
| 40 |
| 41 /// Annotation specifying the name of this kind and whether to use integer or |
| 42 /// string `id`s. |
| 43 /// |
| 44 /// If `name` is omitted, it will default to the name of class to which this |
| 45 /// annotation is attached to. |
| 46 const Kind({this.name, this.idType: IdType.Integer}); |
| 47 } |
| 48 |
| 49 /// The type used for id's of an entity. |
| 50 class IdType { |
| 51 /// Use integer ids for identifying entities. |
| 52 static const IdType Integer = const IdType(1); |
| 53 |
| 54 /// Use string ids for identifying entities. |
| 55 static const IdType String = const IdType(2); |
| 56 |
| 57 final int _type; |
| 58 |
| 59 const IdType(this._type); |
| 60 } |
| 61 |
| 62 /// Describes a property of an Entity. |
| 63 /// |
| 64 /// Please see [Kind] for an example on how to use them. |
| 65 abstract class Property { |
| 66 /// The name of the property. |
| 67 /// |
| 68 /// If it is `null`, the name will be the same as used in the |
| 69 /// model class. |
| 70 final String propertyName; |
| 71 |
| 72 /// Specifies whether this property is required or not. |
| 73 /// |
| 74 /// If required is `true`, it will be enforced when saving model objects to |
| 75 /// the datastore and when retrieving them. |
| 76 final bool required; |
| 77 |
| 78 /// Specifies whether this property should be indexed or not. |
| 79 /// |
| 80 /// When running queries no this property, it is necessary to set [indexed] to |
| 81 /// `true`. |
| 82 final bool indexed; |
| 83 |
| 84 const Property({this.propertyName, this.required: false, this.indexed: true}); |
| 85 |
| 86 bool validate(ModelDB db, Object value) { |
| 87 if (required && value == null) return false; |
| 88 return true; |
| 89 } |
| 90 |
| 91 Object encodeValue(ModelDB db, Object value); |
| 92 |
| 93 Object decodePrimitiveValue(ModelDB db, Object value); |
| 94 } |
| 95 |
| 96 /// An abstract base class for primitive properties which can e.g. be used |
| 97 /// within a composed `ListProperty`. |
| 98 abstract class PrimitiveProperty extends Property { |
| 99 const PrimitiveProperty( |
| 100 {String propertyName, bool required: false, bool indexed: true}) |
| 101 : super(propertyName: propertyName, required: required, indexed: indexed); |
| 102 |
| 103 Object encodeValue(ModelDB db, Object value) => value; |
| 104 |
| 105 Object decodePrimitiveValue(ModelDB db, Object value) => value; |
| 106 } |
| 107 |
| 108 /// A boolean [Property]. |
| 109 /// |
| 110 /// It will validate that values are booleans before writing them to the |
| 111 /// datastore and when reading them back. |
| 112 class BoolProperty extends PrimitiveProperty { |
| 113 const BoolProperty( |
| 114 {String propertyName, bool required: false, bool indexed: true}) |
| 115 : super(propertyName: propertyName, required: required, indexed: indexed); |
| 116 |
| 117 bool validate(ModelDB db, Object value) |
| 118 => super.validate(db, value) && (value == null || value is bool); |
| 119 } |
| 120 |
| 121 /// A integer [Property]. |
| 122 /// |
| 123 /// It will validate that values are integers before writing them to the |
| 124 /// datastore and when reading them back. |
| 125 class IntProperty extends PrimitiveProperty { |
| 126 const IntProperty( |
| 127 {String propertyName, bool required: false, bool indexed: true}) |
| 128 : super(propertyName: propertyName, required: required, indexed: indexed); |
| 129 |
| 130 bool validate(ModelDB db, Object value) |
| 131 => super.validate(db, value) && (value == null || value is int); |
| 132 } |
| 133 |
| 134 /// A double [Property]. |
| 135 /// |
| 136 /// It will validate that values are doubles before writing them to the |
| 137 /// datastore and when reading them back. |
| 138 class DoubleProperty extends PrimitiveProperty { |
| 139 const DoubleProperty( |
| 140 {String propertyName, bool required: false, bool indexed: true}) |
| 141 : super(propertyName: propertyName, required: required, indexed: indexed); |
| 142 |
| 143 bool validate(ModelDB db, Object value) |
| 144 => super.validate(db, value) && (value == null || value is double); |
| 145 } |
| 146 |
| 147 /// A string [Property]. |
| 148 /// |
| 149 /// It will validate that values are strings before writing them to the |
| 150 /// datastore and when reading them back. |
| 151 class StringProperty extends PrimitiveProperty { |
| 152 const StringProperty( |
| 153 {String propertyName, bool required: false, bool indexed: true}) |
| 154 : super(propertyName: propertyName, required: required, indexed: indexed); |
| 155 |
| 156 bool validate(ModelDB db, Object value) |
| 157 => super.validate(db, value) && (value == null || value is String); |
| 158 } |
| 159 |
| 160 /// A key [Property]. |
| 161 /// |
| 162 /// It will validate that values are keys before writing them to the |
| 163 /// datastore and when reading them back. |
| 164 class ModelKeyProperty extends PrimitiveProperty { |
| 165 const ModelKeyProperty( |
| 166 {String propertyName, bool required: false, bool indexed: true}) |
| 167 : super(propertyName: propertyName, required: required, indexed: indexed); |
| 168 |
| 169 bool validate(ModelDB db, Object value) |
| 170 => super.validate(db, value) && (value == null || value is Key); |
| 171 |
| 172 Object encodeValue(ModelDB db, Object value) { |
| 173 if (value == null) return null; |
| 174 return db.toDatastoreKey(value); |
| 175 } |
| 176 |
| 177 Object decodePrimitiveValue(ModelDB db, Object value) { |
| 178 if (value == null) return null; |
| 179 return db.fromDatastoreKey(value as datastore.Key); |
| 180 } |
| 181 } |
| 182 |
| 183 /// A binary blob [Property]. |
| 184 /// |
| 185 /// It will validate that values are blobs before writing them to the |
| 186 /// datastore and when reading them back. Blob values will be represented by |
| 187 /// List<int>. |
| 188 class BlobProperty extends PrimitiveProperty { |
| 189 const BlobProperty({String propertyName, bool required: false}) |
| 190 : super(propertyName: propertyName, required: required, indexed: false); |
| 191 |
| 192 // NOTE: We don't validate that the entries of the list are really integers |
| 193 // of the range 0..255! |
| 194 // If an untyped list was created the type check will always succeed. i.e. |
| 195 // "[1, true, 'bar'] is List<int>" evaluates to `true` |
| 196 bool validate(ModelDB db, Object value) |
| 197 => super.validate(db, value) && (value == null || value is List<int>); |
| 198 |
| 199 Object encodeValue(ModelDB db, Object value) { |
| 200 if (value == null) return null; |
| 201 return new datastore.BlobValue(value); |
| 202 } |
| 203 |
| 204 Object decodePrimitiveValue(ModelDB db, Object value) { |
| 205 if (value == null) return null; |
| 206 |
| 207 datastore.BlobValue blobValue = value; |
| 208 return blobValue.bytes; |
| 209 } |
| 210 } |
| 211 |
| 212 /// A datetime [Property]. |
| 213 /// |
| 214 /// It will validate that values are DateTime objects before writing them to the |
| 215 /// datastore and when reading them back. |
| 216 class DateTimeProperty extends PrimitiveProperty { |
| 217 const DateTimeProperty( |
| 218 {String propertyName, bool required: false, bool indexed: true}) |
| 219 : super(propertyName: propertyName, required: required, indexed: indexed); |
| 220 |
| 221 bool validate(ModelDB db, Object value) |
| 222 => super.validate(db, value) && (value == null || value is DateTime); |
| 223 |
| 224 Object decodePrimitiveValue(ModelDB db, Object value) { |
| 225 if (value is int) { |
| 226 return |
| 227 new DateTime.fromMillisecondsSinceEpoch(value ~/ 1000, isUtc: true); |
| 228 } |
| 229 return value; |
| 230 } |
| 231 } |
| 232 |
| 233 |
| 234 /// A composed list [Property], with a `subProperty` for the list elements. |
| 235 /// |
| 236 /// It will validate that values are List objects before writing them to the |
| 237 /// datastore and when reading them back. It will also validate the elements |
| 238 /// of the list itself. |
| 239 class ListProperty extends Property { |
| 240 final PrimitiveProperty subProperty; |
| 241 |
| 242 // TODO: We want to support optional list properties as well. |
| 243 // Get rid of "required: true" here. |
| 244 const ListProperty(this.subProperty, |
| 245 {String propertyName, bool indexed: true}) |
| 246 : super(propertyName: propertyName, required: true, indexed: indexed); |
| 247 |
| 248 bool validate(ModelDB db, Object value) { |
| 249 if (!super.validate(db, value) || value is! List) return false; |
| 250 |
| 251 for (var entry in value) { |
| 252 if (!subProperty.validate(db, entry)) return false; |
| 253 } |
| 254 return true; |
| 255 } |
| 256 |
| 257 Object encodeValue(ModelDB db, Object value) { |
| 258 if (value == null) return null; |
| 259 List list = value; |
| 260 if (list.length == 0) return null; |
| 261 if (list.length == 1) return subProperty.encodeValue(db, list[0]); |
| 262 return list.map( |
| 263 (value) => subProperty.encodeValue(db, value)).toList(); |
| 264 } |
| 265 |
| 266 Object decodePrimitiveValue(ModelDB db, Object value) { |
| 267 if (value == null) return []; |
| 268 if (value is! List) return [subProperty.decodePrimitiveValue(db, value)]; |
| 269 return (value as List) |
| 270 .map((entry) => subProperty.decodePrimitiveValue(db, entry)) |
| 271 .toList(); |
| 272 } |
| 273 } |
| 274 |
| 275 /// A convenience [Property] for list of strings. |
| 276 class StringListProperty extends ListProperty { |
| 277 const StringListProperty({String propertyName, bool indexed: true}) |
| 278 : super(const StringProperty(), |
| 279 propertyName: propertyName, indexed: indexed); |
| 280 } |
OLD | NEW |