ruby-on-railsruby-on-rails-6friendly-id

Modified ID in URLs to avoid guessing different integer IDs in Rails


I am asking almost to the same question asked here: Encrypted ID in URLs

Only I don't necessarily need the ID encrypted, I use a random string code. And I have tried a similar solution to what João Carlos Ottobboni answered then, with FriendlyId, as below:

In my models/coupon.rb

class Coupon < ApplicationRecord
  extend FriendlyId
  friendly_id :code #code is already a coupon column

  validates :code, uniqueness: true
  before_create :generate_code

  def generate_code
    all_letters = [('A'..'Z'), ('0'..'9')].map(&:to_a).flatten
    random_string = (0...6).map { all_letters [rand(36)] }.join
    self.code = random_string
  end

And in my controllers/coupon_controller.rb

class CouponsController < ApplicationController
before_action :set_coupon, only: [:show, :edit, :update, :redeem, :destroy]
...
  private
    def set_coupon
      @coupon = Coupon.friendly.find(params[:id])
    end

When I'm redirected to a specific coupon path it actually works, redirects me to coupons/SJKE3K instead of coupons/13. But it doesn't prevent me from been able to type 13 in the url and it redirects me too. How can i avoid that? I need the coupons to be accessible only through the code and not through the id.


Solution

  • Coupon.friendly.find(params[:id]) allows both code and id to be used to find the record. If you switch to Coupon.find_by!(code: params[:id]) only the code will be able to find the coupon.

    In that case you don't need Friendly ID at all, you can just continue manually generating the code and finding the record with it.

    That will require a bit more work when generating forms that use the coupon or routes including the coupon as these will always use the integer ID by default - you'll have to explicitly pass id: coupon.code as an argument to the url/path helpers and pass url: my_path(id: coupon.code) or similar when creating forms.