Rails validatation to ensure a username does not clash with an existing route?

后端 未结 1 772
借酒劲吻你
借酒劲吻你 2021-01-12 16:12

I want to ensure users can\'t create usernames that clash with my existing routes. I would also like the ability to deny future routes I may define. I am thinking of accompl

1条回答
  •  礼貌的吻别
    2021-01-12 16:32

    Here's my own answer, tested and working with Rails 3.1.3 and Ruby 1.9.3

    app/models/user.rb

    class User < ActiveRecord::Base
      class_attribute :invalid_usernames
      self.invalid_usernames = Set.new %w()
    
      validates :username, presence:   true,
                           uniqueness: { case_sensitive: false },
                           exclusion:  { in: lambda { self.invalid_usernames }}
    end
    

    config/application.rb

    [:after_initialize, :to_prepare].each do |hook|
      config.send(hook) do
        User.invalid_usernames += Rails.application.routes.routes.map(&:path).join("\n").scan(/\s\/(\w+)/).flatten.compact.uniq
      end
    end
    

    Notes

    I initially tried setting User.invalid_usernames during after_initialize but found it needs to be set during to_prepare (i.e. before each request in development mode, and before the first request in production mode) since the models are reloaded in development before each request and the original setting is lost.

    I am however also setting User.invalid_usernames during after_initialize because the routes don't seem to be available during to_prepare when running in the test environment. Another workaround I tried for this, which does work, is to force load the routes during to_prepare:

    config.to_prepare do
      Rails.application.reload_routes!
      User.invalid_usernames += Rails.application.routes.routes.map(&:path).join("\n").scan(/\s\/(\w+)/).flatten.compact.uniq
    end
    

    I like this because it's DRY and easy to read. But I'm wary of reloading the routes on every request, even if it is only in development mode. I'd rather use something a little harder to read if it means I fully understand the impact. Open to criticisms!

    I also ditched cattr_accessor for class_attribute when I found out the former applies to the entire class hierarchy (i.e. changing its value on a subclass would affect the superclass)

    I also chose to use a Set for User.invalid_usernames instead of an array as there's no need to store and compare against dupes and it was a transparent change.

    I also changed to Ruby 1.9 hash syntax (:

    0 讨论(0)
提交回复
热议问题