Ask A Question

Notifications

You’re not receiving notifications from this thread.

Update and validate multiple entries

Andre Zimpel asked in Rails

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!

Reply

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

Reply

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.

Reply

Oh sweet, glad that worked out!!

Reply

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 🤔

Reply

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.

Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 86,946+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.