law 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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