Eu uso o Pundit para gerenciar autorização em aplicativos Rails. Geralmente, há uma proporção de 1: 1 de modelos para classes de política com uma estratégia de herança que torna a implementação de autorização bastante limpa. Ele permite que você controle quem pode criar, atualizar e excluir quais tipos ou quais registros específicos.
Eu queria dar um passo adiante e entrar na autorização de atributos específicos. Tome por exemplo minhas configurações onde tenho Person
modelos e então uma pessoa pode ter um associado User
. Isso permite uma separação mais clara de preocupações com as informações da pessoa e da conta do usuário. Quando um administrador cria um novo, User
ele escolhe a pessoa com a qual está associado. Porém, uma vez configurado, não deve ser permitido alterar a Person
relação. Foi assim que consegui isso tanto no back-end (parâmetros fortes) quanto no front-end (simple_form)
Em minha ApplicationPolicy
classe base para políticas do Pundit, configurei alguns métodos para tornar isso mais fácil:
class ApplicationPolicy
...
# Default permitted attributes for create and update
def permitted_attributes
# slug is generated by friendly_id and should never be updatable
# id and timestamp fields are never directly updatable
record.attributes.keys.map(&:to_sym) - %i(id slug created_at updated_at deleted_at)
end
def permitted_create_attributes
permitted_attributes
end
def permitted_update_attributes
permitted_attributes
end
end
Então, na política para a Users
, construímos isso para ficar mais granular.
class UserPolicy < ApplicationPolicy
def permitted_attributes
# fields maintained by devise are not directly updatable
super - %i(encrypted_password reset_password_token reset_password_sent_at)
end
def permitted_update_attributes
# for update, it's not allowed to change the person or email (username)
permitted_attributes - %i(person person_id email)
end
end
Agora usarei essas listas de atributos em dois lugares. O primeiro está no meu controlador no lugar da lista usual de parâmetros fortes.
protected
def permitted_params
policy(@user).send("permitted_#{action_name}_attributes")
end
Isso é ótimo e nos dá exatamente a lista de atributos graváveis que desejamos, mas agora queremos que isso seja refletido na IU para que os campos fiquem esmaecidos.
Para fazer isso, tive que corrigir SimpleForm::Inputs::Base
um pouco a classe. Veja como:
# lib/simple_form_extensions/disabled_extensions.rb
module SimpleForm
module DisabledExtensions
private
# I didn't name this method, so don't yell at me for its name!
# rubocop:disable Style/PredicateName
def has_disabled?
super || disabled_by_policy?
end
def disabled_by_policy?
# permit attributes by name or relation name. e.g. person or person_id
!(permitted_attributes.include?(attribute_name) || permitted_attributes.include?(reflection_or_attribute_name))
end
def permitted_attributes
# load the Pundit policy for the object
policy = template.controller.policy(object)
# map action names
action = { edit: :update, new: :create }[template.controller.action_name.to_sym]
# Get the list of permitted attributes as an array of attribute names
# For attributes that permit array values, the entry is not a name, but
# a hash with the key as the attribute name and a value of []
# So for such cases, pluck the hash key as the name
@permitted_attributes ||= policy.send("permitted_#{action}_attributes".to_sym)
.map { |a| a.is_a?(Hash) ? a.keys.first : a }
end
end
end
SimpleForm::Inputs::Base.send :prepend, SimpleForm::DisabledExtensions
Então, no topo de config/initializers/simple_form.rb
require 'simple_form_extensions/disabled_extensions'
Reinicie o servidor e agora a IU desativa automaticamente os campos que não são atualizáveis de acordo com a política do Pundit!