surrounded 0.8.4 → 0.9.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
2
  SHA1:
3
- metadata.gz: ea4a7bea400ebf873e76985cffb1ddff7aa481ff
4
- data.tar.gz: 5baeada83ee58583aa93f3e5764565e18983f2a5
3
+ metadata.gz: cb66bc4320f6971198085580154f6ca683dcc952
4
+ data.tar.gz: cfe6c6e94af4ff0fa78922cd1df3b74666abb58a
5
5
  SHA512:
6
- metadata.gz: f2d4bc4c9435035b92a93fb78f51942e1b8223c0012c5e48d61cb1b0a11c674144604e32e22c9c7a7e94b00721625e47c38b79e9157fb0e82d6a43ef686f3f3b
7
- data.tar.gz: a5273eca6dbfb05540166d248ffb9d4b8434721f7e6241fc4682d051f823356f547f0ef4a25ebfa6f8a35d975e25c09a9d28257639fbcafef81c235b83d3fbed
6
+ metadata.gz: 5e9e696a657da51806a9929adfec0c0ef6f838dae69ebe2acf9c10a547998428a935d021118b7550eff2033316b1b177906749e93e50f3150c62abc60d44d0e1
7
+ data.tar.gz: 36395abadb826b0542061e3d51e0831f533a8c4321d4c4b113469dbfae4520f1ae15916919f4f1bf0006ff8ab84956fce227be65fd1e6b822156324aec840af9
data/.travis.yml CHANGED
@@ -1,9 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
3
  - 2.0.0
5
4
  - 2.1.1
6
- - jruby-19mode # JRuby in 1.9 mode
7
5
  - ruby-head
8
6
  - jruby-head
9
7
  - rbx
data/README.md CHANGED
@@ -1,29 +1,173 @@
1
1
  # ![Surrounded](http://saturnflyer.github.io/surrounded/images/surrounded.png "Surrounded")
2
- ## Bring your own complexity
2
+ ## Be in control of business logic.
3
3
 
4
4
  [![Build Status](https://travis-ci.org/saturnflyer/surrounded.png?branch=master)](https://travis-ci.org/saturnflyer/surrounded)
5
5
  [![Code Climate](https://codeclimate.com/github/saturnflyer/surrounded.png)](https://codeclimate.com/github/saturnflyer/surrounded)
6
6
  [![Coverage Status](https://coveralls.io/repos/saturnflyer/surrounded/badge.png)](https://coveralls.io/r/saturnflyer/surrounded)
7
7
  [![Gem Version](https://badge.fury.io/rb/surrounded.png)](http://badge.fury.io/rb/surrounded)
8
8
 
9
- # Surrounded aims to make things simple and get out of your way.
9
+ # Get work done with only what you need and nothing more.
10
10
 
11
- Most of what you care about is defining the behavior of objects. How they interact is important.
12
- The purpose of this library is to clear away the details of getting things setup and to allow you to make changes to the way you handle roles.
11
+ Surrounded is designed to help you better manage your business logic.
13
12
 
14
- There are two main parts to this library.
13
+ ## How to think about your objects
15
14
 
16
- 1. `Surrounded` gives objects an implicit awareness of other objects in their environments.
17
- 2. `Surrounded::Context` helps you create objects which encapsulate other objects **and** their behavior. These *are* the environments.
15
+ First, name the problem you're solving. Then, break down your problem into responsible roles.
18
16
 
19
- First, take a look at creating contexts. This is where you'll spend most of your time.
17
+ Use your problem name as a class and extend it with `Surrounded::Context`
20
18
 
21
- ## Easily create encapsulated environments for your objects.
19
+ It might look like this:
20
+
21
+ ```ruby
22
+ class Employment
23
+ extend Surrounded::Context
24
+
25
+ role :boss
26
+ role :employee
27
+ end
28
+ ```
29
+
30
+ In your application, you'll initialize this class with objects to play the roles that you've defined, so you'll need to specify which role players will use which role.
31
+
32
+ ```ruby
33
+ class Employment
34
+ extend Surrounded::Context
35
+
36
+ initialize :employee, :boss
37
+
38
+ role :boss
39
+ role :employee
40
+ end
41
+ ```
42
+
43
+ Here, you've specified the order when initializing so you can use it like this:
44
+
45
+ ```ruby
46
+ user1 = User.find(1)
47
+ user2 = User.find(2)
48
+ context = Employment.new(user1, user2)
49
+ ```
50
+
51
+ That ensures that `user1` will become (and have all the features of) the `employee` and `user2` will become (and have all the features of) the `boss`.
52
+
53
+ There are 2 things left to do:
54
+
55
+ 1. define behaviors for each role and
56
+ 2. define how you can trigger their actions
57
+
58
+ ## Defining behaviors for roles
59
+
60
+ Behaviors for your roles are easily defined just like you define a method. Provide your role a block and define methods there.
61
+
62
+ ```ruby
63
+ class Employment
64
+ extend Surrounded::Context
65
+
66
+ initialize :employee, :boss
67
+
68
+ role :boss
69
+
70
+ role :employee do
71
+ def work_weekend
72
+ if fed_up?
73
+ quit
74
+ else
75
+ schedule_weekend_work
76
+ end
77
+ end
78
+
79
+ def quit
80
+ say("I'm sick of this place, #{boss.name}!")
81
+ stomp
82
+ throw_papers
83
+ say("I quit!")
84
+ end
85
+
86
+ def schedule_weekend_work
87
+ # ...
88
+ end
89
+ end
90
+ end
91
+ ```
92
+
93
+ If any of your roles don't have special behaviors, like `boss`, you don't need to specify it. Your `initialize` setup will handle assiging who's who when this context is used.
94
+
95
+ ```ruby
96
+ class Employment
97
+ extend Surrounded::Context
98
+
99
+ initialize :employee, :boss
100
+
101
+ role :employee do
102
+ #...
103
+ end
104
+ end
105
+ ```
106
+
107
+ ## Triggering interactions
108
+
109
+ You'll need to define way to trigger these behaviors to occur so that you can use them.
110
+
111
+ ```ruby
112
+ context = Employment.new(user1, user2)
113
+
114
+ context.plan_weekend_work
115
+ ```
116
+
117
+ The method you need is defined as an instance method in your context, but before that method will work as expected you'll need to mark it as a trigger.
118
+
119
+ ```ruby
120
+ class Employment
121
+ extend Surrounded::Context
122
+
123
+ initialize :employee, :boss
124
+
125
+ def plan_weekend_work
126
+ employee.work_weekend
127
+ end
128
+ trigger :plan_weekend_work
129
+
130
+ role :employee do
131
+ #...
132
+ end
133
+ end
134
+ ```
135
+
136
+ Trigger methods are different from regular instance methods in that they apply behaviors from the roles to the role players.
137
+ A regular instance method just does what you define. But a trigger will make your role players come alive with their behaviors.
138
+
139
+ There's one last thing to make this work.
140
+
141
+ ## Getting your role players ready
142
+
143
+ You'll need to include `Surrounded` in the classes of objects which will be role players in your context.
144
+
145
+ It's as easy as:
146
+
147
+ ```ruby
148
+ class User
149
+ include Surrounded
150
+
151
+ # ...
152
+ end
153
+ ```
154
+
155
+ This gives each of the objects the ability to understand its context and direct access to other objects in the context.
156
+
157
+ ## Why is this valuable?
158
+
159
+ By creating environments which encapsulate roles and all necessary behaviors, you will be better able to isolate the logic of your system. A `user` in your system doesn't have all possible behaviors defined in its class, it gains the behaviors only when they are necessary.
160
+
161
+ The objects that interact have their behaviors defined and available right where they are needed. Implementation is in proximity to necessity. The behaviors you need for each role player are highly cohesive and are coupled to their use rather than being coupled to the class of an object which might use them at some point.
162
+
163
+ # Deeper Dive
164
+
165
+ ## Create encapsulated environments for your objects.
22
166
 
23
167
  Typical initialization of an environment, or a Context in DCI, has a lot of code. For example:
24
168
 
25
169
  ```ruby
26
- class MyEnvironment
170
+ class Employment
27
171
 
28
172
  attr_reader :employee, :boss
29
173
  private :employee, :boss
@@ -38,7 +182,7 @@ class MyEnvironment
38
182
  end
39
183
  ```
40
184
 
41
- This code allows the MyEnvironment class to create instances where it will have an `employee` and a `boss` role internally. These are set to `attr_reader`s and are made private.
185
+ This code allows the Employment class to create instances where it will have an `employee` and a `boss` role internally. These are set to `attr_reader`s and are made private.
42
186
 
43
187
  The `employee` is extended with behaviors defined in the `Employee` module, and in this case there's no extra stuff for the `boss` so it doesn't get extended with anything.
44
188
 
@@ -47,7 +191,7 @@ Most of the time you'll follow a pattern like this. Some objects will get extra
47
191
  By adding `Surrounded::Context` you can shortcut all this work.
48
192
 
49
193
  ```ruby
50
- class MyEnvironment
194
+ class Employment
51
195
  extend Surrounded::Context
52
196
 
53
197
  initialize(:employee, :boss)
@@ -67,7 +211,7 @@ _I don't want to use modules. Can't I use something like SimpleDelegator?_
67
211
  Well, it just so happens that you can. This code will work just fine:
68
212
 
69
213
  ```ruby
70
- class MyEnvironment
214
+ class Employment
71
215
  extend Surrounded::Context
72
216
 
73
217
  initialize(:employee, :boss)
@@ -83,7 +227,7 @@ Instead of extending the `employee` object, Surrounded will run `Employee.new(em
83
227
  But the syntax can be even simpler than that if you want.
84
228
 
85
229
  ```ruby
86
- class MyEnvironment
230
+ class Employment
87
231
  extend Surrounded::Context
88
232
 
89
233
  initialize(:employee, :boss)
@@ -97,7 +241,7 @@ end
97
241
  By default, this code will create a module for you named `Employee`. If you want to use a wrapper, you can do this:
98
242
 
99
243
  ```ruby
100
- class MyEnvironment
244
+ class Employment
101
245
  extend Surrounded::Context
102
246
 
103
247
  initialize(:employee, :boss)
@@ -111,7 +255,7 @@ end
111
255
  But if you're making changes and you decide to move from a module to a wrapper or from a wrapper to a module, you'll need to change that method call. Instead, you could just tell it which type of role to use:
112
256
 
113
257
  ```ruby
114
- class MyEnvironment
258
+ class Employment
115
259
  extend Surrounded::Context
116
260
 
117
261
  initialize(:employee, :boss)
@@ -148,17 +292,17 @@ Now the `User` instances will be able to implicitly access objects in their envi
148
292
 
149
293
  Via `method_missing` those `User` instances can access a `context` object it stores in an internal collection.
150
294
 
151
- Inside of the `MyEnvironment` context we saw above, the `employee` and `boss` objects are instances of `User` for this example.
295
+ Inside of the `Employment` context we saw above, the `employee` and `boss` objects are instances of `User` for this example.
152
296
 
153
297
  Because the `User` class includes `Surrounded`, the instances of that class will be able to access other objects in the same context implicitly.
154
298
 
155
299
  Let's make our context look like this:
156
300
 
157
301
  ```ruby
158
- class MyEnvironment
302
+ class Employment
159
303
  # other stuff from above is still here...
160
304
 
161
- def shove_it
305
+ def plan_weekend_work
162
306
  employee.quit
163
307
  end
164
308
 
@@ -173,7 +317,7 @@ class MyEnvironment
173
317
  end
174
318
  ```
175
319
 
176
- What's happening in there is that when the `shove_it` method is called on the instance of `MyEnvironment`, the `employee` has the ability to refer to `boss` because it is in the same context, e.g. the same environment.
320
+ What's happening in there is that when the `plan_weekend_work` method is called on the instance of `Employment`, the `employee` has the ability to refer to `boss` because it is in the same context, e.g. the same environment.
177
321
 
178
322
  The behavior defined in the `Employee` module assumes that it may access other objects in it's local environment. The `boss` object, for example, is never explicitly passed in as an argument.
179
323
 
@@ -188,10 +332,10 @@ Your context will have methods of it's own which will trigger actions on the obj
188
332
  Here's an example of what we want:
189
333
 
190
334
  ```ruby
191
- class MyEnvironment
335
+ class Employment
192
336
  # other stuff from above is still here...
193
337
 
194
- def shove_it
338
+ def plan_weekend_work
195
339
  employee.store_context(self)
196
340
  employee.quit
197
341
  employee.remove_context
@@ -213,10 +357,10 @@ Now that the `employee` has a reference to the context, it won't blow up when it
213
357
  We saw how we were able to clear up a lot of that repetitive work with the `initialize` method, so this is how we do it here:
214
358
 
215
359
  ```ruby
216
- class MyEnvironment
360
+ class Employment
217
361
  # other stuff from above is still here...
218
362
 
219
- trigger :shove_it do
363
+ trigger :plan_weekend_work do
220
364
  employee.quit
221
365
  end
222
366
 
@@ -236,8 +380,8 @@ By using this `trigger` keyword, our block is the code we care about, but intern
236
380
  The context will also store the triggers so that you can, for example, provide details outside of the environment about what triggers exist.
237
381
 
238
382
  ```ruby
239
- context = MyEnvironment.new(current_user, the_boss)
240
- context.triggers #=> [:shove_it]
383
+ context = Employment.new(current_user, the_boss)
384
+ context.triggers #=> [:plan_weekend_work]
241
385
  ```
242
386
 
243
387
  You might find that useful for dynamically defining user interfaces.
@@ -247,16 +391,16 @@ Sometimes I'd rather not use this DSL, however. I want to just write regular met
247
391
  We can do that too. You'll need to opt in to this by specifying `trigger :your_method_name` for the methods you want to use.
248
392
 
249
393
  ```ruby
250
- class MyEnvironment
394
+ class Employment
251
395
  # other stuff from above is still here...
252
396
 
253
- def shove_it
397
+ def plan_weekend_work
254
398
  employee.quit
255
399
  end
256
- trigger :shove_it
400
+ trigger :plan_weekend_work
257
401
 
258
402
  # or in Ruby 2.x
259
- trigger def shove_it
403
+ trigger def plan_weekend_work
260
404
  employee.quit
261
405
  end
262
406
 
@@ -284,30 +428,30 @@ Fortunately, you can make it easy.
284
428
  By running `protect_triggers` you'll be able to define when triggers may or may not be run. You can still run them, but they'll raise an error. Here's an example.
285
429
 
286
430
  ```ruby
287
- class MyEnvironment
431
+ class Employment
288
432
  extend Surrounded::Context
289
433
  protect_triggers
290
434
 
291
- def shove_it
435
+ def plan_weekend_work
292
436
  employee.quit
293
437
  end
294
- trigger :shove_it
438
+ trigger :plan_weekend_work
295
439
 
296
- disallow :shove_it do
297
- employee.bank_balance < 100
440
+ disallow :plan_weekend_work do
441
+ employee.bank_balance > 1000000
298
442
  end
299
443
  end
300
444
  ```
301
445
 
302
- Then, when the employee role's `bank_balance` is less than `100`, the available triggers won't include `:shove_it`.
446
+ Then, when the employee role's `bank_balance` is greater than `1000000`, the available triggers won't include `:plan_weekend_work`.
303
447
 
304
448
  You can compare the instance of the context by listing `all_triggers` and `triggers` to see what could be possible and what's currently possible.
305
449
 
306
450
  Alternatively, if you just want to define your own methods without the DSL using `disallow`, you can just follow the pattern of `disallow_#{method_name}?` when creating your own protection.
307
451
 
308
- In fact, that's exactly what happens with the `disallow` keyword. After using it here, we'd have a `disallow_shove_it?` method defined.
452
+ In fact, that's exactly what happens with the `disallow` keyword. After using it here, we'd have a `disallow_plan_weekend_work?` method defined.
309
453
 
310
- If you call the disallowed trigger directly, you'll raise a `MyEnvironment::AccessError` exception and the code in your trigger will not be run. You may rescue from that or you may rescue from `Surrounded::Context::AccessError` although you should prefer to use the error name from your own class.
454
+ If you call the disallowed trigger directly, you'll raise a `Employment::AccessError` exception and the code in your trigger will not be run. You may rescue from that or you may rescue from `Surrounded::Context::AccessError` although you should prefer to use the error name from your own class.
311
455
 
312
456
  ## Restricting return values
313
457
 
@@ -316,7 +460,7 @@ _Tell, Don't Ask_ style programming can better be enforced by following East-ori
316
460
  Here's how you enforce it:
317
461
 
318
462
  ```ruby
319
- class MyEnvironment
463
+ class Employment
320
464
  extend Surrounded::Context
321
465
  east_oriented_triggers
322
466
  end
@@ -433,61 +577,6 @@ class Organization
433
577
  end
434
578
  ```
435
579
 
436
- ## Policies for the application of role methods
437
-
438
- There are 2 approaches to applying new behavior to your objects.
439
-
440
- By default your context will add methods to an object before a trigger is run
441
- and behaviors will be removed after the trigger is run.
442
-
443
- Alternatively you may set the behaviors to be added during the initialize method
444
- of your context.
445
-
446
- Here's how it works:
447
-
448
- ```ruby
449
- class ActivatingAccount
450
- extend Surrounded::Context
451
-
452
- apply_roles_on(:trigger) # this is the default
453
- # apply_roles_on(:initialize) # set this to apply behavior from the start
454
-
455
- initialize(:activator, :account)
456
-
457
- role :activator do
458
- def some_behavior; end
459
- end
460
-
461
- def non_trigger_method
462
- activator.some_behavior # not available unless you apply roles on initialize
463
- end
464
-
465
- trigger :some_trigger_method do
466
- activator.some_behavior # always available
467
- end
468
- end
469
- ```
470
-
471
- _Why are those options there?_
472
-
473
- When you initialize a context and apply behavior at the same time, you'll need
474
- to remove that behavior. For example, if you are using Casting AND you apply roles on initialize:
475
-
476
- ```ruby
477
- context = ActiviatingAccount.new(current_user, Account.find(123))
478
- context.do_something
479
- current_user.some_behavior # this method is still available
480
- current_user.uncast # you'll have to manually cleanup
481
- ```
482
-
483
- But if you go with the default and apply behaviors on trigger, your roles will be cleaned up automatically:
484
-
485
- ```ruby
486
- context = ActiviatingAccount.new(current_user, Account.find(123))
487
- context.do_something
488
- current_user.some_behavior # NoMethodError
489
- ```
490
-
491
580
  ## Overview in code
492
581
 
493
582
  Here's a view of the possibilities in code.
@@ -498,9 +587,6 @@ Surrounded::Context.default_role_type = :module # also :wrap, :wrapper, or :inte
498
587
 
499
588
  class ActiviatingAccount
500
589
  extend Surrounded::Context
501
-
502
- apply_roles_on(:trigger) # this is the default
503
- # apply_roles_on(:initialize) # set this to apply behavior from the start
504
590
 
505
591
  # set the default role type only for this class
506
592
  self.default_role_type = :module # also :wrap, :wrapper, or :interface
@@ -545,9 +631,9 @@ class ActiviatingAccount
545
631
  # if you use a regular method and want to use context-specific behavior,
546
632
  # you must handle storing the context yourself:
547
633
  def regular_method
548
- apply_roles # handles the adding of all the roles and behaviors
634
+ apply_behaviors # handles the adding of all the roles and behaviors
549
635
  activator.some_behavior # behavior not available unless you apply roles on initialize
550
- remove_roles # handles the removal of all roles and behaviors
636
+ remove_behaviors # handles the removal of all roles and behaviors
551
637
  end
552
638
 
553
639
  trigger :some_trigger_method do