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

纵饮孤独 提交于 2019-12-01 01:27:49

问题


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 accomplishing this like so:

In the model:

class User < ActiveRecord::Base
  @@invalid_usernames = %w()

  cattr_accessor :invalid_usernames

  validates :username, :exclusion { :in => @@invalid_usernames }
end

In some initializer:

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

Is this "Rails way"? Is there a better way?


回答1:


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 (:



来源:https://stackoverflow.com/questions/8706329/rails-validatation-to-ensure-a-username-does-not-clash-with-an-existing-route

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!