viatech
viatech

Reputation: 89

How to validate that the distance_to of two Geocoder objects is less than 200 mts within a Rails before_save callback?

I am a RoR beginner and I need to check that the distance between user and gym Geocoder objects is less than 200 meters in order to create a check_in object in the DB

Here is my code:

app/models/check_in.rb

class CheckIn < ApplicationRecord
  belongs_to :user
  belongs_to :gym

  before_save :check_in_distance

  protected

  def check_in_distance
    gym = Gym.find_by(id: params[:gym_id])
    distance_to_gym = gym.distance_to([43.9,-98.6])
    if distance_to_gym < 200
      return true
    end
  end

end


app/controllers/api/v1/check_inscontroller.rb


class Api::V1::CheckInsController < ApplicationController

  before_action :authenticate_request!

  def check_in
    @check_in = CheckIn.create!(check_in_params.merge( user: current_user))
    render json: CheckInBlueprint.render(@check_in, root: :data)
  end

  private

  def check_in_params
    params.require(:check_in).permit(:gym_id, :check_in_latitude,
                                   :check_in_longitude)
  end
end

Upvotes: 0

Views: 62

Answers (1)

max
max

Reputation: 102250

You want a custom validation and not a callback:

class CheckIn < ApplicationRecord
  validate :distantance_to_gym

  def distantance_to_gym
    distance = gym.distance_to([check_in_latitude, check_in_longitude])
    errors.add(:base, 'check in is too far from gym') if distance < 200
  end
end

Don't use create! in your controller. Sure its great for lazy debugging, but it raises an exception if the user passes invalid input. Invalid input is not an exceptional event and exceptions should not be used for normal control flow.

The "bang" methods such as .create! should only be used in non-interactive contexts like seed files where creating the record should not be expected to fail or when wrapping it in a transaction to cause a rollback.

Use the normal "non-bang" methods such as save and create and check if the record was actually persisted and respond accordingly:

# see https://github.com/rubocop/ruby-style-guide#namespace-definition
module Api
  module V1
    class CheckInsController < ApplicationController
      # this should be moved to the parent controller
      # use a secure by default model where you opt out instead
      before_action :authenticate_request!
 
      def check_in
        @check_in = CheckIn.new(check_in_params) do |c| 
          c.user = current_user
        end
        if @check_in.save
          render json: CheckInBlueprint.render(@check_in, root: :data),
                 status: :created
        else
          render json: @check_in.errors.full_messages,
                 status: :unprocessable_entity
        end
     end

     private

     def check_in_params
       params.require(:check_in).permit(:gym_id, :check_in_latitude,
                                   :check_in_longitude)
     end
    end
  end
end

Upvotes: 1

Related Questions