如何在Rails的before_save回调中验证用户与健身房Geocoder对象的distance_to小于200米以创建CheckIn记录?
Fixing Your Check-In Distance Validation in Rails
First off, great start with using Geocoder for this check-in distance validation! Let's work through the issues in your code to get it working properly:
Key Problems in Your Current Code
- Models can't access
params: YourCheckInmodel is trying to useparams[:gym_id], butparamsis a controller-only object—models don't have access to it. - Hardcoded coordinates: You're using
[43.9,-98.6]instead of the user's actual check-in location that's being submitted. - Inefficient Gym lookup: Since
CheckInbelongs_to :gym, you don't need to query the gym again—you can just use the already-associatedself.gym. - Validation doesn't block invalid saves: Your
before_savemethod only returnstruewhen the distance is valid, but doesn't prevent saves when it's invalid (and doesn't add error messages for debugging). - Controller doesn't handle failures: Using
create!will raise an exception if validation fails, which will crash your API instead of returning a useful error response.
Corrected Code
app/models/check_in.rb
class CheckIn < ApplicationRecord belongs_to :user belongs_to :gym before_save :validate_check_in_distance protected def validate_check_in_distance # Use the associated gym instead of re-querying gym_location = [gym.latitude, gym.longitude] # Use the check-in's submitted coordinates instead of hardcoding user_check_in_location = [check_in_latitude, check_in_longitude] # Calculate distance (specify meters explicitly to avoid unit confusion) distance_to_gym = Geocoder::Calculations.distance_between(gym_location, user_check_in_location, units: :meters) if distance_to_gym >= 200 # Add an error message and return false to block the save errors.add(:base, "You must be within 200 meters of the gym to check in") return false end true end end
app/controllers/api/v1/check_ins_controller.rb
class Api::V1::CheckInsController < ApplicationController before_action :authenticate_request! def check_in @check_in = CheckIn.new(check_in_params.merge(user: current_user)) if @check_in.save render json: CheckInBlueprint.render(@check_in, root: :data), status: :created else # Return meaningful error messages if validation fails render json: { errors: @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
Notes on the Fixes
- Accessing attributes correctly: The model uses the instance's own
check_in_latitude/check_in_longitudeand the associatedgym's location—this works because the controller passes these params to the model when building the record. - Explicit distance calculation: Using
Geocoder::Calculations.distance_betweenmakes the logic clearer, and specifyingunits: :metersensures you're measuring in the exact unit you need. - Error handling: Adding errors to the model lets you return user-friendly feedback, and the controller now gracefully handles both success and failure cases without crashing.
- Avoiding exceptions: Using
new+saveinstead ofcreate!prevents unhandled exceptions when validation fails, which is critical for a stable API.
内容的提问来源于stack exchange,提问作者viatech
相关产品推荐
相关产品推荐

