InfoGrab DocsInfoGrab Docs

DeclarativePolicy 프레임워크

요약

DeclarativePolicy 프레임워크는 정책 검사 성능을 향상시키고 EE 확장을 용이하게 하기 위해 설계되었습니다. 사용되는 정책은 subject의 클래스명을 기반으로 결정됩니다. Ruby gem 소스는 declarative-policy GitLab 프로젝트에서 확인할 수 있습니다.

DeclarativePolicy 프레임워크는 정책 검사 성능을 향상시키고 EE 확장을 용이하게 하기 위해 설계되었습니다. app/policies의 DSL 코드는 Ability.allowed?가 특정 액션이 subject에 허용되는지 확인하는 데 사용됩니다.

사용되는 정책은 subject의 클래스명을 기반으로 결정됩니다. 예를 들어 Ability.allowed?(user, :some_ability, project)ProjectPolicy를 생성하고 해당 정책에 대한 권한을 확인합니다.

Ruby gem 소스는 declarative-policy GitLab 프로젝트에서 확인할 수 있습니다.

명명 규칙에 대한 정보는 권한 규칙을 참조하세요.

권한 규칙 관리#

권한은 conditionsrules의 두 부분으로 나뉩니다. Conditions는 데이터베이스와 환경에 접근할 수 있는 불리언 표현식이며, rules는 특정 능력을 활성화하거나 차단하는 표현식과 다른 rules의 정적으로 구성된 조합입니다. 능력이 허용되려면 적어도 하나의 rule에 의해 활성화되고, 어떤 rule에 의해서도 차단되어서는 안 됩니다.

Conditions#

Conditions는 condition 메서드로 정의되며, 이름과 블록이 주어집니다. 블록은 정책 객체의 컨텍스트에서 실행됩니다. 따라서 @user@subject에 접근하거나 정책에 정의된 모든 메서드를 호출할 수 있습니다. @user는 nil일 수 있지만(익명의 경우), @subject는 subject 클래스의 실제 인스턴스임이 보장됩니다.

class FooPolicy < BasePolicy
  condition(:is_public) do
    # @subject guaranteed to be an instance of Foo
    @subject.public?
  end

  # instance methods can be called from the condition as well
  condition(:thing) { check_thing }

  def check_thing
    # ...
  end
end

condition을 정의하면 해당 condition이 충족되는지 확인하는 predicate 메서드가 정책에 정의됩니다. 위의 예에서 FooPolicy의 인스턴스는 #is_public?#thing?에도 응답합니다.

Conditions는 scope에 따라 캐시됩니다. Scope와 순서는 뒤에서 다룹니다.

Rules#

rule은 conditions와 다른 rules의 논리적 조합으로, 특정 능력을 활성화하거나 차단하도록 구성됩니다. rule 구성은 정적입니다. rule의 로직은 데이터베이스에 접근하거나 @user 또는 @subject에 대해 알 수 없습니다. 이를 통해 condition 수준에서만 캐싱할 수 있습니다. Rules는 DSL 구성의 블록을 받아 #enable 또는 #prevent에 응답하는 객체를 반환하는 rule 메서드를 통해 지정됩니다.

class FooPolicy < BasePolicy
  # ...

  rule { is_public }.enable :read
  rule { ~thing }.prevent :read

  # equivalently,
  rule { is_public }.policy do
    enable :read
  end

  rule { ~thing }.policy do
    prevent :read
  end
end

rule DSL 내에서 다음을 사용할 수 있습니다.

  • 일반 단어는 이름으로 condition을 참조합니다. 해당 condition이 truthy일 때 적용되는 rule입니다.

  • ~는 부정을 나타내며, negate로도 사용할 수 있습니다.

  • &|는 논리 조합으로, all?(...)any?(...)로도 사용할 수 있습니다.

  • can?(:other_ability):other_ability에 적용되는 rules에 위임합니다. 이는 동적으로 확인할 수 있는 인스턴스 메서드 can?과 다릅니다. 이는 다른 ability에 대한 위임만 구성합니다.

~, &, | 연산자는 DeclarativePolicy::Rule::Base에서 오버라이드된 메서드입니다.

rule DSL 내에서 &&||와 같은 불리언 연산자를 사용하지 마세요. rule 블록 내의 conditions는 불리언이 아닌 객체이기 때문입니다. 삼항 연산자(condition ? ... : ...)와 if 블록에도 동일하게 적용됩니다. 이러한 연산자는 오버라이드할 수 없으므로 custom cop을 통해 금지됩니다.

점수, 순서, 성능#

rules가 판정으로 평가되는 방식을 보려면 Rails 콘솔을 열고 policy.debug(:some_ability)를 실행하세요. 이는 rules가 평가되는 순서대로 출력합니다.

예를 들어, IssuePolicy를 디버그하려면 다음 명령을 실행할 수 있습니다.

user = User.find_by(username: 'sidneyjones')
issue = Issue.first
policy = IssuePolicy.new(user, issue)
policy.debug(:read_issue)

디버그 출력 예시는 다음과 같습니다.

- [0] prevent when all?(confidential, ~can_read_confidential) ((@sidneyjones : Issue/1))
- [0] prevent when archived ((@sidneyjones : Project/4))
- [0] prevent when issues_disabled ((@sidneyjones : Project/4))
- [0] prevent when all?(anonymous, ~public_project) ((@sidneyjones : Project/4))
+ [32] enable when can?(:reporter_access) ((@sidneyjones : Project/4))

각 줄은 평가된 rule을 나타냅니다. 주목할 몇 가지 사항이 있습니다.

  • - 기호는 rule 블록이 false로 평가되었음을 나타냅니다. + 기호는 rule 블록이 true로 평가되었음을 나타냅니다.

  • 괄호 안의 숫자는 점수를 나타냅니다.

  • 줄의 마지막 부분(예: @sidneyjones : Issue/1)은 해당 rule의 사용자명과 subject를 보여줍니다.

여기서 처음 네 개의 rules가 해당 사용자와 subject에 대해 false로 평가되었음을 알 수 있습니다. 예를 들어, 마지막 줄에서 사용자 sidneyjonesProject/4에서 Reporter 권한을 가지고 있었기 때문에 rule이 활성화되었음을 알 수 있습니다.

정책이 특정 ability가 허용되는지 묻는 경우(policy.allowed?(:some_ability)), 정책의 모든 conditions를 계산할 필요는 없습니다. 먼저, 해당 특정 ability와 관련된 rules만 선택됩니다. 그런 다음 실행 모델은 단락 평가를 활용하여 계산 비용에 대한 휴리스틱을 기반으로 rules를 정렬하려고 합니다. 정렬은 동적이고 캐시 인식이므로, 이전에 계산된 conditions가 다른 conditions를 계산하기 전에 먼저 고려됩니다.

점수는 개발자가 conditionscore: 파라미터를 통해 선택하여 다른 rules에 비해 이 rule 평가 비용이 얼마나 드는지 나타냅니다.

Scope#

때로는 condition이 @user의 데이터만 사용하거나 @subject의 데이터만 사용합니다. 이 경우 캐싱 scope를 변경하여 불필요하게 conditions를 재계산하지 않으려고 합니다. 예를 들어 다음과 같이 주어진 경우:

class FooPolicy < BasePolicy
  condition(:expensive_condition) { @subject.expensive_query? }

  rule { expensive_condition }.enable :some_ability
end

단순하게 Ability.allowed?(user1, :some_ability, foo)Ability.allowed?(user2, :some_ability, foo)를 호출하면 서로 다른 사용자에 대한 것이므로 condition을 두 번 계산해야 합니다. 하지만 scope: :subject 옵션을 사용하면:

  condition(:expensive_condition, scope: :subject) { @subject.expensive_query? }

condition의 결과는 subject에 기반해서만 전역적으로 캐시됩니다. 따라서 서로 다른 사용자에 대해 반복적으로 계산되지 않습니다. 마찬가지로, scope: :user는 사용자에 기반해서만 캐시됩니다.

condition이 보편적으로 true인 경우 :global scope를 사용할 수 있습니다.

  condition(:earth_exists, scope: :global) { Planet::Earth.exists? }

scope에 대한 자세한 정보는 gem의 캐싱 문서를 참조하세요.

condition이 실제로 user와 subject 모두의 데이터를 사용하는 경우(단순 익명 확인 포함!) :scope 옵션을 사용하면 결과가 너무 전역적인 scope에서 캐시되어 캐시 버그가 발생합니다.

때로는 하나의 subject에 대해 많은 사용자의 권한을 확인하거나, 하나의 사용자에 대해 많은 subjects의 권한을 확인합니다. 이 경우 선호 scope를 설정하려고 합니다. 즉, 시스템에 반복되는 파라미터에서 캐시할 수 있는 rules를 선호한다고 알려줍니다. 예를 들어, Ability.users_that_can_read_project에서:

def users_that_can_read_project(users, project)
  DeclarativePolicy.subject_scope do
    users.select { |u| allowed?(u, :read_project, project) }
  end
end

이는 예를 들어 user.admin?을 확인하는 것보다 project.public?을 확인하는 것을 선호합니다.

위임#

위임은 다른 subject에 대한 다른 정책의 rules를 포함하는 것입니다. 예를 들어:

class FooPolicy < BasePolicy
  delegate { @subject.project }
end

이는 ProjectPolicy의 모든 rules를 포함합니다. 위임된 conditions는 올바른 위임된 subject로 평가되며, 정책의 일반 rules와 함께 정렬됩니다. 특정 ability와 관련된 rules만 실제로 고려됩니다.

오버라이드#

정책은 위임이 의미 없는 경우 위임된 abilities를 선택적으로 제외할 수 있습니다.

위임된 정책은 위임하는 정책에 적합하지 않은 방식으로 일부 abilities를 정의할 수 있습니다. 예를 들어, 일부 abilities는 추론할 수 있고 일부는 추론할 수 없는 부모-자식 관계를 고려하면:

class ParentPolicy < BasePolicy
  condition(:speaks_spanish) { @subject.spoken_languages.include?(:es) }
  condition(:has_license) { @subject.driving_license.present? }
  condition(:is_employed) { @subject.is_employed? }

  rule { speaks_spanish }.enable :read_spanish
  rule { has_license }.enable :drive_car
  rule { is_employed }.enable :earn_money
  rule { ~is_employed }.prevent :earn_money
end

자식 정책이 부모 정책에 위임하는 경우 일부 값이 잘못될 수 있습니다. 자식이 부모의 언어를 말할 수 있다고 올바르게 추론할 수 있지만, 부모가 운전하거나 돈을 벌 수 있다는 이유만으로 자식이 운전하거나 돈을 벌 수 있다고 추론할 수는 없습니다.

이러한 경우 일부를 처리할 수 있습니다. 예를 들어, 자식 정책에서 운전을 금지할 수 있습니다.

class ChildPolicy < BasePolicy
  delegate { @subject.parent }

  rule { default }.prevent :drive_car
end

돈 버는 것은 더 복잡합니다. 부모 정책의 prevent 호출 때문에, 부모가 고용되어 있지 않으면 부모도 자식도 돈을 벌 수 없습니다. 자식 정책에서 명시적으로 :earn_money를 활성화해도 작동하지 않습니다.

ParentPolicy에서 prevent 호출을 제거해도 도움이 되지 않는데, :earn_money를 활성화하는 rules가 부모와 자식 정책 간에 다르기 때문입니다.

위임을 사용하면 자식 리소스는 부모 정책이 명시적으로 차단하지 않은 권한만 활성화할 수 있습니다. 이 경우 돈을 버는 부모를 둔 자식만 돈을 벌 수 있습니다. 하지만 돈을 벌지 못하는 부모도 자식에게 용돈을 주고 싶을 수 있습니다.

해결책은 자식 정책에서 :earn_money ability를 오버라이드하는 것입니다.

class ChildPolicy < BasePolicy
  delegate { @subject.parent }

  overrides :earn_money

  condition(:has_allowance) { @subject.has_allowance? }

  rule { has_allowance }.enable :earn_money
end

이 정의에 따라 ChildPolicy:earn_money를 충족하기 위해 ParentPolicy를 참조하지 않지만, 다른 abilities에는 여전히 사용합니다. 그런 다음 자식 정책은 Parent가 아닌 Child에 적합한 방식으로 :earn_money를 정의할 수 있습니다.

오버라이드 사용의 대안#

정책 위임 오버라이드는 위임이 복잡한 것과 같은 이유로 복잡합니다. 논리적 추론과 의미론에 대한 명확한 이해가 필요합니다. override를 잘못 사용하면 코드가 중복되고, 잠재적으로 차단해야 할 것을 허용하는 보안 버그가 발생할 수 있습니다. 이런 이유로 다른 접근 방식이 불가능한 경우에만 사용해야 합니다.

다른 접근 방식으로는 예를 들어 다른 ability 이름을 사용하는 것이 있습니다. 수입을 버는 것과 용돈을 받는 것은 의미론적으로 구별되며, 다르게 명명할 수 있습니다(이 경우 earn_salaryearn_allowance가 될 수 있습니다). 이는 호출 사이트가 얼마나 다형적인지에 따라 달라질 수 있습니다. 항상 Parent 또는 Child로 정책을 확인한다는 것을 알고 있다면 적절한 ability 이름을 선택할 수 있습니다. 호출 사이트가 다형적이라면 그렇게 할 수 없습니다.

정책 클래스 지정#

주어진 subject에 사용되는 정책을 오버라이드할 수도 있습니다.

class Foo

  def self.declarative_policy_class
    'SomeOtherPolicy'
  end
end

이는 일반적으로 계산된 FooPolicy 클래스 대신 SomeOtherPolicy 클래스에서 권한을 사용하고 확인합니다.

DeclarativePolicy 프레임워크

GitLab v19.1
원문 보기
요약

DeclarativePolicy 프레임워크는 정책 검사 성능을 향상시키고 EE 확장을 용이하게 하기 위해 설계되었습니다. 사용되는 정책은 subject의 클래스명을 기반으로 결정됩니다. Ruby gem 소스는 declarative-policy GitLab 프로젝트에서 확인할 수 있습니다.

DeclarativePolicy 프레임워크는 정책 검사 성능을 향상시키고 EE 확장을 용이하게 하기 위해 설계되었습니다. app/policies의 DSL 코드는 Ability.allowed?가 특정 액션이 subject에 허용되는지 확인하는 데 사용됩니다.

사용되는 정책은 subject의 클래스명을 기반으로 결정됩니다. 예를 들어 Ability.allowed?(user, :some_ability, project)ProjectPolicy를 생성하고 해당 정책에 대한 권한을 확인합니다.

Ruby gem 소스는 declarative-policy GitLab 프로젝트에서 확인할 수 있습니다.

명명 규칙에 대한 정보는 권한 규칙을 참조하세요.

권한 규칙 관리#

권한은 conditionsrules의 두 부분으로 나뉩니다. Conditions는 데이터베이스와 환경에 접근할 수 있는 불리언 표현식이며, rules는 특정 능력을 활성화하거나 차단하는 표현식과 다른 rules의 정적으로 구성된 조합입니다. 능력이 허용되려면 적어도 하나의 rule에 의해 활성화되고, 어떤 rule에 의해서도 차단되어서는 안 됩니다.

Conditions#

Conditions는 condition 메서드로 정의되며, 이름과 블록이 주어집니다. 블록은 정책 객체의 컨텍스트에서 실행됩니다. 따라서 @user@subject에 접근하거나 정책에 정의된 모든 메서드를 호출할 수 있습니다. @user는 nil일 수 있지만(익명의 경우), @subject는 subject 클래스의 실제 인스턴스임이 보장됩니다.

class FooPolicy < BasePolicy
  condition(:is_public) do
    # @subject guaranteed to be an instance of Foo
    @subject.public?
  end

  # instance methods can be called from the condition as well
  condition(:thing) { check_thing }

  def check_thing
    # ...
  end
end

condition을 정의하면 해당 condition이 충족되는지 확인하는 predicate 메서드가 정책에 정의됩니다. 위의 예에서 FooPolicy의 인스턴스는 #is_public?#thing?에도 응답합니다.

Conditions는 scope에 따라 캐시됩니다. Scope와 순서는 뒤에서 다룹니다.

Rules#

rule은 conditions와 다른 rules의 논리적 조합으로, 특정 능력을 활성화하거나 차단하도록 구성됩니다. rule 구성은 정적입니다. rule의 로직은 데이터베이스에 접근하거나 @user 또는 @subject에 대해 알 수 없습니다. 이를 통해 condition 수준에서만 캐싱할 수 있습니다. Rules는 DSL 구성의 블록을 받아 #enable 또는 #prevent에 응답하는 객체를 반환하는 rule 메서드를 통해 지정됩니다.

class FooPolicy < BasePolicy
  # ...

  rule { is_public }.enable :read
  rule { ~thing }.prevent :read

  # equivalently,
  rule { is_public }.policy do
    enable :read
  end

  rule { ~thing }.policy do
    prevent :read
  end
end

rule DSL 내에서 다음을 사용할 수 있습니다.

  • 일반 단어는 이름으로 condition을 참조합니다. 해당 condition이 truthy일 때 적용되는 rule입니다.

  • ~는 부정을 나타내며, negate로도 사용할 수 있습니다.

  • &|는 논리 조합으로, all?(...)any?(...)로도 사용할 수 있습니다.

  • can?(:other_ability):other_ability에 적용되는 rules에 위임합니다. 이는 동적으로 확인할 수 있는 인스턴스 메서드 can?과 다릅니다. 이는 다른 ability에 대한 위임만 구성합니다.

~, &, | 연산자는 DeclarativePolicy::Rule::Base에서 오버라이드된 메서드입니다.

rule DSL 내에서 &&||와 같은 불리언 연산자를 사용하지 마세요. rule 블록 내의 conditions는 불리언이 아닌 객체이기 때문입니다. 삼항 연산자(condition ? ... : ...)와 if 블록에도 동일하게 적용됩니다. 이러한 연산자는 오버라이드할 수 없으므로 custom cop을 통해 금지됩니다.

점수, 순서, 성능#

rules가 판정으로 평가되는 방식을 보려면 Rails 콘솔을 열고 policy.debug(:some_ability)를 실행하세요. 이는 rules가 평가되는 순서대로 출력합니다.

예를 들어, IssuePolicy를 디버그하려면 다음 명령을 실행할 수 있습니다.

user = User.find_by(username: 'sidneyjones')
issue = Issue.first
policy = IssuePolicy.new(user, issue)
policy.debug(:read_issue)

디버그 출력 예시는 다음과 같습니다.

- [0] prevent when all?(confidential, ~can_read_confidential) ((@sidneyjones : Issue/1))
- [0] prevent when archived ((@sidneyjones : Project/4))
- [0] prevent when issues_disabled ((@sidneyjones : Project/4))
- [0] prevent when all?(anonymous, ~public_project) ((@sidneyjones : Project/4))
+ [32] enable when can?(:reporter_access) ((@sidneyjones : Project/4))

각 줄은 평가된 rule을 나타냅니다. 주목할 몇 가지 사항이 있습니다.

  • - 기호는 rule 블록이 false로 평가되었음을 나타냅니다. + 기호는 rule 블록이 true로 평가되었음을 나타냅니다.

  • 괄호 안의 숫자는 점수를 나타냅니다.

  • 줄의 마지막 부분(예: @sidneyjones : Issue/1)은 해당 rule의 사용자명과 subject를 보여줍니다.

여기서 처음 네 개의 rules가 해당 사용자와 subject에 대해 false로 평가되었음을 알 수 있습니다. 예를 들어, 마지막 줄에서 사용자 sidneyjonesProject/4에서 Reporter 권한을 가지고 있었기 때문에 rule이 활성화되었음을 알 수 있습니다.

정책이 특정 ability가 허용되는지 묻는 경우(policy.allowed?(:some_ability)), 정책의 모든 conditions를 계산할 필요는 없습니다. 먼저, 해당 특정 ability와 관련된 rules만 선택됩니다. 그런 다음 실행 모델은 단락 평가를 활용하여 계산 비용에 대한 휴리스틱을 기반으로 rules를 정렬하려고 합니다. 정렬은 동적이고 캐시 인식이므로, 이전에 계산된 conditions가 다른 conditions를 계산하기 전에 먼저 고려됩니다.

점수는 개발자가 conditionscore: 파라미터를 통해 선택하여 다른 rules에 비해 이 rule 평가 비용이 얼마나 드는지 나타냅니다.

Scope#

때로는 condition이 @user의 데이터만 사용하거나 @subject의 데이터만 사용합니다. 이 경우 캐싱 scope를 변경하여 불필요하게 conditions를 재계산하지 않으려고 합니다. 예를 들어 다음과 같이 주어진 경우:

class FooPolicy < BasePolicy
  condition(:expensive_condition) { @subject.expensive_query? }

  rule { expensive_condition }.enable :some_ability
end

단순하게 Ability.allowed?(user1, :some_ability, foo)Ability.allowed?(user2, :some_ability, foo)를 호출하면 서로 다른 사용자에 대한 것이므로 condition을 두 번 계산해야 합니다. 하지만 scope: :subject 옵션을 사용하면:

  condition(:expensive_condition, scope: :subject) { @subject.expensive_query? }

condition의 결과는 subject에 기반해서만 전역적으로 캐시됩니다. 따라서 서로 다른 사용자에 대해 반복적으로 계산되지 않습니다. 마찬가지로, scope: :user는 사용자에 기반해서만 캐시됩니다.

condition이 보편적으로 true인 경우 :global scope를 사용할 수 있습니다.

  condition(:earth_exists, scope: :global) { Planet::Earth.exists? }

scope에 대한 자세한 정보는 gem의 캐싱 문서를 참조하세요.

condition이 실제로 user와 subject 모두의 데이터를 사용하는 경우(단순 익명 확인 포함!) :scope 옵션을 사용하면 결과가 너무 전역적인 scope에서 캐시되어 캐시 버그가 발생합니다.

때로는 하나의 subject에 대해 많은 사용자의 권한을 확인하거나, 하나의 사용자에 대해 많은 subjects의 권한을 확인합니다. 이 경우 선호 scope를 설정하려고 합니다. 즉, 시스템에 반복되는 파라미터에서 캐시할 수 있는 rules를 선호한다고 알려줍니다. 예를 들어, Ability.users_that_can_read_project에서:

def users_that_can_read_project(users, project)
  DeclarativePolicy.subject_scope do
    users.select { |u| allowed?(u, :read_project, project) }
  end
end

이는 예를 들어 user.admin?을 확인하는 것보다 project.public?을 확인하는 것을 선호합니다.

위임#

위임은 다른 subject에 대한 다른 정책의 rules를 포함하는 것입니다. 예를 들어:

class FooPolicy < BasePolicy
  delegate { @subject.project }
end

이는 ProjectPolicy의 모든 rules를 포함합니다. 위임된 conditions는 올바른 위임된 subject로 평가되며, 정책의 일반 rules와 함께 정렬됩니다. 특정 ability와 관련된 rules만 실제로 고려됩니다.

오버라이드#

정책은 위임이 의미 없는 경우 위임된 abilities를 선택적으로 제외할 수 있습니다.

위임된 정책은 위임하는 정책에 적합하지 않은 방식으로 일부 abilities를 정의할 수 있습니다. 예를 들어, 일부 abilities는 추론할 수 있고 일부는 추론할 수 없는 부모-자식 관계를 고려하면:

class ParentPolicy < BasePolicy
  condition(:speaks_spanish) { @subject.spoken_languages.include?(:es) }
  condition(:has_license) { @subject.driving_license.present? }
  condition(:is_employed) { @subject.is_employed? }

  rule { speaks_spanish }.enable :read_spanish
  rule { has_license }.enable :drive_car
  rule { is_employed }.enable :earn_money
  rule { ~is_employed }.prevent :earn_money
end

자식 정책이 부모 정책에 위임하는 경우 일부 값이 잘못될 수 있습니다. 자식이 부모의 언어를 말할 수 있다고 올바르게 추론할 수 있지만, 부모가 운전하거나 돈을 벌 수 있다는 이유만으로 자식이 운전하거나 돈을 벌 수 있다고 추론할 수는 없습니다.

이러한 경우 일부를 처리할 수 있습니다. 예를 들어, 자식 정책에서 운전을 금지할 수 있습니다.

class ChildPolicy < BasePolicy
  delegate { @subject.parent }

  rule { default }.prevent :drive_car
end

돈 버는 것은 더 복잡합니다. 부모 정책의 prevent 호출 때문에, 부모가 고용되어 있지 않으면 부모도 자식도 돈을 벌 수 없습니다. 자식 정책에서 명시적으로 :earn_money를 활성화해도 작동하지 않습니다.

ParentPolicy에서 prevent 호출을 제거해도 도움이 되지 않는데, :earn_money를 활성화하는 rules가 부모와 자식 정책 간에 다르기 때문입니다.

위임을 사용하면 자식 리소스는 부모 정책이 명시적으로 차단하지 않은 권한만 활성화할 수 있습니다. 이 경우 돈을 버는 부모를 둔 자식만 돈을 벌 수 있습니다. 하지만 돈을 벌지 못하는 부모도 자식에게 용돈을 주고 싶을 수 있습니다.

해결책은 자식 정책에서 :earn_money ability를 오버라이드하는 것입니다.

class ChildPolicy < BasePolicy
  delegate { @subject.parent }

  overrides :earn_money

  condition(:has_allowance) { @subject.has_allowance? }

  rule { has_allowance }.enable :earn_money
end

이 정의에 따라 ChildPolicy:earn_money를 충족하기 위해 ParentPolicy를 참조하지 않지만, 다른 abilities에는 여전히 사용합니다. 그런 다음 자식 정책은 Parent가 아닌 Child에 적합한 방식으로 :earn_money를 정의할 수 있습니다.

오버라이드 사용의 대안#

정책 위임 오버라이드는 위임이 복잡한 것과 같은 이유로 복잡합니다. 논리적 추론과 의미론에 대한 명확한 이해가 필요합니다. override를 잘못 사용하면 코드가 중복되고, 잠재적으로 차단해야 할 것을 허용하는 보안 버그가 발생할 수 있습니다. 이런 이유로 다른 접근 방식이 불가능한 경우에만 사용해야 합니다.

다른 접근 방식으로는 예를 들어 다른 ability 이름을 사용하는 것이 있습니다. 수입을 버는 것과 용돈을 받는 것은 의미론적으로 구별되며, 다르게 명명할 수 있습니다(이 경우 earn_salaryearn_allowance가 될 수 있습니다). 이는 호출 사이트가 얼마나 다형적인지에 따라 달라질 수 있습니다. 항상 Parent 또는 Child로 정책을 확인한다는 것을 알고 있다면 적절한 ability 이름을 선택할 수 있습니다. 호출 사이트가 다형적이라면 그렇게 할 수 없습니다.

정책 클래스 지정#

주어진 subject에 사용되는 정책을 오버라이드할 수도 있습니다.

class Foo

  def self.declarative_policy_class
    'SomeOtherPolicy'
  end
end

이는 일반적으로 계산된 FooPolicy 클래스 대신 SomeOtherPolicy 클래스에서 권한을 사용하고 확인합니다.