Update and validate multiple entries
Hi friends
I got a kinda tricky problem which I usually would do through a nested form, but since I'm writing an API which has some tricky associations I need something custom (I guess):
First of all I got a ContentModel. A ContentModel has many ContentModelFields. They just describe of what kinds of fields a ContentModel consists, e.g. title:string, year:integer..., pretty much the same as a rails model but it can be created and edited dynamically via an backend.
In addition to that I got a ContentEntry. That one has got a relation to the ContentModel so that's clear what kind of model the ContentEntry describes. Also a ContentEntry has got many ContentEntryFields. They store the ContentEntry id plus an ContentModelField id and a value.
An Example:
ContentModel:
id: 1
title: Lookbook
ContentModelfields
id: 1
content_model_id: 1
title: Title
field_id: title
field_type: string
id: 2
content_model_id: 1
title: Year
field_id: year
field_type: integer
id: 3
content_model_id: 1
title: Link
field_id: link
field_type: string
An then there is a ContentEntry
id: 1
content_model_id: 1
ContentEntryFields
id: 1
content_model_field_id: 1
content_entry_id: 1
value: PRACTICE
id: 2
content_model_field_id: 2
content_entry_id: 1
value: 2016
id: 3
content_model_field_id: 3
content_entry_id: 1
value: /drops/practice
Now... there is the tricky part: I want to write an API which updates the entries. I'd like the PUT request (/api/v1/entries/1)to send some fields like so:
fields: {
title: PRACTICE,
year: 2016,
link: /drops/practice
}
or
fields: {
title: OCEANS,
year: 2017,
link: /drops/oceans
}
As far as I (and google) know, I can't use the nested stuff with accepts_nested_attributes_for here since I first of all need to find the ContentModelFields in order to know which ContentEntryField is the right one for title, year or link. Than I need to find that exact ContentEntryField based on the field_id of the ContentModelField and update that with the new value.
So far so good. I've done that whole thing this way (@content_entry get's set by an before_action):
# get fields from params
api_fields = params[:entryData][:fields]
# find or create on all (!) fields with the new values
api_fields.each do |api_field|
content_model_field = @content_entry.content_model_fields.where(field_id: api_field).first
content_entry_field = @content_entry.content_entry_fields.where(content_model_field_id: content_model_field.id).first_or_create
content_entry_field.update_attributes(value: params[:entryData][:fields][api_field.to_sym])
end
Now on to the problem: I want to add validations (which are stored within the ContentModelField). Currently if the ContentModel consists of three fields and there might be an validation error (or any other error) on the third one, the first two would be saved which would be not so cool since I want to update the ContentEntry as a whole.
Now I don't know what the f to do :/
Thank you for your help!
I'm not completely sure since I have very limited experience with making an API with Rails, but could your problem be solved by wrapping your find/save/update in a transaction?
Something like:
ActiveRecord::Base.transaction do
api_fields.each do |api_field|
content_model_field = @content_entry.content_model_fields.where(field_id: api_field).first
content_entry_field = @content_entry.content_entry_fields.where(content_model_field_id:: content_model_field.id).first_or_create
content_entry_field.update_attributes(value: params[:entryData][:fields][api_field.to_sym])
end
end
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
No clue if this would actually work, just throwing it out there =D
Thank you very much! Transactions work great!
I just hat do change
content_entry_field.update_attributes(value: params[:entryData][:fields][api_field.to_sym])
to
content_entry_field.value = params[:entryData][:fields][api_field.to_sym]
content_entry_field.save!
in order to raise an exception if there is an validation error.
Well, there is one thing I wish I could handle differently: what if 3 of the actions fail due a validation error? With the transaction every error would be validated step by step and not all errors as a whole 🤔
Hmm, this I'm not sure of. Someone with more knowledge of transactions and/or validations may have a solution if it's even possible...
From my understanding, a transaction gets rolled back as soon as there are any exceptions thrown. This is why when you have 3 validations, it will roll back the very first time an exception is thrown, so the remaining 2 validations aren't even ran (I think at least, but I could be wrong here).
You may end up having to make a custom validation to check the data before it's even handed over to the transaction. This should be fine to do if it's simple validations, like presence or data type. However, if you have to check for uniqueness then it would result in extra hit to the DB and wouldn't necessarily protect you from a race condition.