Recently I added validations to one of the models in my application, and this seems to have caused a somewhat strange behaviour that I'm not handling properly in my code.
Here's a hypothetical example:
Clients
# name: string, phone: string, address: string
class Client < ActiveRecord::Base
has_many :transactions
accepts_nested_attributes_for :transaction, allow_destroy: true
validate :phone, numericality: true
end
Transactions
# p_date: date, location_id: integer
class Transaction < ActiveRecord::Base
belongs_to :client
end
This is how the controller would look like (again, have in mind that this is hypothetical):
PurchasesController
before_action :set_client, only: [:show, :edit, :update, :destroy]
def update
respond_to do |format|
if @client.update(client_params)
format.html { redirect_to clients_path, notice: 'Updated Succesfully' }
format.json { render :show, status: :ok, location: @client }
else
format.html { render :edit }
format.json { render json: @client.errors, status: :unprocessable_entity }
end
end
end
def client_params
params.require(:client).permit(
:name, :phone, :address,
transaction_attributes: [:id, :p_date, :location_id, :_destroy]
)
end
def set_client
@client = Client.find(params[:id])
end
For new records this works fine, but when I run into old ones that do not conform to the new validation rules in the phone number, the nested attributes aren't saved, because it's parent record is no longer valid.
I'm trying to find a way how to handle such errors.
Currently, this would be handled by the else condition in if @client.update(client_params) in the controller. When an error happens, the controller renders the :edit action, which results in another error in my view, cause now the helper that generates the fields for the nested form is receiving a null value for @client.
The view in question that generates the error looks like this:
purchases/:client_id/edit.html.haml
= form_for @client, :url => {:controller => 'purchase', :action => 'update'} do |f|
- if @client.errors.any?
#error_explanation
%h2
The following errors were found:
%ul
- @client.errors.full_messages.each do |message|
%li= message
=render 'form', f: f
.actions
=f.submit 'Save Changes', :class => 'btn btn-md btn-primary'
The error says: "First argument in form cannot contain nil or be empty", which I'm assuming it is cause the render is not sending the id of the Client.
In case you're wondering, I'm using form_for @client, :url => {:controller => 'purchase', :action => 'update'} do |f| cause this view is not in the Client controller. if I omit the extra parameters, the form is sent directly to the Client controller, which has different code pertaining only to the Client model.
I've partially managed to work around this by using the following in the update action:
def update
respond_to do |format|
if @client.update(client_params)
# *snip*
else
format.html { redirect_to edit_purchase_path(@client) }
format.json { render json: @client.errors, status: :unprocessable_entity }
end
end
end
This will redirect me back to the edit action, but no errors are printed. I get an identical page with the values used before I edited the values of the Transaction. I'm think there must be an easy way to send the errors back to the view, but I'm not sure where are these supposed to be used.
The "original" controller was generated by a scaffold, so the render :edit part is from the scaffolding itself. I'm aware my example could be somewhat vague (I'm just transcribing what I'm experiencing), so bear with me if this sounds a little odd. I'll gladly go into more detail if the information provided isn't enough.