Follow progress in #82
[notes to clarify my thoughts and for posterity, but anyone feel free to jump in]
I think we need to add collections to mirage - essentially, a model layer - because of associations. Here's why.
If you're just using fixtures, fine, you can manage related IDs yourself and return whatever you want. But this becomes a hassle, couples your tests to external files, etc. Lots of people want something simpler.
So, we have factories. But, factories are supposed to define the minimum attributes necessary to make your model valid, as well as attributes for other common scenarios.
So you go to define a contact
factory and an address
factory, and you say "for a contact to be valid, it also needs an address". You specify this in your factory:
// factories/contact.js
export default Mirage.Factory.extend({
name: 'Sam',
address: Mirage.association()
});
The thing is, when you go to .create()
a contact, you need to create the associated address. This means building the attrs from the factory, as well as relating the two models via some id within Mirage's database. Essentially, we need to know where the foreign key lives, so in your route handlers you can fetch the related models.
Okay, so let's say a contact hasOne
address. We could specify this in the factory...
export default Mirage.Factory.extend({
name: 'Sam',
- address: Mirage.association()
+ address: Mirage.hasOne()
but then, users would need to ensure all their relationship metadata is in their factories. What if this doesn't match up with their business requirements regarding what makes their models valid? For example, what if a contact didn't need an address to be valid, and so the factory didn't have the association in its definition? In this case, we still want the user to be able to do
var contact = server.create('contact');
var address = server.create('address', {contact: contact});
in their tests. But if the association information isn't in the factory, how will Mirage know to store the foreign key on the addresses
collection (i.e., to add a contact_id
field to each address in the database), rather than on the contacts
collection? It won't.
So, factories aren't a good place to encode relationship information.
Another possibility is in the upcoming serializer layer. We need serializers because, we want users to be able to say from a route handler, "respond with this contact", and then for that contact to go through a serializer, where they decide which attrs to expose, whether to include a root key, what relationships to include etc. (similar to ActiveModelSerializers).
So, we could require users to add the relationship metadata to the serializers. But, this is an unnecessary coupling as well. What if you don't want to include related addresses in your default ContactSerializer
. You shouldn't have to, just to let Mirage know about your relationships. Users will also want to be able to specify multiple serializers for different scenarios - for example, to show a small subset of attrs for a summary overview route, and then all attrs + related models for a detail route. Deciphering the relationship metadata from these various serializers seems intractable.
In factory_girl, you specify an associated factory by referencing its name. The thing is, factory_girl is building ActiveRecord objects. The association information is encoded in your ActiveRecord model definitions - has_many, belongs_to, etc. In Mirage, we don't have a model layer. So, I think the solution is, create one.
It's going to be very simple, where you essentially declare what your Mirage collections are, and you specify where the foreign keys are:
// mirage/collections.js
export default function() {
this.collection('contacts');
this.collection('addresses', {belongsTo: 'contacts'});
}
Not sure what the API will be, but anyway, something like this.
With this information, we can give users a consistent answer about where they expect to find related models in the db
object in their route handlers, as well as make serializers + factories much simpler to work with.