udit
udit

Reputation: 2783

Rendering ActiveRecord validation errors with attributes AND full error messages

Here's a simple controller update action:

  def update
    note = Note.find(params[:id])

    if note.update(note_params)
      render json: note.to_json
    else
      render json: {errors: note.errors}, status: :unprocessable_entity
    end
  end

This renders errors in the form {"errors":{"title":["can't be blank"]}}

but I want it in the form of {"errors":{"title":["Title can't be blank"]}}

Simply using {errors: note.errors.full_messages} gives {:errors=>["Title can't be blank"]} and misses the attribute keys.

The only way I can get it into the desired form seems to be a bit more involved:

      full_messages_with_keys = note.errors.keys.inject({}) do |hash, key|
        hash[key] = note.errors.full_messages_for(key)
        hash
      end
      render json: {errors: full_messages_with_keys}, status: :unprocessable_entity

This works, but it seems odd that I have to do this since it seems to be a pretty common use case for doing validations on a SPA front-end. Is there a built-in method/more canonical way?

Upvotes: 2

Views: 2699

Answers (2)

user1032752
user1032752

Reputation: 871

You can pass an additional argument to to_hash or to_json to generate messages that include the attribute name:

person.errors.to_hash(true)

or

person.errors.to_json(full_messsages: true)

will both return {"title":["Title can't be blank"]}

Upvotes: 1

max
max

Reputation: 102036

You can use ActiveModel::Errors#group_by_attribute to get a hash of errors per key:

person.errors.group_by_attribute
# => {:name=>[<#ActiveModel::Error>, <#ActiveModel::Error>]}

From there is simply a matter of generating the full message from each ActiveModel::Error instance:

note.errors
    .group_by_attribute
    .transform_values { |errors| errors.map(&:full_message) }

Is there a built-in method/more canonical way?

Not really. A framework can't cover every possible need. It provides the tools needed to format the errors however you want.

However instead of repeating this all across your controllers this functionality can be pushed into the model layer, a serializer or even monkeypatched onto ActiveModel::Errors if you want to get crazy.

class ApplicationRecord < ActiveRecord::Base
  def grouped_errors
    errors.group_by_attribute
          .transform_values { |errors| errors.map(&:full_message) }
  end
end

Upvotes: 1

Related Questions