rocket_pants
rocket_pants copied to clipboard
Displaying validation errors in client application forms
Hi Darcy, Thanks for the handy gem. I've got one question I couldn't find an easy solution for, maybe you can help me out with this. I have two separate applications (one for regular users, another administration) with a single user base, the admin app acting as a web service for the user app (the API client). I need to display validation errors on login/registration forms in the user app. Since the user app is not backed by a DB, things become a little unwieldy on the client side since I don't get any of the ActiveModel convenience stuff out of the box when using RP clients instead of ActiveRecord models. Here's what I had to do to get the errors displayed properly.
I defined base classes for clients and the hashes they encapsulate (they are later used to trick form builder into binding input fields to attributes). The encapsulated 'model' has to have a model_name method and an ActiveModel error hash, otherwise simple_form_for
will die with exceptions I've spent about 3 hours debugging :(
module Api
class Base < RocketPants::Client
version 1
base_uri Rails.application.config.api.base_uri
attr_accessor :auth_token
def base_request_options
session[:user] ? { headers: { 'X-AUTH' => session[:user][:auth_token] } } : {}
end
end
class ModelBase < APISmith::Smash
property :id
property :errors
def self.model_name
ActiveModel::Name.new(self)
end
def to_key
id
end
def populate_errors_from(error_hash)
self.errors = ActiveModel::Errors.new(self)
error_hash.each do |attr, msgs|
msgs.each { |msg| self.errors.add(attr, msg) }
end
end
end
end
Now, a client for the User model:
class Api::UserClient < Api::Base
class User < Api::ModelBase
property :first_name
property :last_name
property :email
property :password
property :password_confirmation
property :provider_id
end
attr_reader :user
def initialize(*args)
@user = User.new(*args)
super(*args)
end
def find(id)
get "/user", extra_query: { id: id }
end
def save
post "/users", extra_query: { user: @user }
end
end
The corresponding controller on the client application side is below. It rescues the invalid record exception raised by the client base code, populates the error hash and re-renders the action with the form.
class UsersController < ApplicationController
skip_before_filter :authenticate, only: [:new, :create]
def create
@user_client = Api::UserClient.new(params[:user])
respond_to do |format|
begin
@user_client.save
rescue RocketPants::InvalidResource => e
format.html do
@user = @user_client.user
@user.populate_errors_from(e.errors)
render action: :new
end
else
format.html { redirect_to root_path, notice: t("signed_up_but_not_approved") }
end
end
end
end
This works, but feels way too convoluted for a simple scenario like this. I feel there should be an easier way to do this. I guess this post is a little too long, but anyway I'd be grateful if you could read through this and point me in the right direction here. I hope I've explained everything clear enough.
Thanks.