Rails验证以确保用户名不会与现有路由冲突?

poo*_*nza 7 validation activerecord ruby-on-rails activemodel ruby-on-rails-3

我想确保用户无法创建与我现有路由冲突的用户名.我也希望能够拒绝我可能定义的未来路线.我想要完成这样的事情:

在模型中:

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

  cattr_accessor :invalid_usernames

  validates :username, :exclusion { :in => @@invalid_usernames }
end
Run Code Online (Sandbox Code Playgroud)

在一些初始化程序中:

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

这是"Rails方式"吗?有没有更好的办法?

poo*_*nza 6

这是我自己的答案,测试和使用Rails 3.1.3和Ruby 1.9.3

应用程序/模型/ 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
Run Code Online (Sandbox Code Playgroud)

配置/ 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
Run Code Online (Sandbox Code Playgroud)

笔记

我最初尝试设置User.invalid_usernames,after_initialize但发现它需要在开发期间to_prepare(即在开发模式中的每个请求之前,以及生产模式中的第一个请求之前)设置,因为模型在每个请求之前重新加载并且原始设置丢失.

然而,我也在设置User.invalid_usernames,after_initialize因为to_prepare在测试环境中运行时,路径似乎不可用.我为此尝试的另一个解决方法是,在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
Run Code Online (Sandbox Code Playgroud)

我喜欢这个,因为它很干并且易于阅读.但我担心在每个请求上重新加载路由,即使它只是在开发模式下.我宁愿使用的东西有点困难,如果这意味着我完全理解阅读的冲击.开放批评!

当我发现前者适用于整个类层次结构时(也就是更改它在子类上的值会影响超类)时我也抛弃cattr_accessorclass_attribute

我还选择使用Setfor User.invalid_usernames而不是数组,因为不需要存储和比较dupes,这是一个透明的变化.

我还改为Ruby 1.9哈希语法(: