I’m working on an internal tool. Here are the three main models im working with:
Site
has_many Document
has_many Version
The document model essentially stores a body of content. What I want to do is setup document versioning with the Version model. So the version would store the content.
Right now, I create a new document via the nested URL: POST /sites/:id/documents. Since the form is there, and I have no plans on changing the user experience here, I started building it to use both the Document and Version models. However, I’m starting to feel like this is wrong. Managing validations from a child object and pushing them up to the parent is becoming a pain.
Another note:
The Document has an integer field called active_version_id. It stores the version that is currently the active one of that document. I then use that ID to get the version model, to show the main content connected to that document. I set it to the version created when a document is created.
What i want to do
In short, here’s the workflow I would expect:
- Goes to create new document
- Puts in the content
- Submits the form
- Parent Document is created.
- Using the parent document, it creates a new version. Any validation errors on the content at the version level should prevent the save of both models.
- Errors from the version object should show up on the page, so the user knows.
Here’s my Version model, so you can see the validation logic. It basically checks if the headers in the document are valid (which is just some identification text). And that the headers aren’t used in versions of any other document in that site:
def document_is_in_valid_format
p = CopyProcess::Processor.new
if !p.contains_valid_headers(self.content)
Rails.logger.debug "Headers invalid."
msg = "Content headers must be in valid format."
self.document.errors[:base] << msg
self.errors[:base] << msg
else
# Check that headers are unique to the parent document
headers = content.split(/\n/)[1..3].join(' - ').gsub(/\/\*|\\\*/,'')
site_documents = []
# get all documents where it's not this one's parent
if self.id
site_documents = Document.where(["site_id = ? AND id <> ?", self.site_id, self.document_id])
else
site_documents = Document.where(["site_id = ?", self.site_id])
end
site_documents.includes(:versions)
site_documents.each do |doc|
if doc.version_names.include?(headers)
msg = "Content headers are not unique."
self.document.errors[:base] << msg
self.errors[:base] << msg
break
end
end
end
end
I’m wondering if the solution here might be to use a nested form for. For step 5, i imagine a transaction would help prevent both saves.
Check in http://api.rubyonrails.org for:
Also take into account that you can use validations on _id/_ids fields, like
validates :document_ids, presence: trueto make sure there is at least one document.To make the code more readable i would try split your long validation sequence in custom validators using: