simple_form: entradas dinamicamente desativadas com base na política do Pundit

Captura de tela 23/02/2017 em 10.59.05.png

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 Personmodelos 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, Userele escolhe a pessoa com a qual está associado. Porém, uma vez configurado, não deve ser permitido alterar a Personrelação. Foi assim que consegui isso tanto no back-end (parâmetros fortes) quanto no front-end (simple_form)

Em minha ApplicationPolicyclasse 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::Baseum 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!