1 module dorm.types.relations; 2 3 import dorm.declarative.conversion; 4 import dorm.model; 5 import dorm.types.patches; 6 7 version(none) static struct ManyToManyField(alias idOrModel) 8 { 9 alias T = ModelFromIdOrModel!idOrModel; 10 alias primaryKeyAlias = IdAliasFromIdOrModel!idOrModel; 11 enum primaryKeyField = IdFieldFromIdOrModel!idOrModel; 12 alias PrimaryKeyType = typeof(primaryKeyAlias); 13 14 bool toClear; 15 PrimaryKeyType[] toAdd; 16 PrimaryKeyType[] toRemove; 17 18 private T[] cached; 19 private bool resolved; 20 21 T[] populated() 22 { 23 assert(resolved, "ManyToManyField reference is not populated! Call " 24 ~ "`db.populate!(Model.manyToManyFieldName)(modelInstance)` or query " 25 ~ "data with the recursion flag set!"); 26 return cached; 27 } 28 29 void setCachedPopulated(T[] populated) 30 { 31 cached = populated; 32 resolved = true; 33 } 34 35 void add(T other) 36 { 37 auto refField = __traits(child, other, primaryKeyAlias); 38 toRemove = toRemove.remove!(refField); 39 toAdd ~= refField; 40 } 41 42 void add(PrimaryKeyType primaryKey) 43 { 44 toRemove = toRemove.remove!(primaryKey); 45 toAdd ~= primaryKey; 46 } 47 48 void add(Range)(Range range) 49 if (!is(Range == T) 50 && !is(Range == PrimaryKeyType)) 51 { 52 foreach (item; range) 53 add(item); 54 } 55 56 void remove(T other) 57 { 58 auto refField = __traits(child, other, primaryKeyAlias); 59 toAdd = toAdd.remove!(refField); 60 toRemove ~= refField; 61 } 62 63 void add(PrimaryKeyType primaryKey) 64 { 65 toRemove = toRemove.remove!(primaryKey); 66 toAdd ~= primaryKey; 67 } 68 69 void remove(Range)(Range range) 70 if (!is(Range == T) 71 && !is(Range == PrimaryKeyType)) 72 { 73 foreach (item; range) 74 remove(item); 75 } 76 77 void clear() 78 { 79 toAdd.length = 0; 80 toRemove.length = 0; 81 toClear = true; 82 } 83 } 84 85 /** 86 * DORM field type representing a referenced model through a foreign key in SQL. 87 * 88 * The actual data stored in the DB as foreign key is what's stored in the 89 * `foreignKey` member. The `populated` property is simply a cache member for 90 * supporting proactively fetched joined data from the database. Trying to 91 * access the populated data without having it populated will result in a 92 * program crash through `assert(false)`. 93 * 94 * Bugs: `db.populate(ModelRef)` is not yet implemented 95 */ 96 static template ModelRef(alias idOrPatch) 97 { 98 static if (is(idOrPatch : Model) 99 && !__traits(compiles, typeof(IdAliasFromIdOrPatch!idOrPatch)) 100 && !__traits(compiles, LogicalFields!idOrPatch)) 101 pragma(msg, __FILE__ ~ "(" ~ __LINE__.stringof ~ "): Hint: if this field fails because of a circular reference, " 102 ~ "refer to the field directly instead of refering to a model:\n" 103 ~ "\tModelRef!(" ~ __traits(identifier, idOrPatch) ~ ".remotePrimaryKey) field;\n" 104 ~ "\n" 105 ~ "\tThis is a currently known limitation that's unlikely to be fixed soon.\n" 106 ~ "\t--------------------------"); 107 alias primaryKeyAlias = IdAliasFromIdOrPatch!idOrPatch; 108 alias TPatch = PatchFromIdOrPatch!idOrPatch; 109 alias T = ModelFromSomePatch!TPatch; 110 alias ModelRef = ModelRefImpl!(primaryKeyAlias, T, TPatch); 111 } 112 113 /// ditto 114 static struct ModelRefImpl(alias id, _TModel, _TSelect) 115 { 116 alias TModel = _TModel; 117 alias TSelect = _TSelect; 118 alias primaryKeyAlias = id; 119 enum primaryKeySourceName = __traits(identifier, id); 120 enum primaryKeyColumnName = DormColumnNameImpl!primaryKeyAlias; 121 alias PrimaryKeyType = typeof(primaryKeyAlias); 122 123 /// The actual data stored in the DB field. Can be manipulated manually to 124 /// perform special operations not supported otherwise. 125 PrimaryKeyType foreignKey; 126 127 private TSelect cached; 128 private bool resolved; 129 130 /// Returns: `true` if populated can be called, `false` otherwise. 131 bool isPopulated() const @property 132 { 133 return resolved; 134 } 135 136 /** 137 * Returns: the value that was fetched from the database. Only works if the 138 * value was either requested to be included using `select.populate` or by 139 * calling `db.populate(thisObject)`. 140 * 141 * When trying to call this function with an unpopulate object, the program 142 * will crash with an AssertError. 143 */ 144 TSelect populated() 145 { 146 if (!resolved) 147 { 148 assert(false, "ModelRef reference is not populated! Call " 149 ~ "`db.populate!(Model.referenceFieldName)(modelInstance)` or query " 150 ~ "data with `select!T.populate(o => o.fieldNameToPopulate.yes)`!"); 151 } 152 return cached; 153 } 154 155 /** 156 * Sets the populated value as well as the foreign key for saving in the DB. 157 */ 158 auto opAssign(TSelect value) 159 { 160 resolved = true; 161 cached = value; 162 foreignKey = __traits(child, value, primaryKeyAlias); 163 return value; 164 } 165 166 /// Returns true if `other`'s primary key is equal to the foreign key stored 167 /// in this ModelRef instance. Does not check any other fields. Does not 168 /// require this ModelRef to be populated. 169 bool refersTo(const TModel other) const 170 { 171 return foreignKey == mixin("other.", primaryKeySourceName); 172 } 173 174 static if (!is(TModel == TSelect)) 175 { 176 /// ditto 177 bool refersTo(const TSelect other) const 178 { 179 return foreignKey == __traits(child, other, primaryKeyAlias); 180 } 181 } 182 } 183 184 // TODO: need to figure out how to make BackRefs 185 version (none) 186 static struct BackRef(alias foreignField) 187 { 188 static assert(is(__traits(parent, foreignField) : Model), 189 "Invalid foreign key field `" ~ foreignField.stringof 190 ~ "`! Change to `BackRef!(OtherModel.foreignKeyReferencingThis)`"); 191 192 alias T = __traits(parent, foreignField); 193 194 private T[] cached; 195 private bool resolved; 196 197 T[] populated() 198 { 199 assert(resolved, "BackRef value is not populated! Call " 200 ~ "`db.populate!(Model.otherFieldReferencingThis)(modelInstance)` or query " 201 ~ "data with the recursion flag set!"); 202 return cached; 203 } 204 }