law 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d061854fe0fefb951cb900d1a806ef2129b4e061454d174320b3a0ebc787d88d
4
- data.tar.gz: e0089a68100f76dafe3fd8530cef2a00a40b9f15a00953063e64987d5322aae3
3
+ metadata.gz: 3492ad53cbc6aec4593a8992e9e3725350306057ddfcde3e557e0f06e6a0b1d0
4
+ data.tar.gz: 7dfdf907d2bb6a0d87d31ce4b29b4f303435d969f56aa0729622e67193682d05
5
5
  SHA512:
6
- metadata.gz: 8fa2fab862e7400f5954dc70e6c47501e955b08b650b82b445df6ae6f587544e50e236bd7f63be0eb85dfee4ef785e21188e866c69834fa85a1b9b1adb0444d5
7
- data.tar.gz: 87d1e43d5d187eda7d788fa4cbef041280324e40afdb8c02cdc851e83384a6c898c19f57a03bfe36044c98d6adc840f59e84f5b1b2c3b3115cddcc14ff846439
6
+ metadata.gz: b646db6d863c688f091b2b78999a34be85c78895b4d79259e9f01d17fba9aaf4424e24b999834a6d61d99b38d3c7a4d9867d8de64b2eb4b2ad7cb82c6c86cfb9
7
+ data.tar.gz: 0472a5af9b1ed53608bc96badeaafbe090897504c5d309bdaf83ee2341bab2f566998c054bd4e0651c002fd5db7c77309b0f9edbebfa8cb634949e9f297da9e2
data/README.md CHANGED
@@ -1,8 +1,17 @@
1
1
  # Law
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/law`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ Enforce the laws of your Rails application with highly extensible access policies.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ [![Gem Version](https://badge.fury.io/rb/law.svg)](https://badge.fury.io/rb/law)
6
+ [![Build Status](https://semaphoreci.com/api/v1/freshly/law/branches/develop/badge.svg)](https://semaphoreci.com/freshly/law)
7
+ [![Maintainability](https://api.codeclimate.com/v1/badges/c5667b201773ea79ff5e/maintainability)](https://codeclimate.com/github/Freshly/law/maintainability)
8
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/c5667b201773ea79ff5e/test_coverage)](https://codeclimate.com/github/Freshly/law/test_coverage)
9
+
10
+ * [Installation](#installation)
11
+ * [Usage](#usage)
12
+ * [Development](#development)
13
+ * [Contributing](#contributing)
14
+ * [License](#license)
6
15
 
7
16
  ## Installation
8
17
 
@@ -32,7 +41,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
41
 
33
42
  ## Contributing
34
43
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/law.
44
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Freshly/law.
36
45
 
37
46
  ## License
38
47
 
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Judgement** determines if an **Actor** (represented by their **Roles**) are in violation of the given **Statute**.
4
+ module Law
5
+ class Judgement < Spicerack::RootObject
6
+ class << self
7
+ def judge!(petition)
8
+ new(petition).judge!
9
+ end
10
+
11
+ def judge(petition)
12
+ new(petition).judge
13
+ end
14
+ end
15
+
16
+ attr_reader :petition, :violations, :applied_regulations
17
+
18
+ delegate :statute, :applicable_regulations, to: :petition
19
+
20
+ def initialize(petition)
21
+ @petition = petition
22
+ @violations = []
23
+ @applied_regulations = []
24
+ end
25
+
26
+ def adjudicated?
27
+ applied_regulations.present?
28
+ end
29
+
30
+ def authorized?
31
+ violations.blank?
32
+ end
33
+
34
+ def judge
35
+ judge!
36
+ rescue NotAuthorizedError => exception
37
+ error :not_authorized, exception: exception
38
+ false
39
+ end
40
+
41
+ def judge!
42
+ if statute.unregulated?
43
+ @applied_regulations = [ nil ]
44
+ return true
45
+ end
46
+
47
+ raise InjunctionError if applicable_regulations.blank?
48
+ raise AlreadyJudgedError if adjudicated?
49
+
50
+ @applied_regulations = applicable_regulations.map { |regulation| regulation.new(petition: petition) }
51
+ @violations = applied_regulations.reject(&:valid?)
52
+
53
+ raise NotAuthorizedError unless authorized?
54
+
55
+ true
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "laws/actions"
4
+ require_relative "laws/statutes"
5
+ require_relative "laws/petitions"
6
+ require_relative "laws/judgements"
7
+
8
+ # A **Law** defines which **Statutes** are enforced against specifics **Actions**.
9
+ module Law
10
+ class LawBase < Spicerack::InputObject
11
+ include Conjunction::Junction
12
+ suffixed_with "Law"
13
+
14
+ include Law::Laws::Judgements
15
+ include Law::Laws::Actions
16
+ include Law::Laws::Statutes
17
+ include Law::Laws::Petitions
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Law** enforces various **Statutes** to restrict **Actions**.
4
+ module Law
5
+ module Laws
6
+ module Actions
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :actions, instance_writer: false, default: {}
11
+
12
+ delegate :statute_for_action?, to: :class
13
+ end
14
+
15
+ class_methods do
16
+ def inherited(base)
17
+ base.actions = actions.dup
18
+ super
19
+ end
20
+
21
+ def statute_for_action?(action)
22
+ actions[action].present?
23
+ end
24
+
25
+ private
26
+
27
+ def define_action(*input_actions, enforces: nil)
28
+ raise ArgumentError, "invalid statute: #{enforces}" unless enforceable?(enforces)
29
+
30
+ input_actions.each do |action|
31
+ actions[action] = enforces
32
+ enforces.try(:enforced_by, self, action)
33
+ define_judgement_predicates_for_action(action)
34
+ end
35
+ end
36
+
37
+ def enforceable?(enforces)
38
+ enforces.nil? || enforces.respond_to?(:enforced_by)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Law** is used to make a **Judgement** about whether an **Action** is authorized.
4
+ module Law
5
+ module Laws
6
+ module Judgements
7
+ extend ActiveSupport::Concern
8
+
9
+ class_methods do
10
+ private
11
+
12
+ def define_judgement_predicates_for_action(action)
13
+ method_name = "#{action}?".to_sym
14
+ define_method(method_name) { authorized?(action) }
15
+ define_singleton_method(method_name) { |**options| new(**options).public_send(method_name) }
16
+ end
17
+ end
18
+
19
+ def judgement(action)
20
+ Law::Judgement.new(petition_for_action(action))
21
+ end
22
+
23
+ def authorize(action)
24
+ judgement(action).tap(&:judge)
25
+ end
26
+
27
+ def authorize!(action)
28
+ judgement(action).tap(&:judge!)
29
+ end
30
+
31
+ def authorized?(action)
32
+ judgement(action).judge
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Law** creates a **Petition** for the **Statute** enforced against an **Action**; all other data must be specified.
4
+ module Law
5
+ module Laws
6
+ module Petitions
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ option :source
11
+ option :permissions, default: []
12
+ option :target
13
+ option :params, default: {}
14
+ end
15
+
16
+ def petition_for_action(action)
17
+ Law::Petition.new(
18
+ statute: actions[action] || _default_statute,
19
+ source: source,
20
+ permissions: permissions,
21
+ target: target,
22
+ params: params,
23
+ )
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Law** can have a default **Statute** to apply against **Actions** which do not otherwise specify.
4
+ module Law
5
+ module Laws
6
+ module Statutes
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ delegate :_default_statute, :default_statute?, to: :class
11
+ end
12
+
13
+ class_methods do
14
+ attr_reader :_default_statute
15
+
16
+ def inherited(base)
17
+ base.default_statute(_default_statute) if default_statute?
18
+ super
19
+ end
20
+
21
+ def default_statute?
22
+ _default_statute.present?
23
+ end
24
+
25
+ protected
26
+
27
+ def default_statute(statute)
28
+ raise ArgumentError, "invalid statute: #{enforces}" unless statute.respond_to?(:enforced_by)
29
+
30
+ @_default_statute = statute
31
+ statute.enforced_by(self, :__default__)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ # To make something permissible by the enforcement of **Laws**.
4
+ module Law
5
+ module Legalize
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ attr_reader :judgement
10
+ helper_method :law if respond_to?(:helper_method)
11
+ end
12
+
13
+ def authorized?
14
+ judgement.try(:authorized?) || false
15
+ end
16
+
17
+ def adjudicated?
18
+ judgement.try(:adjudicated?) || false
19
+ end
20
+
21
+ def violations
22
+ judgement.try(:violations) || []
23
+ end
24
+
25
+ def law(object = nil, petitioner = nil, permissions: nil, parameters: nil, law_class: nil)
26
+ object ||= try(:controller_name)&.singularize&.camelize&.safe_constantize
27
+ petitioner ||= try(:current_user)
28
+ permissions ||= petitioner.try(:permissions)
29
+ law_class ||= object.try(:conjugate, Law::LawBase)
30
+
31
+ raise ArgumentError, "a Law is required" unless law_class.is_a?(Class)
32
+
33
+ law_class.new(permissions: permissions, source: petitioner, target: object, params: parameters)
34
+ end
35
+
36
+ def authorize!(**options)
37
+ authorize(**options) or raise Law::NotAuthorizedError
38
+ end
39
+
40
+ def authorize(action = nil, object: nil, petitioner: nil, permissions: nil, parameters: nil, law_class: nil)
41
+ action ||= try(:action_name)
42
+ parameters ||= try(:params)
43
+
44
+ raise ArgumentError, "an action is required" if action.nil?
45
+
46
+ options = { permissions: permissions, parameters: parameters, law_class: law_class }
47
+ @judgement = law(object, petitioner, **options).authorize(action)
48
+ authorized?
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Law
4
+ class PermissionList < Collectible::CollectionBase
5
+ ensures_item_class Symbol
6
+ end
7
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Petition** is used to make a **Judgement** determining if an action would violate a given **Statute**.
4
+ module Law
5
+ class Petition < Spicerack::InputObject
6
+ argument :statute, allow_nil: false
7
+ option :source
8
+ option :permissions, default: []
9
+ option :target
10
+ option :params, default: {}
11
+
12
+ def permission_list
13
+ Law::PermissionList.new(Array.wrap(permissions).flatten.compact)
14
+ end
15
+ memoize :permission_list
16
+
17
+ def applicable_regulations
18
+ statute.regulations.select { |regulation| permission_list.include? regulation.key }
19
+ end
20
+ memoize :applicable_regulations
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "regulations/statutes"
4
+ require_relative "regulations/core"
5
+
6
+ # A **Regulation** is the "lock" which has a matching **Permission** "key".
7
+ module Law
8
+ class RegulationBase < Spicerack::InputModel
9
+ include Regulations::Statutes
10
+ include Regulations::Core
11
+
12
+ def self.key
13
+ name.chomp("Regulation").underscore.to_sym
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Regulation** accepts a **Petition** and applies **Validations** to ensure the request is acceptable.
4
+ module Law
5
+ module Regulations
6
+ module Core
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ argument :petition
11
+ end
12
+
13
+ private
14
+
15
+ def respond_to_missing?(method_name, include_private = false)
16
+ petition_delegate, delegated_method_name, is_setter = petition_delegator_for_method_name(method_name)
17
+ delegation_type(petition_delegate, delegated_method_name, is_setter) != :none || super
18
+ end
19
+
20
+ def method_missing(method_name, *arguments)
21
+ petition_delegate, delegated_method_name, is_setter = petition_delegator_for_method_name(method_name)
22
+
23
+ case delegation_type(petition_delegate, delegated_method_name, is_setter)
24
+ when :delegate
25
+ petition_delegate
26
+ when :method
27
+ petition_delegate.public_send(delegated_method_name, *arguments)
28
+ when :attribute_set
29
+ petition_delegate[delegated_method_name] = arguments.first
30
+ when :attribute_sym
31
+ petition_delegate[delegated_method_name]
32
+ when :attribute_str
33
+ petition_delegate[delegated_method_name.to_s]
34
+ else
35
+ super
36
+ end
37
+ end
38
+
39
+ # rubocop:disable Metrics/PerceivedComplexity
40
+ # rubocop:disable Metrics/CyclomaticComplexity
41
+ def delegation_type(petition_delegate, delegated_method_name, is_setter)
42
+ unless petition_delegate.nil?
43
+ return :delegate if delegated_method_name.blank? && !is_setter
44
+
45
+ method_name_to_test = is_setter ? "#{delegated_method_name}=".to_sym : delegated_method_name
46
+ return :method if petition_delegate.respond_to?(method_name_to_test)
47
+
48
+ if petition_delegate.respond_to?(:key?)
49
+ return :attribute_set if is_setter
50
+ return :attribute_sym if petition_delegate.key?(delegated_method_name)
51
+ return :attribute_str if petition_delegate.key?(delegated_method_name.to_s)
52
+ end
53
+ end
54
+
55
+ :none
56
+ end
57
+ # rubocop:enable Metrics/CyclomaticComplexity
58
+ # rubocop:enable Metrics/PerceivedComplexity
59
+
60
+ def petition_delegator_for_method_name(method_name)
61
+ parts = method_name.to_s.split("_")
62
+ petition_delegate = parts.shift.to_sym
63
+
64
+ return if petition_delegate.nil? || !petition.respond_to?(petition_delegate)
65
+
66
+ method_name = parts.join("_")
67
+ is_setter = method_name.delete_suffix!("=").present?
68
+
69
+ [ petition.public_send(petition_delegate), method_name.to_sym, is_setter ]
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Regulation** is imposed by various **Statutes** to restrict **Actors** from perform actions.
4
+ module Law
5
+ module Regulations
6
+ module Statutes
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :statutes, instance_writer: false, default: []
11
+ end
12
+
13
+ class_methods do
14
+ def inherited(base)
15
+ base.statutes = []
16
+ super
17
+ end
18
+
19
+ def imposed_by(statute)
20
+ statutes << statute
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests references of statutes as defaults in laws.
4
+ #
5
+ # class ExampleStatute < ApplicationStatute
6
+ # end
7
+ #
8
+ # class ExampleLaw < ApplicationStatute
9
+ # default_statute ExampleStatute
10
+ # end
11
+ #
12
+ # RSpec.describe ExampleStatute, type: :statute do
13
+ # it { is_expected.to be_default_for ExampleLaw }
14
+ # end
15
+
16
+ RSpec::Matchers.define :be_default_for do |*laws|
17
+ match do
18
+ laws.each { |law| expect(test_subject.laws).to include(law => a_collection_including(:__default__)) }
19
+ end
20
+ description { "be default for #{Array.wrap(laws).join(", ")}" }
21
+ failure_message do
22
+ "expected #{test_subject} to be default for #{Array.wrap(laws).join(", ")}; #{test_subject.laws}"
23
+ end
24
+ failure_message_when_negated do
25
+ "expected #{test_subject} not to be default for #{Array.wrap(laws).join(", ")}; #{test_subject.laws}"
26
+ end
27
+
28
+ def test_subject
29
+ subject.is_a?(Class) ? subject : subject.class
30
+ end
31
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests references of statutes in laws.
4
+ #
5
+ # class ExampleStatute < ApplicationStatute
6
+ # end
7
+ #
8
+ # class ExampleLaw < ApplicationStatute
9
+ # define_action :new, enforces: ExampleRegulation
10
+ # define_action :create, enforces: ExampleRegulation
11
+ # end
12
+ #
13
+ # RSpec.describe ExampleStatute, type: :statute do
14
+ # it { is_expected.to be_enforced_by ExampleLaw, :new, :create }
15
+ # end
16
+
17
+ RSpec::Matchers.define :be_enforced_by do |statute, *methods|
18
+ match { expect(test_subject.laws).to include(statute => a_collection_including(*methods.flatten)) }
19
+ description { "be enforced by #{statute} on #{methods.flatten.join(", ")}" }
20
+ failure_message do
21
+ "expected #{test_subject} to be enforced by #{statute} on #{methods.flatten.join(", ")}; #{test_subject.laws}"
22
+ end
23
+ failure_message_when_negated do
24
+ "expected #{test_subject} not to be enforced by #{statute} on #{methods.flatten.join(", ")}; #{test_subject.laws}"
25
+ end
26
+
27
+ def test_subject
28
+ subject.is_a?(Class) ? subject : subject.class
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests references of statutes to regulations.
4
+ #
5
+ # class ExampleRegulation < ApplicationRegulation
6
+ # end
7
+ #
8
+ # class ExampleStatute < ApplicationStatute
9
+ # impose ExampleRegulation
10
+ # end
11
+ #
12
+ # RSpec.describe ExampleRegulation, type: :regulation do
13
+ # it { is_expected.to be_imposed_by ExampleStatute }
14
+ # end
15
+
16
+ RSpec::Matchers.define :be_imposed_by do |*statutes|
17
+ match { expect(test_subject.statutes).to include *Array.wrap(statutes).flatten }
18
+ description { "be imposed by #{Array.wrap(statutes).flatten}" }
19
+ failure_message do
20
+ "expected #{test_subject} to be imposed by #{Array.wrap(statutes).flatten}; #{test_subject.statutes}"
21
+ end
22
+ failure_message_when_negated do
23
+ "expected #{test_subject} not to be imposed by #{Array.wrap(statutes).flatten}; #{test_subject.statutes}"
24
+ end
25
+
26
+ def test_subject
27
+ subject.is_a?(Class) ? subject : subject.class
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests references of statutes in laws.
4
+ #
5
+ # class ExampleStatute < ApplicationStatute
6
+ # end
7
+ #
8
+ # class ExampleLaw < ApplicationStatute
9
+ # define_action :new, enforces: ExampleRegulation
10
+ # define_action :create, enforces: ExampleRegulation
11
+ # define_action :edit
12
+ # end
13
+ #
14
+ # RSpec.describe ExampleLaw, type: :law do
15
+ # it { is_expected.to define_action :new, with_statute: ExampleRegulation }
16
+ # it { is_expected.to define_action :edit, with_statute: :default }
17
+ # end
18
+
19
+ RSpec::Matchers.define :define_action do |action, with_statute:|
20
+ match do
21
+ for_default = with_statute == :default
22
+ expect(test_subject.actions[action]).to eq with_statute unless for_default
23
+ expect(test_subject.statute_for_action?(action)).to eq !for_default
24
+ end
25
+ description { "define action #{action} with statute #{with_statute}" }
26
+ failure_message do
27
+ "expected #{test_subject} to define action #{action}, statute: #{with_statute}; #{test_subject.actions[action]}"
28
+ end
29
+ failure_message_when_negated do
30
+ "expected #{test_subject} not to define action #{action}, statute: #{with_statute}; #{test_subject.actions[action]}"
31
+ end
32
+
33
+ def test_subject
34
+ subject.is_a?(Class) ? subject : subject.class
35
+ end
36
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests references of statutes as defaults in laws.
4
+ #
5
+ # class ExampleStatute < ApplicationStatute
6
+ # end
7
+ #
8
+ # class ExampleLaw < ApplicationStatute
9
+ # default_statute ExampleStatute
10
+ # end
11
+ #
12
+ # RSpec.describe ExampleLaw, type: :law do
13
+ # it { is_expected.to have_default_statute ExampleStatute }
14
+ # end
15
+
16
+ RSpec::Matchers.define :have_default_statute do |statute|
17
+ match { expect(test_subject._default_statute).to eq statute }
18
+ description { "have default statute #{statute}" }
19
+ failure_message { "expected #{test_subject} to have default statute #{statute}; #{test_subject._default_statute}" }
20
+ failure_message_when_negated do
21
+ "expected #{test_subject} not to have default statute #{statute}; #{test_subject._default_statute}"
22
+ end
23
+
24
+ def test_subject
25
+ subject.is_a?(Class) ? subject : subject.class
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # RSpec matcher that tests imposing of regulations by statutes.
4
+ #
5
+ # class ExampleRegulation < ApplicationRegulation
6
+ # end
7
+ #
8
+ # class ExampleStatute < ApplicationStatute
9
+ # impose ExampleRegulation
10
+ # end
11
+ #
12
+ # RSpec.describe ExampleStatute, type: :statute do
13
+ # it { is_expected.to impose_regulations ExampleRegulation }
14
+ # end
15
+
16
+ RSpec::Matchers.define :impose_regulations do |*regulations|
17
+ match { expect(test_subject.regulations).to include *Array.wrap(regulations).flatten }
18
+ description { "impose regulations #{Array.wrap(regulations).flatten}" }
19
+ failure_message do
20
+ "expected #{test_subject} to impose regulations #{Array.wrap(regulations).flatten}; #{test_subject.regulations}"
21
+ end
22
+ failure_message_when_negated do
23
+ "expected #{test_subject} not to impose regulations #{Array.wrap(regulations).flatten}; #{test_subject.regulations}"
24
+ end
25
+
26
+ def test_subject
27
+ subject.is_a?(Class) ? subject : subject.class
28
+ end
29
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "custom_matchers/be_default_for"
4
+ require_relative "custom_matchers/be_enforced_by"
5
+ require_relative "custom_matchers/be_imposed_by"
6
+ require_relative "custom_matchers/define_action"
7
+ require_relative "custom_matchers/have_default_statute"
8
+ require_relative "custom_matchers/impose_regulations"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ if defined?(Shoulda::Matchers::ActiveModel)
4
+ RSpec.configure do |config|
5
+ config.include(Shoulda::Matchers::ActiveModel, type: :regulation)
6
+ end
7
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "rspec/custom_matchers"
4
+ require_relative "rspec/shoulda_matcher_helper"
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "statutes/regulations"
4
+ require_relative "statutes/laws"
5
+
6
+ # A **Statute** restricts actions to **Actors** by enforcing a collection of **Permissions**.
7
+ module Law
8
+ class StatuteBase < Spicerack::RootObject
9
+ include Law::Statutes::Regulations
10
+ include Law::Statutes::Laws
11
+ end
12
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Statute** is enforced by **Laws**.
4
+ module Law
5
+ module Statutes
6
+ module Laws
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :laws, instance_writer: false, default: Hash.new { |hash, key| hash[key] = [] }
11
+ end
12
+
13
+ class_methods do
14
+ def inherited(base)
15
+ base.laws = Hash.new { |hash, key| hash[key] = [] }
16
+ super
17
+ end
18
+
19
+ def enforced_by(law, action)
20
+ laws[law] << action
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A **Statute** is a collection of imposed **Regulations**.
4
+ module Law
5
+ module Statutes
6
+ module Regulations
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ class_attribute :regulations, instance_writer: false, default: []
11
+ delegate :unregulated?, to: :class
12
+ end
13
+
14
+ class_methods do
15
+ def unregulated?
16
+ regulations.empty?
17
+ end
18
+
19
+ def inherited(base)
20
+ base.regulations = regulations.dup
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ def impose(*imposed_regulations)
27
+ imposed_regulations = imposed_regulations.flatten.compact
28
+
29
+ ensure_valid_regulations(imposed_regulations)
30
+
31
+ imposed_regulations.each do |regulation|
32
+ regulations << regulation
33
+ track_regulation(regulation)
34
+ end
35
+ end
36
+
37
+ def track_regulation(regulation)
38
+ regulation.imposed_by(self)
39
+ end
40
+
41
+ def ensure_valid_regulations(imposed_regulations)
42
+ raise ArgumentError, "a regulation is required" if imposed_regulations.empty?
43
+
44
+ invalid_regulations = imposed_regulations.reject { |permission| permission.respond_to?(:imposed_by) }
45
+ raise ArgumentError, "invalid regulations: #{invalid_regulations.join(", ")}" if invalid_regulations.present?
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
data/lib/law/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Law
2
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
3
5
  end
data/lib/law.rb CHANGED
@@ -1,6 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support"
4
+ require "active_support/core_ext/enumerable"
5
+
6
+ require "spicery"
7
+
1
8
  require "law/version"
2
9
 
10
+ require "law/regulation_base"
11
+
12
+ require "law/permission_list"
13
+
14
+ require "law/statute_base"
15
+
16
+ require "law/petition"
17
+ require "law/judgement"
18
+
19
+ require "law/law_base"
20
+
21
+ require "law/legalize"
22
+
3
23
  module Law
4
24
  class Error < StandardError; end
5
- # Your code goes here...
25
+
26
+ class AlreadyJudgedError < Error; end
27
+
28
+ class NotAuthorizedError < Error; end
29
+ class InjunctionError < NotAuthorizedError; end
6
30
  end
metadata CHANGED
@@ -1,29 +1,77 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: law
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Garside
8
8
  autorequire:
9
- bindir: exe
9
+ bindir: bin
10
10
  cert_chain: []
11
- date: 2019-05-06 00:00:00.000000000 Z
11
+ date: 2020-02-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 5.2.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: spicery
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.22.3.1
34
+ - - "<"
35
+ - !ruby/object:Gem::Version
36
+ version: '1.0'
37
+ type: :runtime
38
+ prerelease: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.22.3.1
44
+ - - "<"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
13
47
  - !ruby/object:Gem::Dependency
14
48
  name: bundler
15
49
  requirement: !ruby/object:Gem::Requirement
16
50
  requirements:
17
51
  - - "~>"
18
52
  - !ruby/object:Gem::Version
19
- version: '2.0'
53
+ version: 2.0.1
20
54
  type: :development
21
55
  prerelease: false
22
56
  version_requirements: !ruby/object:Gem::Requirement
23
57
  requirements:
24
58
  - - "~>"
25
59
  - !ruby/object:Gem::Version
26
- version: '2.0'
60
+ version: 2.0.1
61
+ - !ruby/object:Gem::Dependency
62
+ name: pry-byebug
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 3.7.0
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 3.7.0
27
75
  - !ruby/object:Gem::Dependency
28
76
  name: rake
29
77
  requirement: !ruby/object:Gem::Requirement
@@ -39,42 +87,130 @@ dependencies:
39
87
  - !ruby/object:Gem::Version
40
88
  version: '10.0'
41
89
  - !ruby/object:Gem::Dependency
42
- name: rspec
90
+ name: simplecov
43
91
  requirement: !ruby/object:Gem::Requirement
44
92
  requirements:
45
93
  - - "~>"
46
94
  - !ruby/object:Gem::Version
47
- version: '3.0'
95
+ version: '0.16'
48
96
  type: :development
49
97
  prerelease: false
50
98
  version_requirements: !ruby/object:Gem::Requirement
51
99
  requirements:
52
100
  - - "~>"
53
101
  - !ruby/object:Gem::Version
54
- version: '3.0'
55
- description: Illegal operations take on a whole new meaning
102
+ version: '0.16'
103
+ - !ruby/object:Gem::Dependency
104
+ name: timecop
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 0.9.1
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 0.9.1
117
+ - !ruby/object:Gem::Dependency
118
+ name: shoulda-matchers
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '='
122
+ - !ruby/object:Gem::Version
123
+ version: 4.0.1
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '='
129
+ - !ruby/object:Gem::Version
130
+ version: 4.0.1
131
+ - !ruby/object:Gem::Dependency
132
+ name: rspice
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: 0.22.3.1
138
+ - - "<"
139
+ - !ruby/object:Gem::Version
140
+ version: '1.0'
141
+ type: :development
142
+ prerelease: false
143
+ version_requirements: !ruby/object:Gem::Requirement
144
+ requirements:
145
+ - - ">="
146
+ - !ruby/object:Gem::Version
147
+ version: 0.22.3.1
148
+ - - "<"
149
+ - !ruby/object:Gem::Version
150
+ version: '1.0'
151
+ - !ruby/object:Gem::Dependency
152
+ name: spicerack-styleguide
153
+ requirement: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: 0.22.3.1
158
+ - - "<"
159
+ - !ruby/object:Gem::Version
160
+ version: '1.0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: 0.22.3.1
168
+ - - "<"
169
+ - !ruby/object:Gem::Version
170
+ version: '1.0'
171
+ description: Enforce the laws of your Rails application with highly extensible access
172
+ policies
56
173
  email:
57
174
  - garside@gmail.com
58
175
  executables: []
59
176
  extensions: []
60
177
  extra_rdoc_files: []
61
178
  files:
62
- - ".gitignore"
63
- - ".rspec"
64
- - ".travis.yml"
65
- - Gemfile
66
179
  - LICENSE.txt
67
180
  - README.md
68
- - Rakefile
69
- - bin/console
70
- - bin/setup
71
- - law.gemspec
72
181
  - lib/law.rb
182
+ - lib/law/judgement.rb
183
+ - lib/law/law_base.rb
184
+ - lib/law/laws/actions.rb
185
+ - lib/law/laws/judgements.rb
186
+ - lib/law/laws/petitions.rb
187
+ - lib/law/laws/statutes.rb
188
+ - lib/law/legalize.rb
189
+ - lib/law/permission_list.rb
190
+ - lib/law/petition.rb
191
+ - lib/law/regulation_base.rb
192
+ - lib/law/regulations/core.rb
193
+ - lib/law/regulations/statutes.rb
194
+ - lib/law/rspec/custom_matchers.rb
195
+ - lib/law/rspec/custom_matchers/be_default_for.rb
196
+ - lib/law/rspec/custom_matchers/be_enforced_by.rb
197
+ - lib/law/rspec/custom_matchers/be_imposed_by.rb
198
+ - lib/law/rspec/custom_matchers/define_action.rb
199
+ - lib/law/rspec/custom_matchers/have_default_statute.rb
200
+ - lib/law/rspec/custom_matchers/impose_regulations.rb
201
+ - lib/law/rspec/shoulda_matcher_helper.rb
202
+ - lib/law/spec_helper.rb
203
+ - lib/law/statute_base.rb
204
+ - lib/law/statutes/laws.rb
205
+ - lib/law/statutes/regulations.rb
73
206
  - lib/law/version.rb
74
- homepage: https://www.freshly.com/
207
+ homepage: https://github.com/Freshly/law/tree/master
75
208
  licenses:
76
209
  - MIT
77
- metadata: {}
210
+ metadata:
211
+ homepage_uri: https://github.com/Freshly/law/tree/master
212
+ source_code_uri: https://github.com/Freshly/law/tree/master
213
+ changelog_uri: https://github.com/Freshly/law/blob/master/CHANGELOG.md
78
214
  post_install_message:
79
215
  rdoc_options: []
80
216
  require_paths:
@@ -90,9 +226,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
226
  - !ruby/object:Gem::Version
91
227
  version: '0'
92
228
  requirements: []
93
- rubyforge_project:
94
- rubygems_version: 2.7.6
229
+ rubygems_version: 3.0.3
95
230
  signing_key:
96
231
  specification_version: 4
97
- summary: Enforce the laws of your application
232
+ summary: Give illegal operations a whole new meaning with this policy enforcement
98
233
  test_files: []
data/.gitignore DELETED
@@ -1,11 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.5.1
7
- before_install: gem install bundler -v 2.0.1
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in law.gemspec
4
- gemspec
data/Rakefile DELETED
@@ -1,6 +0,0 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
3
-
4
- RSpec::Core::RakeTask.new(:spec)
5
-
6
- task :default => :spec
data/bin/console DELETED
@@ -1,14 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "bundler/setup"
4
- require "law"
5
-
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
8
-
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
- # require "pry"
11
- # Pry.start
12
-
13
- require "irb"
14
- IRB.start(__FILE__)
data/bin/setup DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
data/law.gemspec DELETED
@@ -1,29 +0,0 @@
1
-
2
- lib = File.expand_path("../lib", __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "law/version"
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "law"
8
- spec.version = Law::VERSION
9
- spec.authors = ["Eric Garside"]
10
- spec.email = ["garside@gmail.com"]
11
-
12
- spec.summary = "Enforce the laws of your application"
13
- spec.description = "Illegal operations take on a whole new meaning"
14
- spec.homepage = "https://www.freshly.com/"
15
- spec.license = "MIT"
16
-
17
- # Specify which files should be added to the gem when it is released.
18
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
19
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
20
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
- end
22
- spec.bindir = "exe"
23
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
- spec.require_paths = ["lib"]
25
-
26
- spec.add_development_dependency "bundler", "~> 2.0"
27
- spec.add_development_dependency "rake", "~> 10.0"
28
- spec.add_development_dependency "rspec", "~> 3.0"
29
- end