Leo
Leo

Reputation: 109

RoR: Validate non-model field before create controller method

I need to validate a field before the create method

In my _form.html.erb I have two models, one is the owner model, and the other is a model I create to have other arguments, I need to validate those arguments before getting in the create method, I can use an if, but it is not the best practice to do it.

def create
  @customer = Customer.new(customer_params)
  #read the city name, since this is requested by city name (string) and it shoud be "id" in the system
  city = city_params()
  @customer.city_id = City.find_by(name: city["name"]).id
  respond_to do |format|
    if @customer.save
      format.html { redirect_to @customer, notice: 'Customer was successfully created.' }
      format.json { render action: 'show', status: :created, location: @customer }
    else
      format.html { render action: 'new' }
      format.json { render json: @customer.errors, status: :unprocessable_entity }
    end
  end
end

I need to validate the the city name, because the customer owner must have the city_id, and the _form requests the name (string), so I need to find the city but previously I need to validate the city name has a value and it exists,

How can I validate this in the model ?

Upvotes: 1

Views: 923

Answers (3)

Richard Peck
Richard Peck

Reputation: 76784

before_validate

You could use the before_validate callback in your model:

#app/models/customer.rb
Class Customer < ActiveRecord::Base
   attr_accessor :city_name
   before_validate :set_city

   private

   def set_city
       city_id = City.find_by(name: city_name).id
   end
end

--

Custom Validation Method

I think the bottom line is you'll be best using a custom validation method for this. You basically want to return the user to the form with an error saying "City not found" or similar; which is entirely within the remit of a custom validation method:

#app/models/customer.rb
Class Customer < ActiveRecord::Base
   validate :check_city_id

   private

   def check_city_id
       errors.add(:city_id, "City doesn't exist") unless City.try city_id
   end
end

--

System

This kind of issue can be handled by simply giving the user options to select the id at input; rather than selecting by name:

#app/views/customers/new.html.erb
<%= form_for @customer do |f| %>
   <%= f.select :city_id, City.all.collect {|p| [ p.name, p.id ] } %>
<% end %>

I think your method of giving the user the ability to pick a city name, and then validating in the backend is very inefficient; whilst giving the user a rigid set of options to select a buyer by city is far more robust

Upvotes: 0

Chris Peters
Chris Peters

Reputation: 18090

If I were you, I would start out by keeping all of this logic in the controller and use a filter to find the city:

class CustomersController < ApplicationController
  before_action :find_city, only: [:create, :update]

  def create
    @customer = Customer.new(customer_params)
    #read the city name, since this is requested by city name (string) and it shoud be "id" in the system
    @customer.city_id = @city.try(:id) # This returns `nil` if the city was not found
    respond_to do |format|
      if @customer.save
        format.html { redirect_to @customer, notice: 'Customer was successfully created.' }
        format.json { render action: 'show', status: :created, location: @customer }
      else
        format.html { render action: 'new' }
        format.json { render json: @customer.errors, status: :unprocessable_entity }
      end
    end
  end

private

  def find_city
    @city = City.find_by(name: params[:city][:name]) # No need for strong parameters for this
  end
end

Then make sure you're validating the presence of city_id in your Customer class:

class Customer < ActiveRecord::Base
  validates :city_id, presence: true
end

Later on, if you find that you need this logic to be extracted from the controller, then consider looking at creating a service object or a form object. Because this is a simple case (only 2 classes are involved), I would hold off on creating those constructs for now though. The controller layer is sufficient enough to handle this simple logic.

Why not move the logic directly into the model? I can tell you from experience that you do not want to mess your model up with tons of logic involving other model classes. Customer should not really know much about City in my opinion.

Upvotes: 2

userRandom
userRandom

Reputation: 160

We have something called callbacks http://api.rubyonrails.org/classes/AbstractController/Callbacks/ClassMethods.html ..using this we can trigger our required validations in the model.

You can create your own logic of validation like example

before_create :method_name 

def method_name
 your logic.....example: validates :city_name ..

end

Upvotes: -1

Related Questions