Handling storage validations that are not enforced by the db?

Hi, If I understand the repo pattern correctly, we should be relying on database constraints to manage as much of the integrity of the datastore as possible, via things like uniq indexes and other datastore abstractions. It seems that the commands and connectors are designed to throw in these cases.

I’m curious though what abstraction one would use to manage an operation that needs to check the store for a valid or next value when the datastore itself does not necessarily enforce it or provide it, but you might need to in the context of a write or update. My first guess is in the Command that defines the operation, but I could see that that could also be done in the ChangeSet, but I’m not sure if its breaks the responsibility of the changeset to do a db query to validate or invalidate a specific set of changes against a command.

I’m struggling a little to understand this partly because it seems like I can get access to the relations and the query layer from pretty much any abstraction which is nice and flexible but also makes it challenging to understand as a newcomer where the appropriate behavior exists in the context of the data mapper machinery.

if there’s a specific patterns as expressed in a spec or example, a link would suffice. I’d be interested in seeing the interplay between the datastore check and the error raised, and how the repo or the higher order abstraction would then communicate that back to the caller.

Thanks folks!

Check out dry-validation. It’s a generic data validation library that plays well with rom-rb.

dry-validation is great, but what I’m thinking about are things like:

  • rom-http needing to do an OPTIONS or pre-fetch request to validate against the API
  • rom-csv or rom-yml checking the uniqueness of a field, like an email address, before inserting into the dataset
  • rom-sql having to do a validates_uniqueness_of type behavior which requires a roundtrip to the db before writing.

Admittedly in a perfect scenario, you may not have to do this, but it’s common in my line of work to have to connect to databases I don’t control the types of indices or constraints I would prefer to have. The attraction of rom for me is being able to have the flexibility to manage those scenarios more easily than in other data access solutions. I’m just not sure “where” to do it.

Like, is it best to be implemented as a sort of “find or create/update” behavior at the repository level, or implemented in one of the other abstractions closer to the persistence interface (Relation/Gateway/Dataset/Command/Changeset)?

This is a low-priority question, but one that I’d like to understand as I experiment more deeply. Thanks!

Re-reading the core concepts page, it seems like I would do this in the Command: “Augments Relations & Commands with adapter specific methods for querying the datastore”, providing my own subclassed command for the adapter if my specific use varies from the provided command for the utilized adapter. Does that seem accurate?

@jedschneider we used to have a “validation” concept in the commands in the early days of rom and it got removed :slightly_smiling_face: All your use cases can be handled by dry-validation. You can write your own macros to keep things concise in your contracts too.

Like, is it best to be implemented as a sort of “find or create/update” behavior at the repository level

Yes, a find_or_create method in a repository would work well. If you’re on PG, you can also benefit from Upsert command.

1 Like