README.md in surrounded-0.9.11 vs README.md in surrounded-1.0.0
- old
+ new
@@ -1,12 +1,10 @@
# ![Surrounded](http://saturnflyer.github.io/surrounded/images/surrounded.png "Surrounded")
## Be in control of business logic.
[![Build Status](https://travis-ci.org/saturnflyer/surrounded.png?branch=master)](https://travis-ci.org/saturnflyer/surrounded)
[![Code Climate](https://codeclimate.com/github/saturnflyer/surrounded.png)](https://codeclimate.com/github/saturnflyer/surrounded)
-[![Coverage Status](https://coveralls.io/repos/saturnflyer/surrounded/badge.png)](https://coveralls.io/r/saturnflyer/surrounded)
-[![Gem Version](https://badge.fury.io/rb/surrounded.png)](http://badge.fury.io/rb/surrounded)
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.
## How to think about your objects
@@ -41,34 +39,34 @@
Here, you've specified the order when initializing so you can use it like this:
```ruby
user1 = User.find(1)
user2 = User.find(2)
-context = Employment.new(user1, user2)
+context = Employment.new(employee: user1, boss: user2)
```
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`.
There are 2 things left to do:
1. define behaviors for each role and
2. define how you can trigger their actions
-Currently initializing contexts does not require the use of keyword arguments, _but it will in the future_.
+Initializing contexts does not require the use of keyword arguments, but you may opt out.
-You should consider using explicit names when initialize now by using `keyword_initialize`:
+You should consider using explicit names when initialize now by using `initialize_without_keywords`:
```ruby
class Employment
extend Surrounded::Context
- keyword_initialize :employee, :boss
+ initialize_withou_keywords :employee, :boss
end
user1 = User.find(1)
user2 = User.find(2)
-context = Employment.new(employee: user1, boss: user2)
+context = Employment.new(user1, user2)
```
This will allow you to prepare your accessing code to use keywords.
If you need to override the initializer with additional work, you have the ability to use a block to be evaluated in the context of the initialized object.
@@ -133,11 +131,11 @@
## Triggering interactions
You'll need to define way to trigger these behaviors to occur so that you can use them.
```ruby
-context = Employment.new(user1, user2)
+context = Employment.new(employee: user1, boss: user2)
context.plan_weekend_work
```
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.
@@ -247,11 +245,11 @@
By adding `Surrounded::Context` you can shortcut all this work.
```ruby
class Employment
extend Surrounded::Context
-
+
initialize(:employee, :boss)
module Employee
# extra behavior here...
end
@@ -267,11 +265,11 @@
Well, it just so happens that you can. This code will work just fine:
```ruby
class Employment
extend Surrounded::Context
-
+
initialize(:employee, :boss)
class Employee < SimpleDelegator
# extra behavior here...
end
@@ -283,11 +281,11 @@
But the syntax can be even simpler than that if you want.
```ruby
class Employment
extend Surrounded::Context
-
+
initialize(:employee, :boss)
role :employee do
# extra behavior here...
end
@@ -297,11 +295,11 @@
By default, this code will create a module for you named `Employee`. If you want to use a wrapper, you can do this:
```ruby
class Employment
extend Surrounded::Context
-
+
initialize(:employee, :boss)
wrap :employee do
# extra behavior here...
end
@@ -311,11 +309,11 @@
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:
```ruby
class Employment
extend Surrounded::Context
-
+
initialize(:employee, :boss)
role :employee, :wrapper do
# extra behavior here...
end
@@ -344,11 +342,11 @@
end
```
Now the `User` instances will be able to implicitly access objects in their environment.
-Via `method_missing` those `User` instances can access a `context` object it stores in an internal collection.
+Via `method_missing` those `User` instances can access a `context` object it stores in an internal collection.
Inside of the `Employment` context we saw above, the `employee` and `boss` objects are instances of `User` for this example.
Because the `User` class includes `Surrounded`, the instances of that class will be able to access other objects in the same context implicitly.
@@ -440,11 +438,11 @@
context.triggers #=> [:plan_weekend_work]
```
You might find that useful for dynamically defining user interfaces.
-Sometimes I'd rather not use this DSL, however. I want to just write regular methods.
+Sometimes I'd rather not use this DSL, however. I want to just write regular methods.
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.
```ruby
class Employment
@@ -452,11 +450,11 @@
def plan_weekend_work
employee.quit
end
trigger :plan_weekend_work
-
+
# or in Ruby 2.x
trigger def plan_weekend_work
employee.quit
end
@@ -485,16 +483,16 @@
```ruby
class Employment
extend Surrounded::Context
protect_triggers
-
+
def plan_weekend_work
employee.quit
end
trigger :plan_weekend_work
-
+
disallow :plan_weekend_work do
employee.bank_balance > 1000000
end
end
```
@@ -625,27 +623,27 @@
```ruby
class Organization
extend Surrounded::Context
initialize_without_keywords :leader, :members
-
+
role :members do
# special behavior for the collection
end
-
+
role :member do
# special behavior to be applied to each member in the collection
- end
+ end
end
```
## Reusing context objects
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:
```ruby
-context = Employment.new(current_user, the_boss)
+context = Employment.new(employee: current_user, boss: the_boss)
context.rebind(employee: another_user, boss: someone_else) # same context, new players
```
## Background Processing
@@ -669,11 +667,11 @@
trigger_name = args.delete(:trigger)
job_class.new(args).send(trigger_name)
end
end
end
-ExpensiveCalculation.new(some_object, some_collection).send_to_background(:do_expensive_calculation)
+ExpensiveCalculation.new(leader: some_object, members: some_collection).send_to_background(:do_expensive_calculation)
```
The above example is merely pseudo-code to show how `initializer_arguments` can be used. Customize it according to your own needs.
## Overview in code
@@ -684,18 +682,18 @@
# set default role type for *all* contexts in your program
Surrounded::Context.default_role_type = :module # also :wrap, :wrapper, or :interface
class ActiviatingAccount
extend Surrounded::Context
-
+
# set the default role type only for this class
self.default_role_type = :module # also :wrap, :wrapper, or :interface
# shortcut initialization code
initialize(:activator, :account)
# or handle it yourself
- def initialize(activator, account)
+ def initialize(activator:, account:)
# this must be done to handle the mapping of roles to objects
# pass an array of arrays with role name symbol and the object for that role
map_roles([[:activator, activator],[:account, account]])
# or pass a hash
map_roles(:activator => activator, :account => account)
@@ -705,22 +703,25 @@
super
end
# these also must be done if you create your own initialize method.
# this is a shortcut for using attr_reader and private
private_attr_reader :activator, :account
-
+
# If you need to mix default initialzation and extra work use a block
initialize :activator, :account do
map_roles(:third_party => get_some_other_object)
+ # explicitly set a single role
+ map_role(:something_new, 'SomeRoleConstant', object_to_play_the_role)
end
+
# but remember to set the extra accessors:
- private_attr_reader :third_party
+ private_attr_reader :third_party, :something_new
- # initialize with keyword arguments
- keyword_initialize(:activator, :account)
- # this makes the following instance method signature with required keyword arguments
- def initialize(activator:, account:)
+ # initialize without keyword arguments
+ initialize_without_keywords(:activator, :account)
+ # this makes the following instance method signature with positional arguments
+ def initialize(activator, account)
# ...
end
# Handle method name collisions on role players against role names in the context
on_name_collision :raise # will raise your context namespaced error: ActiviatingAccount::NameCollisionError
@@ -754,11 +755,11 @@
# def initialize(...);
# # ... your code here
# end
# end
- # if you use a regular method and want to use context-specific behavior,
+ # if you use a regular method and want to use context-specific behavior,
# you must handle storing the context yourself:
def regular_method
apply_behaviors # handles the adding of all the roles and behaviors
activator.some_behavior # behavior not available unless you apply roles on initialize
ensure
@@ -769,58 +770,58 @@
# This trigger or the forward* methods are preferred for creating triggers.
trigger :some_trigger_method do
activator.some_behavior # behavior always available
end
-
+
trigger def some_other_trigger
activator.some_behavior # behavior always available
end
-
+
def regular_non_trigger
activator.some_behavior # behavior always available with the following line
end
trigger :regular_non_trigger # turns the method into a trigger
-
+
# create restrictions on what triggers may be used
protect_triggers # <-- this is required if you want to protect your triggers this way.
disallow :some_trigger_method do
# whatever conditional code for the instance of the context
end
# you could also use `guard` instead of `disallow`
-
+
# or define your own method without the `disallow` keyword
def disallow_some_trigger_method?
# whatever conditional code for the instance of the context
end
# Prefer using `disallow` because it will wrap role players in their roles for you;
# the `disallow_some_trigger_method?` defined above, does not.
-
+
# Create shortcuts for triggers as class methods
# so you can do ActiviatingAccount.some_trigger_method(activator, account)
# This will make all triggers shortcuts.
shortcut_triggers
# Alterantively, you could implement shortcuts individually:
def self.some_trigger_method(activator, account)
instance = self.new(activator, account)
instance.some_trigger_method
end
-
+
# Set triggers to always return the context object
# so you can enforce East-oriented style or Tell, Don't Ask
east_oriented_triggers
-
+
# Forward context instance methods as triggers to role players
forward_trigger :role_name, :method_name
forward_trigger :role_name, :method_name, :alternative_trigger_name_for_method_name
forward_triggers :role_name, :list, :of, :methods, :to, :forward
forwarding [:list, :of, :methods, :to, :forward] => :role_name
end
-# with keyword_initialize (will be changed to initialize)
+# with initialize (also keyword_initialize)
context = ActiviatingAccount.new(activator: some_object, account: some_account)
-# with initialize (this will be moved to initialize_without_keywords)
+# with initialize_without_keywords
context = ActiviatingAccount.new(some_object, some_account)
context.triggers # => lists a Set of triggers
# when using protect_triggers
context.triggers # => lists a Set of triggers which may currently be called
context.triggers # => lists a Set of all triggers (the same as if protect_triggers was _not_ used)
@@ -1070,10 +1071,10 @@
$ bundle
Or install it yourself as:
$ gem install surrounded
-
+
## Installation for Rails
See [surrounded-rails](https://github.com/saturnflyer/surrounded-rails)
## Contributing