surrounded 0.9.11 → 1.1.0

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.
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,22 +1,19 @@
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)
7
8
  parameters = setup_args.join(',')
8
9
  default_initializer(parameters, setup_args, &block)
9
10
  end
10
- def initialize(*setup_args, &block)
11
- warn "Deprecated: The behavior of 'initialize' will require keywords in the future
12
- Consider using keyword arguments or switching to 'initialize_without_keywords'\n\n"
13
- initialize_without_keywords(*setup_args, &block)
14
- end
15
11
 
16
- def keyword_initialize(*setup_args, &block)
12
+ def initialize(*setup_args, &block)
17
13
  parameters = setup_args.map{|a| "#{a}:"}.join(',')
18
14
  default_initializer(parameters, setup_args, &block)
19
15
  end
16
+ alias keyword_initialize initialize
20
17
  alias initialize_with_keywords keyword_initialize
21
18
 
22
19
  def initializer_block
@@ -45,4 +42,4 @@ module Surrounded
45
42
  end
46
43
  end
47
44
  end
48
- 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
@@ -1,16 +1,9 @@
1
1
  module Surrounded
2
2
  module Context
3
- class InvalidRoleType < ::StandardError
4
- unless method_defined?(:cause)
5
- def initialize(msg=nil)
6
- super
7
- @cause = $!
8
- end
9
- attr_reader :cause
10
- end
11
- end
3
+ class InvalidRoleType < ::StandardError; end
12
4
 
13
5
  module RoleBuilders
6
+ extend Seclusion
14
7
 
15
8
  def self.extended(base)
16
9
  Surrounded::Exceptions.define(base, exceptions: :InvalidRoleType)
@@ -67,7 +60,7 @@ module Surrounded
67
60
  # Admin
68
61
  private_const_set(RoleName(name), Negotiator.for_role(behavior))
69
62
  end
70
-
63
+
71
64
  private
72
65
  def RoleName(text, suffix=nil)
73
66
  RoleName.new(text, suffix)
@@ -75,4 +68,4 @@ module Surrounded
75
68
 
76
69
  end
77
70
  end
78
- end
71
+ end
@@ -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
@@ -53,7 +54,7 @@ module Surrounded
53
54
  def default_role_type=(type)
54
55
  @default_role_type = type
55
56
  end
56
-
57
+
57
58
  # Provide the ability to create access control methods for your triggers.
58
59
  def protect_triggers; self.extend(::Surrounded::AccessControl); end
59
60
 
@@ -68,21 +69,6 @@ module Surrounded
68
69
  def role_const_defined?(name)
69
70
  const_defined?(name, false)
70
71
  end
71
-
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
72
 
87
73
  # Conditional const_get for a named role behavior
88
74
  def role_const(name)
@@ -119,11 +105,14 @@ module Surrounded
119
105
  self.class.triggers
120
106
  end
121
107
 
122
- def rebind(options_hash)
108
+ # Reuse the same context object but pass new values
109
+ def rebind(**options_hash)
123
110
  clear_instance_variables
124
- initialize(**options_hash)
125
- rescue ArgumentError
126
- initialize(*options_hash.values)
111
+ begin
112
+ initialize(**options_hash)
113
+ rescue ArgumentError
114
+ initialize(*options_hash.values)
115
+ end
127
116
  self
128
117
  end
129
118
 
@@ -193,7 +182,7 @@ module Surrounded
193
182
  return obj if !wrapper_name
194
183
  klass.method(wrapper_name).call(obj)
195
184
  end
196
-
185
+
197
186
  def remove_behavior(role, behavior, object)
198
187
  if behavior && role_const_defined?(behavior)
199
188
  remover_name = (module_removal_methods + unwrap_methods).find do |meth|
@@ -306,4 +295,4 @@ module Surrounded
306
295
  end
307
296
  end
308
297
  end
309
- end
298
+ 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 = "0.9.11"
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
@@ -39,7 +39,7 @@ describe Surrounded::Context, 'auto-assigning roles for collections' do
39
39
  let(:other_two){ User.new('Jason') }
40
40
  let(:others){ [other_one, other_two] }
41
41
 
42
- let(:context){ CollectionContext.new(members, others) }
42
+ let(:context){ CollectionContext.new(members: members, others: others) }
43
43
 
44
44
  it 'assigns the collection role to collections' do
45
45
  assert_equal members.size, context.get_members_count
@@ -37,7 +37,7 @@ end
37
37
  describe Surrounded::Context, 'access control' do
38
38
  let(:user){ User.new("Jim") }
39
39
  let(:other_user){ User.new("Guille") }
40
- let(:context){ FilteredContext.new(user, other_user) }
40
+ let(:context){ FilteredContext.new(user: user, other_user: other_user) }
41
41
 
42
42
  it 'includes triggers when allowed' do
43
43
  context.stub(:disallow_if_ready?, false) do
@@ -38,7 +38,7 @@ end
38
38
  describe Surrounded::Context, 'forwarding triggers' do
39
39
  let(:user){ User.new("Jim") }
40
40
  let(:other_user){ User.new("Guille") }
41
- let(:context){ Sending.new(user, other_user) }
41
+ let(:context){ Sending.new(one: user, two: other_user) }
42
42
 
43
43
  it 'forwards multiple configured instance methods as triggers' do
44
44
  assert_equal 'hello', context.hello
@@ -3,7 +3,7 @@ require 'test_helper'
3
3
  describe Surrounded::Context, 'reusing context object' do
4
4
  let(:user){ User.new("Jim") }
5
5
  let(:other_user){ User.new("Guille") }
6
- let(:context){ TestContext.new(user, other_user) }
6
+ let(:context){ TestContext.new(user: user, other_user: other_user) }
7
7
 
8
8
  it 'allows rebinding new players' do
9
9
  expect(context.access_other_object).must_equal 'Guille'