surrounded 1.0.0 → 1.1.0

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
- SHA1:
3
- metadata.gz: 32e91c5123e8db80ef2b15289144c2cec4f5a774
4
- data.tar.gz: 8ffdd46f4497eee0fc0c96f29de1efb0631879d6
2
+ SHA256:
3
+ metadata.gz: 152c9c728084be8069384cf825cedf5ab827d4fd28b0710cbdd19229d8284bc7
4
+ data.tar.gz: 82a50b285aa5328084481c438d78f8ed3e17ba362bcad45c14efaf5ad1bf0fc3
5
5
  SHA512:
6
- metadata.gz: a2c8b8e5b137b7b8b74c2642cdc490bbcab860dcd85ca0a08e9b67490ce0f1ff4c5b405821bb3daa1e3a643514e75023265cc902b0617a3cff1bee90a61a2480
7
- data.tar.gz: a25fd8d9c292be440405e732ce59f5087a0100a628a0f424ad95a918f72e924377c8485eeeec4a70fa1d01dd900ad5828530ee693ab252d1b56ebfa569aa16ad
6
+ metadata.gz: 9f7cf841bf2c4ceeb27fd6bd2f11c4041cad7c28e1ce71574b5bbc1c201b0944a2481966b1c2349b3cbfd465a98f7627f1621fae0e0e8d2c2c9fcde42859577f
7
+ data.tar.gz: aa91c16fefb0cdc19f1b597d2fffd876c2f0bce15bcbcaacb3a713570acac350d8ff434a8c3d6177d4c9e1a79c620611f012d1aff2bcd24fef10ddf696bcd13b
@@ -0,0 +1,70 @@
1
+ # For most projects, this workflow file will not need changing; you simply need
2
+ # to commit it to your repository.
3
+ #
4
+ # You may wish to alter this file to override the set of languages analyzed,
5
+ # or to provide custom queries or build logic.
6
+ #
7
+ # ******** NOTE ********
8
+ # We have attempted to detect the languages in your repository. Please check
9
+ # the `language` matrix defined below to confirm you have the correct set of
10
+ # supported CodeQL languages.
11
+ #
12
+ name: "CodeQL"
13
+
14
+ on:
15
+ push:
16
+ branches: [ master ]
17
+ pull_request:
18
+ # The branches below must be a subset of the branches above
19
+ branches: [ master ]
20
+ schedule:
21
+ - cron: '41 18 * * 4'
22
+
23
+ jobs:
24
+ analyze:
25
+ name: Analyze
26
+ runs-on: ubuntu-latest
27
+ permissions:
28
+ actions: read
29
+ contents: read
30
+ security-events: write
31
+
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ language: [ 'ruby' ]
36
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37
+ # Learn more about CodeQL language support at https://git.io/codeql-language-support
38
+
39
+ steps:
40
+ - name: Checkout repository
41
+ uses: actions/checkout@v3
42
+
43
+ # Initializes the CodeQL tools for scanning.
44
+ - name: Initialize CodeQL
45
+ uses: github/codeql-action/init@v2
46
+ with:
47
+ languages: ${{ matrix.language }}
48
+ # If you wish to specify custom queries, you can do so here or in a config file.
49
+ # By default, queries listed here will override any specified in a config file.
50
+ # Prefix the list here with "+" to use these queries and those in the config file.
51
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
52
+
53
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54
+ # If this step fails, then you should remove it and run the build manually (see below)
55
+ - name: Autobuild
56
+ uses: github/codeql-action/autobuild@v2
57
+
58
+ # ℹ️ Command-line programs to run using the OS shell.
59
+ # 📚 https://git.io/JvXDl
60
+
61
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62
+ # and modify them (or add more) to build your code if your project
63
+ # uses a compiled language
64
+
65
+ #- run: |
66
+ # make bootstrap
67
+ # make release
68
+
69
+ - name: Perform CodeQL Analysis
70
+ uses: github/codeql-action/analyze@v2
@@ -0,0 +1,18 @@
1
+ name: spec
2
+
3
+ on: push
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ ruby: [ '2.7', '3.0', '3.1' ]
11
+ name: Ruby ${{ matrix.ruby }} sample
12
+ steps:
13
+ - uses: actions/checkout@v2
14
+ - uses: ruby/setup-ruby@v1
15
+ with:
16
+ ruby-version: ${{ matrix.ruby }}
17
+ bundler-cache: true
18
+ - run: bundle exec rake
data/Changelog.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [1.1.0]
6
+
7
+ - Dropped support for Ruby below 2.7 to take advantage of the ellipsis for argument forwarding
8
+ - Memoize the Negotiator methods for interface
9
+
10
+ ## [1.0.1]
11
+
12
+ - Fix a bug where shortcut_triggers would not work with keyword initialize
13
+
5
14
  ## [1.0.0]
6
15
 
7
16
  - Drop deprecations around Context initialize method. It now requires keyword arguments. Non-keyword argumennts may be used with initialize_without_keywords
data/Gemfile CHANGED
@@ -3,7 +3,6 @@ source 'https://rubygems.org'
3
3
  group :test do
4
4
  gem 'minitest'
5
5
  gem "simplecov"
6
- gem 'codeclimate-test-reporter', '~> 1.0.8'
7
6
  gem 'casting'
8
7
  end
9
8
 
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2016 'Jim Gay'
1
+ Copyright (c) 2013-2022 'Jim Gay'
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # ![Surrounded](http://saturnflyer.github.io/surrounded/images/surrounded.png "Surrounded")
2
2
  ## Be in control of business logic.
3
3
 
4
- [![Build Status](https://travis-ci.org/saturnflyer/surrounded.png?branch=master)](https://travis-ci.org/saturnflyer/surrounded)
4
+ [![Build Status](https://github.com/saturnflyer/surrounded/actions/workflows/test.yml/badge.svg)](https://github.com/saturnflyer/surrounded/actions)
5
5
  [![Code Climate](https://codeclimate.com/github/saturnflyer/surrounded.png)](https://codeclimate.com/github/saturnflyer/surrounded)
6
6
 
7
7
  Surrounded is designed to help you better manage your business logic by keeping cohesive behaviors together. Bring objects together to implement your use cases and gain behavior only when necessary.
@@ -53,13 +53,13 @@ There are 2 things left to do:
53
53
 
54
54
  Initializing contexts does not require the use of keyword arguments, but you may opt out.
55
55
 
56
- You should consider using explicit names when initialize now by using `initialize_without_keywords`:
56
+ You should consider using explicit names when initializing now by using `initialize_without_keywords`:
57
57
 
58
58
  ```ruby
59
59
  class Employment
60
60
  extend Surrounded::Context
61
61
 
62
- initialize_withou_keywords :employee, :boss
62
+ initialize_without_keywords :employee, :boss
63
63
  end
64
64
 
65
65
  user1 = User.find(1)
@@ -636,6 +636,22 @@ class Organization
636
636
  end
637
637
  ```
638
638
 
639
+ If you want to change the way the singular verson of a role is used, override `singularize_name`:
640
+
641
+ ```ruby
642
+ class Organization
643
+ extend Surrounded::Context
644
+
645
+ def singularize_name(name)
646
+ if name == "my special rule"
647
+ # do your thing
648
+ else
649
+ super # use the default
650
+ end
651
+ end
652
+ end
653
+ ```
654
+
639
655
  ## Reusing context objects
640
656
 
641
657
  If you create a context object and need to use the same type of object with new role players, you may use the `rebind` method. It will clear any instance_variables from your context object and map the given objects to their names:
@@ -824,7 +840,7 @@ context = ActiviatingAccount.new(some_object, some_account)
824
840
  context.triggers # => lists a Set of triggers
825
841
  # when using protect_triggers
826
842
  context.triggers # => lists a Set of triggers which may currently be called
827
- context.triggers # => lists a Set of all triggers (the same as if protect_triggers was _not_ used)
843
+ context.all_triggers # => lists a Set of all triggers (the same as if protect_triggers was _not_ used)
828
844
  context.allow?(:trigger_name) # => returns a boolean if the trigger may be run
829
845
 
830
846
  # reuse the context object with new role players
@@ -835,6 +851,20 @@ context.rebind(activator: another_object, account: another_account)
835
851
 
836
852
  The dependencies are minimal. The plan is to keep it that way but allow you to configure things as you need. The [Triad](http://github.com/saturnflyer/triad) project was written specifically to manage the mapping of roles and objects to the modules which contain the behaviors. It is used in Surrounded to keep track of role player, roles, and role constant names but it is not a hard requirement. You may implement your own but presently you'll need to dive into the implementation to fully understand how. Future updates may provide better support and guidance.
837
853
 
854
+ If you want to override the class used for mapping roles to behaviors, override the `role_map` method.
855
+
856
+ ```ruby
857
+ class MyContext
858
+ extend Surrounded::Context
859
+
860
+ def role_map
861
+ @container ||= role_mapper_class.new(base: MySpecialDataContainer)
862
+ end
863
+ end
864
+ ```
865
+
866
+ The class you provide will be initialized with `new` and is expected to implement the methods: `:update`, `:each`, `:values`, and `:keys`.
867
+
838
868
  If you're using [Casting](http://github.com/saturnflyer/casting), for example, Surrounded will attempt to use that before extending an object, but it will still work without it.
839
869
 
840
870
  ## Support for other ways to apply behavior
@@ -918,9 +948,9 @@ class MyCustomContext
918
948
  end
919
949
  ```
920
950
 
921
- You can remember the method name by the convention that `remove` or `apply` describes it's function, `behavior` refers to the first argument (thet contsant holding the behaviors), and then the name of the role which refers to the role playing object: `remove_behavior_role`.
951
+ You can remember the method name by the convention that `remove` or `apply` describes it's function, `behavior` refers to the first argument (the constant holding the behaviors), and then the name of the role which refers to the role playing object: `remove_behavior_role`.
922
952
 
923
- ##Name collisions between methods and roles
953
+ ## Name collisions between methods and roles
924
954
 
925
955
  Lets say that you wish to create a context as below, intending to use instances of the following two classes as role players:
926
956
 
data/examples/bottles.rb CHANGED
@@ -1,145 +1,135 @@
1
- require 'surrounded'
1
+ require "surrounded"
2
+ class CountdownSong
3
+ def initialize(max:, min:)
4
+ @max = max
5
+ @min = min
6
+ end
7
+ attr_reader :max, :min
8
+
9
+ def sing_with(verse_template)
10
+ max.downto(min).map { |num| verse(num, verse_template) }
11
+ end
12
+
13
+ def verse(number, verse_template)
14
+ verse_template.lyrics(number)
15
+ end
16
+ end
17
+
18
+ class BottleVerse
19
+ def self.lyrics(number)
20
+ new(bottle_number: number).lyrics
21
+ end
2
22
 
3
- class Countdown
4
23
  extend Surrounded::Context
5
-
6
- initialize :singer, :number, :location
7
-
8
- trigger :start do
9
- singer.start
10
- end
11
-
12
- trigger :continue do
13
- singer.continue
14
- end
15
-
16
- trigger :finish do
17
- singer.announce_status
18
- end
19
-
20
- role :singer do
21
- def start
22
- announce_full_status
23
- take_action
24
- end
25
-
26
- def continue
27
- announce_status
28
- pause
29
- start
30
- end
31
-
32
- def announce_full_status
33
- output %{#{location.status}, #{location.inventory}}.capitalize
34
- end
35
-
36
- def announce_status
37
- output %{#{location.status}}.capitalize
24
+
25
+ initialize :bottle_number
26
+
27
+ trigger :lyrics do
28
+ "#{bottle_number} of beer on the wall, ".capitalize +
29
+ "#{bottle_number} of beer.\n" +
30
+ "#{bottle_number.action}, " +
31
+ "#{bottle_number.successor} of beer on the wall.\n"
32
+ end
33
+
34
+ role :bottle_number, :wrapper do
35
+ def self.handles(number)
36
+ BottleVerse.register_bottle_role(number, self)
38
37
  end
39
-
40
- def take_action
41
- if location.empty?
42
- output %{Go to the store and get some more}
43
- next_part.finish
44
- else
45
- output %{#{location.subtraction}, pass it around}.capitalize
46
- next_part.continue
47
- end
38
+
39
+ def to_s
40
+ "#{quantity} #{container}"
48
41
  end
49
-
50
- def pause
51
- output ""
42
+
43
+ def quantity
44
+ __getobj__.to_s
52
45
  end
53
-
54
- def next_part
55
- context.class.new(singer, number.pred, location)
46
+
47
+ def container
48
+ "bottles"
56
49
  end
57
- end
58
-
59
- role :number, :wrap do
60
- def name
61
- self.zero? ? 'no more' : to_i
50
+
51
+ def action
52
+ "Take #{pronoun} down and pass it around"
62
53
  end
63
-
54
+
64
55
  def pronoun
65
- self == 1 ? 'it' : 'one'
66
- end
67
-
68
- def container
69
- self == 1 ? 'bottle' : 'bottles'
56
+ "one"
70
57
  end
71
-
72
- def pred
73
- self.zero? ? 99 : super
58
+
59
+ def successor
60
+ context.bottle_role_player(pred)
74
61
  end
75
62
  end
76
-
77
- role :location do
78
- def empty?
79
- number.zero?
80
- end
81
-
82
- def inventory
83
- %{#{number.name} #{number.container} of beer}
84
- end
85
-
86
- def status
87
- %{#{inventory} #{placement} #{name}}
88
- end
89
-
90
- def subtraction
91
- %{take #{number.pronoun} #{removal_strategy}}
92
- end
63
+
64
+ # Inherit from existing role
65
+ def self.bottle_role(name, &block)
66
+ mod_name = RoleName(name)
67
+ klass = Class.new(BottleNumber, &block)
68
+ const_set(mod_name, klass)
93
69
  end
94
- end
95
70
 
96
- class Location
97
- include Surrounded
98
-
99
- def placement
100
- 'on'
71
+ def self.register_bottle_role(number, klass)
72
+ @@registry ||= Hash.new { BottleNumber }
73
+ @@registry[number] = klass
101
74
  end
102
-
103
- def removal_strategy
104
- 'off'
75
+
76
+ def bottle_role_for(number)
77
+ @@registry[number]
105
78
  end
106
-
107
- def name
108
- "the " + self.class.to_s.downcase
79
+
80
+ def map_role_bottle_number(num)
81
+ map_role(:bottle_number, bottle_role_for(num), num)
109
82
  end
110
- end
111
83
 
112
- class Wall < Location
113
- def removal_strategy
114
- 'down'
84
+ def bottle_role_player(number)
85
+ bottle_role_for(number).new(number)
115
86
  end
116
- end
117
87
 
118
- class Box < Location
119
- def placement
120
- 'in'
88
+ role :bottle_number_0, :bottle_role do
89
+ handles 0
90
+
91
+ def quantity
92
+ "no more"
93
+ end
94
+
95
+ def action
96
+ "Go to the store and buy some more"
97
+ end
98
+
99
+ def successor
100
+ context.bottle_role_player(99)
101
+ end
121
102
  end
122
-
123
- def removal_strategy
124
- 'out'
103
+
104
+ role :bottle_number_1, :bottle_role do
105
+ handles 1
106
+
107
+ def container
108
+ "bottle"
109
+ end
110
+
111
+ def pronoun
112
+ "it"
113
+ end
125
114
  end
126
115
  end
127
116
 
128
- class Chorus
129
- include Surrounded
130
- def output(value)
131
- STDOUT.puts(value)
117
+ class Bottles
118
+ def song_template(upper: 99, lower: 0)
119
+ CountdownSong.new(max: upper, min: lower)
132
120
  end
133
- end
134
121
 
135
- class Sheet
136
- include Surrounded
137
- def output(value)
138
- File.open('bottles.txt', 'a') do |f|
139
- f.puts(value)
140
- end
122
+ def song
123
+ song_template.sing_with(BottleVerse)
124
+ end
125
+
126
+ def verses(upper, lower)
127
+ song_template(upper: upper, lower: lower).sing_with(BottleVerse)
128
+ end
129
+
130
+ def verse(number)
131
+ song_template.verse(number, BottleVerse)
141
132
  end
142
133
  end
143
134
 
144
- Countdown.new(Chorus.new, 3, Wall.new).start
145
- # Countdown.new(Sheet.new, 3, Box.new).start
135
+ puts Bottles.new.song
@@ -33,12 +33,10 @@ module Surrounded
33
33
  mod = Module.new
34
34
  mod.class_eval {
35
35
  define_method "disallow_#{name}?" do
36
- begin
37
- apply_behaviors
38
- instance_exec(&block)
39
- ensure
40
- remove_behaviors
41
- end
36
+ apply_behaviors
37
+ instance_exec(&block)
38
+ ensure
39
+ remove_behaviors
42
40
  end
43
41
  }
44
42
  const_set("SurroundedAccess#{name}", mod)
@@ -55,22 +53,16 @@ module Surrounded
55
53
  # in disallow blocks.
56
54
  def triggers
57
55
  all_triggers.select {|name|
58
- method_restrictor = "disallow_#{name}?"
59
- !self.respond_to?(method_restrictor, true) || !self.send(method_restrictor)
56
+ allow?(name)
60
57
  }.to_set
61
58
  end
62
59
 
63
60
  # Ask if the context will allow access to a trigger given the current players.
64
61
  def allow?(name)
65
- unless self.respond_to?(name)
66
- raise NoMethodError, %{undefined method `#{name}' for #{self.inspect}}
67
- end
68
- if self.respond_to?("disallow_#{name}?")
69
- !self.public_send("disallow_#{name}?")
70
- else
71
- true
72
- end
62
+ raise NoMethodError, %{undefined method `#{name}' for #{self.inspect}} unless self.respond_to?(name)
63
+ predicate = "disallow_#{name}?"
64
+ !self.respond_to?(predicate) || !self.public_send(predicate)
73
65
  end
74
66
  end
75
67
  end
76
- end
68
+ end
@@ -1,6 +1,7 @@
1
1
  module Surrounded
2
2
  module Context
3
3
  module Initializing
4
+ extend Seclusion
4
5
  # Shorthand for creating an instance level initialize method which
5
6
  # handles the mapping of the given arguments to their named role.
6
7
  def initialize_without_keywords(*setup_args, &block)
@@ -41,4 +42,4 @@ module Surrounded
41
42
  end
42
43
  end
43
44
  end
44
- end
45
+ end
@@ -15,8 +15,9 @@ module Surrounded
15
15
  # circumvent method_missing
16
16
  mod.instance_methods(false).each do |meth|
17
17
  num = __LINE__; klass.class_eval %{
18
- def #{meth}(*args, &block)
19
- __behaviors__.instance_method(:#{meth}).bind(@object).call(*args, &block)
18
+ def #{meth}(...)
19
+ @#{meth}_method ||= __behaviors__.instance_method(:#{meth}).bind(@object)
20
+ @#{meth}_method.call(...)
20
21
  end
21
22
  }, __FILE__, num
22
23
  end
@@ -57,8 +58,8 @@ module Surrounded
57
58
  @object = object
58
59
  end
59
60
 
60
- def method_missing(meth, *args, &block)
61
- @object.send(meth, *args, &block)
61
+ def method_missing(meth, ...)
62
+ @object.send(meth, ...)
62
63
  end
63
64
 
64
65
  def respond_to_missing?(meth, include_private=false)
@@ -66,4 +67,4 @@ module Surrounded
66
67
  end
67
68
  end
68
69
  end
69
- end
70
+ end
@@ -3,6 +3,7 @@ module Surrounded
3
3
  class InvalidRoleType < ::StandardError; end
4
4
 
5
5
  module RoleBuilders
6
+ extend Seclusion
6
7
 
7
8
  def self.extended(base)
8
9
  Surrounded::Exceptions.define(base, exceptions: :InvalidRoleType)
@@ -6,12 +6,16 @@ module Surrounded
6
6
  extend Forwardable
7
7
 
8
8
  class << self
9
+ # Get the role map container and provide an alternative if desired
10
+ # Ex: RoleMap.from_base(SomeCustomContainer)
9
11
  def from_base(klass=::Triad)
10
- role_mapper = Class.new(self)
11
- Surrounded::Exceptions.define(role_mapper, exceptions: :ItemNotPresent, namespace: klass)
12
- role_mapper.container_class=(klass)
13
- role_mapper.def_delegators :container, :update, :each, :values, :keys
14
- role_mapper
12
+ unless const_defined?(:Container)
13
+ role_mapper = Class.new(self)
14
+ role_mapper.container_class=(klass)
15
+ Surrounded::Exceptions.define(role_mapper, exceptions: :ItemNotPresent, namespace: klass)
16
+ const_set(:Container, role_mapper)
17
+ end
18
+ const_get(:Container)
15
19
  end
16
20
 
17
21
  def container_class=(klass)
@@ -19,23 +23,28 @@ module Surrounded
19
23
  end
20
24
  end
21
25
 
26
+ def_delegators :container, :update, :each, :values, :keys
27
+
22
28
  def container
23
29
  @container ||= self.class.instance_variable_get(:@container_class).new
24
30
  end
25
31
 
32
+ # Check if a role exists in the map
26
33
  def role?(role)
27
34
  keys.include?(role)
28
35
  end
29
36
 
37
+ # Check if an object is playing a role in this map
30
38
  def role_player?(object)
31
39
  !values(object).empty?
32
- rescue ::StandardError
40
+ rescue self.container.class::ItemNotPresent
33
41
  false
34
42
  end
35
43
 
44
+ # Get the object playing the given role
36
45
  def assigned_player(role)
37
46
  values(role).first
38
47
  end
39
48
  end
40
49
  end
41
- end
50
+ end
@@ -0,0 +1,20 @@
1
+ module Surrounded
2
+ module Context
3
+ module Seclusion
4
+ # Set a named constant and make it private
5
+ def private_const_set(name, const)
6
+ unless role_const_defined?(name)
7
+ const = const_set(name, const)
8
+ private_constant name.to_sym
9
+ end
10
+ const
11
+ end
12
+
13
+ # Create attr_reader for the named methods and make them private
14
+ def private_attr_reader(*method_names)
15
+ attr_reader(*method_names)
16
+ private(*method_names)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -54,7 +54,7 @@ module Surrounded
54
54
 
55
55
  def define_trigger(name)
56
56
  line = __LINE__; self.class_eval %{
57
- def #{name}(*args, &block)
57
+ def #{name}(...)
58
58
  begin
59
59
  apply_behaviors
60
60
 
@@ -71,7 +71,7 @@ module Surrounded
71
71
  if method_defined?(name)
72
72
  %{super}
73
73
  else
74
- %{self.send("__trigger_#{name}", *args, &block)}
74
+ %{self.send("__trigger_#{name}", ...)}
75
75
  end
76
76
  end
77
77
 
@@ -85,4 +85,4 @@ module Surrounded
85
85
  end
86
86
  end
87
87
  end
88
- end
88
+ end
@@ -1,6 +1,7 @@
1
1
  require 'set'
2
2
  require 'surrounded/exceptions'
3
3
  require 'surrounded/context/role_map'
4
+ require 'surrounded/context/seclusion'
4
5
  require 'surrounded/context/role_builders'
5
6
  require 'surrounded/context/initializing'
6
7
  require 'surrounded/context/forwarding'
@@ -20,7 +21,7 @@ module Surrounded
20
21
  module Context
21
22
  def self.extended(base)
22
23
  base.class_eval {
23
- extend RoleBuilders, Initializing, Forwarding, NameCollisionDetector
24
+ extend Seclusion, RoleBuilders, Initializing, Forwarding, NameCollisionDetector
24
25
 
25
26
  @triggers = Set.new
26
27
  include InstanceMethods
@@ -69,21 +70,6 @@ module Surrounded
69
70
  const_defined?(name, false)
70
71
  end
71
72
 
72
- # Set a named constant and make it private
73
- def private_const_set(name, const)
74
- unless role_const_defined?(name)
75
- const = const_set(name, const)
76
- private_constant name.to_sym
77
- end
78
- const
79
- end
80
-
81
- # Create attr_reader for the named methods and make them private
82
- def private_attr_reader(*method_names)
83
- attr_reader(*method_names)
84
- private(*method_names)
85
- end
86
-
87
73
  # Conditional const_get for a named role behavior
88
74
  def role_const(name)
89
75
  if role_const_defined?(name)
@@ -119,10 +105,11 @@ module Surrounded
119
105
  self.class.triggers
120
106
  end
121
107
 
108
+ # Reuse the same context object but pass new values
122
109
  def rebind(**options_hash)
123
110
  clear_instance_variables
124
111
  begin
125
- initialize(options_hash)
112
+ initialize(**options_hash)
126
113
  rescue ArgumentError
127
114
  initialize(*options_hash.values)
128
115
  end
@@ -3,9 +3,21 @@ module Surrounded
3
3
  private
4
4
 
5
5
  def define_shortcut(name)
6
- singleton_class.send(:define_method, name) do |*args|
7
- instance = self.new(*args)
8
- instance.public_send(name)
6
+ # if keyword initialize
7
+ if instance_method(:initialize).parameters.dig(0,0) == :keyreq
8
+ singleton_class.send(:define_method, name) do |**args|
9
+ instance = begin
10
+ self.new(**args)
11
+ end
12
+ instance.public_send(name)
13
+ end
14
+ else # non-keyword initialize
15
+ singleton_class.send(:define_method, name) do |*args|
16
+ instance = begin
17
+ self.new(*args)
18
+ end
19
+ instance.public_send(name)
20
+ end
9
21
  end
10
22
  end
11
23
 
@@ -16,4 +28,4 @@ module Surrounded
16
28
  super
17
29
  end
18
30
  end
19
- end
31
+ end
@@ -1,6 +1,6 @@
1
1
  module Surrounded
2
- VERSION = "1.0.0"
3
-
2
+ VERSION = "1.1.0"
3
+
4
4
  def self.version
5
5
  VERSION
6
6
  end
data/surrounded.gemspec CHANGED
@@ -20,6 +20,5 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "triad", "~> 0.3.0"
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.12"
24
23
  spec.add_development_dependency "rake"
25
24
  end
@@ -17,10 +17,35 @@ class ShortcutContext
17
17
  end
18
18
  end
19
19
 
20
+ class ShortcutContextNoKeywords
21
+ extend Surrounded::Context
22
+ shortcut_triggers
23
+
24
+ initialize_without_keywords :user, :other
25
+
26
+ trigger :shorty do
27
+ user.speak
28
+ end
29
+
30
+ role :user do
31
+ def speak
32
+ 'it works, shorty!'
33
+ end
34
+ end
35
+ end
36
+
20
37
  describe Surrounded::Context, 'shortcuts' do
21
38
  let(:user){ User.new("Jim") }
22
39
  let(:other){ User.new("Guille") }
23
40
  it 'creates shortcut class methods for triggers' do
24
41
  assert_equal 'it works, shorty!', ShortcutContext.shorty(user: user, other: other)
25
42
  end
26
- end
43
+ end
44
+
45
+ describe Surrounded::Context, 'shortcuts with initialize_without_keywords' do
46
+ let(:user){ User.new("Jim") }
47
+ let(:other){ User.new("Guille") }
48
+ it 'creates shortcut class methods for triggers' do
49
+ assert_equal 'it works, shorty!', ShortcutContextNoKeywords.shorty(user, other)
50
+ end
51
+ end
@@ -77,7 +77,7 @@ describe ProxyContext do
77
77
  end
78
78
 
79
79
  it 'passes missing methods up the ancestry of the object' do
80
- err = ->{ context.admin_missing_method }.must_raise(NoMethodError)
80
+ err = _{ context.admin_missing_method }.must_raise(NoMethodError)
81
81
 
82
82
  assert_match(/ProxyUser.*name="Jim"/, err.message)
83
83
  end
@@ -28,7 +28,7 @@ describe Surrounded::Context, '.initialize' do
28
28
  err = assert_raises(ArgumentError){
29
29
  KeywordContext.new(other_user: User.new('Amy'))
30
30
  }
31
- assert_match(/missing keyword: user/, err.message)
31
+ assert_match(/missing keyword: :?user/, err.message)
32
32
  end
33
33
  end
34
34
 
@@ -53,11 +53,35 @@ describe Surrounded::Context, '.role' do
53
53
  def hello
54
54
  'hello from admin'
55
55
  end
56
+
57
+ def splat_args(*args)
58
+ args
59
+ end
60
+
61
+ def keyword_args(**kwargs)
62
+ kwargs
63
+ end
64
+
65
+ def mixed_args(*args, **kwargs)
66
+ [args, kwargs]
67
+ end
56
68
  end
57
69
 
58
70
  trigger :admin_hello do
59
71
  admin.hello
60
72
  end
73
+
74
+ trigger :splat_args do |*args|
75
+ admin.splat_args(*args)
76
+ end
77
+
78
+ trigger :keyword_args do |**kwargs|
79
+ admin.keyword_args(**kwargs)
80
+ end
81
+
82
+ trigger :mixed_args do |*args, **kwargs|
83
+ admin.mixed_args(*args, **kwargs)
84
+ end
61
85
  end
62
86
 
63
87
  class Hello
@@ -85,6 +109,18 @@ describe Surrounded::Context, '.role' do
85
109
  it 'creates a private accessor method' do
86
110
  assert context.respond_to?(:admin, true)
87
111
  end
112
+
113
+ it 'works with multiple args' do
114
+ assert_equal context.splat_args("one", "two"), %w[ one two ]
115
+ end
116
+
117
+ it 'works with multiple keyword args' do
118
+ assert_equal context.keyword_args(one: "one", two: "two"), { one: "one", two: "two" }
119
+ end
120
+
121
+ it 'works with multiple mixed args' do
122
+ assert_equal context.mixed_args("one", :two, three: "three", four: "four"), [["one", :two], { three: "three", four: "four" }]
123
+ end
88
124
  end
89
125
 
90
126
  describe 'unknown' do
@@ -149,4 +185,4 @@ describe Surrounded::Context, '.role' do
149
185
  end
150
186
  end
151
187
  end
152
- end
188
+ end
data/test/test_helper.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'simplecov'
2
2
  require 'minitest/autorun'
3
- SimpleCov.start
3
+ SimpleCov.enable_coverage :branch
4
+ SimpleCov.add_filter %r{version.rb}
5
+ SimpleCov.start unless defined?(Coverage)
4
6
 
5
7
  require 'surrounded'
6
8
  require 'surrounded/context'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surrounded
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "'Jim Gay'"
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-04-30 00:00:00.000000000 Z
11
+ date: 2022-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: triad
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.3.0
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '1.12'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '1.12'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rake
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -60,10 +46,11 @@ extensions: []
60
46
  extra_rdoc_files: []
61
47
  files:
62
48
  - ".codeclimate.yml"
49
+ - ".github/workflows/codeql-analysis.yml"
50
+ - ".github/workflows/test.yml"
63
51
  - ".gitignore"
64
52
  - ".pullreview.yml"
65
53
  - ".simplecov"
66
- - ".travis.yml"
67
54
  - Changelog.md
68
55
  - Gemfile
69
56
  - LICENSE.txt
@@ -80,6 +67,7 @@ files:
80
67
  - lib/surrounded/context/negotiator.rb
81
68
  - lib/surrounded/context/role_builders.rb
82
69
  - lib/surrounded/context/role_map.rb
70
+ - lib/surrounded/context/seclusion.rb
83
71
  - lib/surrounded/context/trigger_controls.rb
84
72
  - lib/surrounded/east_oriented.rb
85
73
  - lib/surrounded/exceptions.rb
@@ -109,7 +97,7 @@ homepage: http://github.com/saturnflyer/surrounded
109
97
  licenses:
110
98
  - MIT
111
99
  metadata: {}
112
- post_install_message:
100
+ post_install_message:
113
101
  rdoc_options: []
114
102
  require_paths:
115
103
  - lib
@@ -124,9 +112,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
112
  - !ruby/object:Gem::Version
125
113
  version: '0'
126
114
  requirements: []
127
- rubyforge_project:
128
- rubygems_version: 2.6.8
129
- signing_key:
115
+ rubygems_version: 3.3.23
116
+ signing_key:
130
117
  specification_version: 4
131
118
  summary: Create encapsulated environments for your objects.
132
119
  test_files:
data/.travis.yml DELETED
@@ -1,19 +0,0 @@
1
- before_install:
2
- - gem install bundler
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.4.1
7
- - 2.3.4
8
- - 2.2.7
9
- - ruby-head
10
- - jruby-head
11
- matrix:
12
- allow_failures:
13
- - rvm: ruby-head
14
- - rvm: jruby-head
15
- addons:
16
- code_climate:
17
- repo_token: 7488b157e7b7f48eac865a9f830fe90a39e6ac10b17f854e17b9529e1854762c
18
- after_success:
19
- - bundle exec codeclimate-test-reporter