Quiero asegurarme de que los usuarios no puedan crear nombres de usuario que entren en conflicto con mis rutas existentes. También me gustaría tener la capacidad de negar rutas futuras que pueda definir. Estoy pensando en lograr esto así:

En el modelo:

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

  cattr_accessor :invalid_usernames

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

En algún inicializador:

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

¿Es esto "Rails way"? ¿Existe una forma mejor?

7
poochenza 3 ene. 2012 a las 03:20
Perdón por la confusión, eliminé mis otras validaciones. Esto es simplemente para evitar conflictos con rutas sin nombre de usuario . Para conflictos con otros nombres de usuario, ya tengo una validación de unicidad más restricciones de nivel de base de datos. Editar: por cierto, si necesita hacerlo dinámicamente, siempre puede usar :in => lambda { ... } (que es en realidad lo que debería usar arriba independientemente, creo)
 – 
poochenza
3 ene. 2012 a las 16:21

1 respuesta

La mejor respuesta

Aquí está mi propia respuesta, probada y trabajando con Rails 3.1.3 y 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

Notas

Inicialmente intenté configurar User.invalid_usernames durante after_initialize, pero descubrí que debe configurarse durante to_prepare (es decir, antes de cada solicitud en el modo de desarrollo y antes de la primera solicitud en modo de producción) ya que los modelos se recargan en desarrollo antes de cada solicitud y se pierde la configuración original.

Sin embargo, estoy también configurando User.invalid_usernames durante after_initialize porque las rutas no parecen estar disponibles durante to_prepare cuando se ejecuta en el entorno de prueba. Otra solución alternativa que probé para esto, que funciona, es forzar la carga de las rutas durante 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

Me gusta esto porque es SECO y fácil de leer. Pero desconfío de recargar las rutas en cada solicitud, incluso si solo está en modo de desarrollo. Prefiero usar algo un pequeño más difícil de leer si eso significa que entiendo completamente el impacto. ¡Abierto a críticas!

También abandoné cattr_accessor por class_attribute cuando descubrí que el primero se aplica a toda la jerarquía de clases (es decir, cambiar su valor en una subclase afectaría a la superclase)

También elegí usar un Set para User.invalid_usernames en lugar de una matriz, ya que no hay necesidad de almacenar y comparar con los incautos y fue un cambio transparente.

También cambié a la sintaxis hash Ruby 1.9 (:

6
poochenza 3 ene. 2012 a las 18:56
También puede filtrar las rutas en función de .verb si desea ser más relajado (aunque busque '' y 'GET'). Es posible que desee hacer algo con rutas comodín (/*page.html y demás). Y un after_initialize gancho para asegurarse de que no ha agregado una ruta que se toma como nombre de usuario sería una buena idea también.
 – 
mu is too short
4 ene. 2012 a las 10:40
Y puedes aceptar tus propias respuestas (incluso hay una insignia) y esta es sin duda una buena candidato a aceptar su propia respuesta.
 – 
mu is too short
4 ene. 2012 a las 10:42