From 2426099721e03377bd050dfbd614421a5e8cb369 Mon Sep 17 00:00:00 2001 From: Jim Gay Date: Wed, 30 Oct 2013 13:26:08 -0700 Subject: [PATCH] Create gh-pages branch via GitHub --- index.html | 463 ++++++++++++++++++++++++++++----------- javascripts/scale.fix.js | 17 ++ params.json | 2 +- stylesheets/styles.css | 255 +++++++++++++++++++++ 4 files changed, 608 insertions(+), 129 deletions(-) create mode 100644 javascripts/scale.fix.js create mode 100644 stylesheets/styles.css diff --git a/index.html b/index.html index 91f3c12..26fbf3b 100644 --- a/index.html +++ b/index.html @@ -1,149 +1,200 @@ - + - + - - - - + Surrounded by saturnflyer + + + + - Surrounded by saturnflyer - -
-
+
+
+

Surrounded

+

Gives an object implicit access to other objects in it's environment.

-
-

Surrounded

-

Gives an object implicit access to other objects in it's environment.

-
+

View the Project on GitHub saturnflyer/surrounded

-
- Download .zip - Download .tar.gz - View on GitHub -
-
+ +
+
+

+Surrounded +

-
-

-Surrounded

+

+Bring your own complexity

Build Status Code Climate Coverage Status Gem Version

-

-Create encapsulated environments for your objects.

+

+Surrounded aims to make things simple and get out of your way.

-

Keep the distraction of other features out of your way. Write use cases and focus on just the business logic

+

Most of what you care about is defining the behavior of objects. How they interact is important. +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.

+ +

There are two main parts to this library.

+ +
    +
  1. +Surrounded gives objects an implicit awareness of other objects in their environments.
  2. +
  3. +Surrounded::Context helps you create objects which encapsulate other objects. These are the environments.
  4. +

First, take a look at creating contexts. This is where you'll spend most of your time.

-Usage

+Easily create encapsulated environments for your objects. -

Add Surrounded to your objects to give them awareness of other objects.

+

Typical initialization of an environment, or a Context in DCI, has a lot of code. For example:

-
class User
-  include Surrounded
+
class MyEnvironment
+
+  attr_reader :employee, :boss
+  private :employee, :boss
+  def initialize(employee, boss)
+    @employee = employee.extend(Employee)
+    @boss = boss
+  end
+
+  module Employee
+    # extra behavior here...
+  end
 end
 
-

Now your user instances will be able to get objects in their environment.

- -

What environment!? I don't get it.

+

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_readers and are made private.

-

I didn't explain that yet.

+

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.

-

You can make an object which contains other objects. It acts as an environment -and objects inside should have knowledge of the other objects in the environment. -Take a breath, because there's a lot going on.

+

Most of the time you'll follow a pattern like this. Some objects will get extra behavior and some won't. The modules that you use to provide the behavior will match the names you use for the roles to which you assign objects.

-

First, you extend a class with the appropriate module to turn it into an object environment:

+

By adding Surrounded::Context you can shortcut all this work.

-
class MyEnvironment
+
class MyEnvironment
   extend Surrounded::Context
+
+  initialize(:employee, :boss)
+
+  module Employee
+    # extra behavior here...
+  end
 end
 
-

Typical initialization of this environment has a lot of code. For example:

+

Surrounded gives you an initialize class method which does all the setup work for you.

-
class MyEnvironment
+

+Managing Roles

+ +

I don't want to use modules. Can't I use something like SimpleDelegator?

+ +

Well, it just so happens that you can. This code will work just fine:

+ +
class MyEnvironment
   extend Surrounded::Context
 
-  attr_reader :employee, :boss
-  private :employee, :boss
-  def initialize(employee, boss)
-    @employee = employee.extend(Employee)
-    @boss = boss
+  initialize(:employee, :boss)
+
+  class Employee < SimpleDelegator
+    # extra behavior here...
   end
+end
+
- module Employee +

Instead of extending the employee object, Surrounded will run Employee.new(employee) to create the wrapper for you. You'll need to include the Surrounded module in your wrapper, but we'll get to that.

+ +

But the syntax can be even simpler than that if you want.

+ +
class MyEnvironment
+  extend Surrounded::Context
+
+  initialize(:employee, :boss)
+
+  role :employee do
     # extra behavior here...
   end
 end
 
-

WTF was all that!?

+

By default, this code will create a module for you named Employee. If you want to use a wrapper, you can do this:

-

Relax. I'll explain.

+
class MyEnvironment
+  extend Surrounded::Context
 
-

When you create an instance of MyEnvironment it has certain objects inside. -Here we see that it has an employee and a boss. Inside the methods of the environment it's simpler and easier to write employee instead of @employee so we make them attr_readers. But we don't need these methods to be externally accessible so we set them to private.

+ initialize(:employee, :boss) -

Next, we want to add environment-specific behavior to the employee so we extend the object with the module Employee.

+ wrap :employee do + # extra behavior here... + end +end +
-

If you're going to be doing this a lot, it's painful. Here's what Surrounded does for you:

+

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:

-
class MyEnvironment
+
class MyEnvironment
   extend Surrounded::Context
 
   initialize(:employee, :boss)
 
-  module Employee
+  role :employee, :wrapper do
     # extra behavior here...
   end
 end
 
-

There! All that boilerplate code is cleaned up.

+

The default available types are :module, :wrap or :wrapper, and :interface. We'll get to interface below. The :wrap and :wrapper types are the same and they'll both create classes which inherit from SimpleDelegator and include Surrounded for you.

+ +

These are minor little changes which highlight how simple it is to use Surrounded.

+ +

Well... I want to use Casting so I get the benefit of modules without extending objects. Can I do that?

-

Notice that there's no Boss module. If a module of that name does not exist, the object passed into initialize simply won't gain any new behavior.

+

Yup. The ability to use Casting is built-in. If the objects you provide to your context respond to cast_as then Surrounded will use that.

-

OK. I think I get it, but what about the objects? How are they aware of their environment? Isn't that what this is supposed to do?

+

Ok. So is that it?

-

Yup. Ruby doesn't have a notion of a local environment, so we lean on method_missing to do the work for us.

+

There's a lot more. Let's look at the individual objects and what they need for this to be valuable...

-
class User
+

+Objects' access to their environments

+ +

Add Surrounded to your objects to give them awareness of other objects.

+ +
class User
   include Surrounded
 end
 
-

With that, all instances of User have implicit access to their surroundings.

+

Now the User instances will be able to implicitly access objects in their environment.

-

Yeah... How?

+

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 a @__surroundings__ collection. I didn't mention how the context is set, however.

+

Inside of the MyEnvironment context we saw above, the employee and boss objects are instances of User for this example.

-

Your environment will have methods of it's own that will trigger actions on the objects inside, but we need those trigger methods to set the environment instance as the current context so that the objects it contains can access them.

+

Because the User class includes Surrounded, the instances of that class will be able to access other objects in the same context implicitly.

-

Here's an example of what we want:

+

Let's make our context look like this:

-
class MyEnvironment
+
class MyEnvironment
   # other stuff from above is still here...
 
   def shove_it
-    employee.store_context(self)
     employee.quit
-    employee.remove_context
   end
 
-  module Employee
+  role :employee do
     def quit
       say("I'm sick of this place, #{boss.name}!")
       stomp
@@ -154,53 +205,183 @@ 

end

-

What's happening in there is that when the shove_it method is called, the current environment object is stored as the context.

+

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.

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.

-

WTF!? That's insane!

+

What Surrounded does for us is to make the relationship between objects and gives them the ability to access each other. Adding new or different roles to the context now only requires that we add them to the context and nothing else. No explicit references must be passed to each individual method. The objects are aware of the other objects around them and can refer to them by their role name.

-

I thought so too, at first. But continually passing references assumes there's no relationship between objects in that method. What Surrounded does for us is to make the relationship between objects and gives them the ability to access each other.

+

I didn't mention how the context is set, however.

-

This simple example may seem trivial, but the more contextual code you have the more cumbersome passing references becomes. By moving knowledge to the local environment, you're free to make changes to the procedures without the need to alter method signatures with new refrences or the removal of unused ones.

+

+Tying objects together

-

By using Surrounded::Context you are declaring a relationship between the objects inside.

+

Your context will have methods of it's own which will trigger actions on the objects inside, but we need those trigger methods to set the accessible context for each of the contained objects.

-

Because all the behavior is defined internally and only relevant internally, those relationships don't exist outside of the environment.

+

Here's an example of what we want:

-

OK. I think I understand. So I can change business logic just by changing the procedures and the objects. I don't need to adjust arguments for a new requirement. That's kind of cool!

+
class MyEnvironment
+  # other stuff from above is still here...
 
-

Damn right.

+ def shove_it + employee.store_context(self) + employee.quit + employee.remove_context + end -

But you don't want to continually set those context details, do you?

+ role :employee do + def quit + say("I'm sick of this place, #{boss.name}!") + stomp + throw_papers + say("I quit!") + end + end +end +
-

No. That's annoying.

+

Now that the employee has a reference to the context, it won't blow up when it hits boss inside that quit method.

-

Yeah. Instead, it would be easier to have this library do the work for us. -Here's what you can do:

+

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:

-
class MyEnvironment
-  # the other code from above...
+
class MyEnvironment
+  # other stuff from above is still here...
 
   trigger :shove_it do
     employee.quit
   end
+
+  role :employee do
+    def quit
+      say("I'm sick of this place, #{boss.name}!")
+      stomp
+      throw_papers
+      say("I quit!")
+    end
+  end
 end
 
-

By using this trigger keyword, our block is the code we care about, but internally the method is written to set the @__surroundings__ collection.

- -

Hmm. I don't like having to do that.

+

By using this trigger keyword, our block is the code we care about, but internally the method is created to first set all the objects' current contexts.

-

Me either. I'd rather just use def but getting automatic code for setting the context is really convenient. -It also allows us to store the triggers so that you can, for example, provide details outside of the environment about what triggers exist.

+

The context will also store the triggers so that you can, for example, provide details outside of the environment about what triggers exist.

-
context = MyEnvironment.new(current_user, the_boss)
+
context = MyEnvironment.new(current_user, the_boss)
 context.triggers #=> [:shove_it]
 

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.

+ +

We can do that too. You'll need to opt in to this by specifying set_methods_as_triggers for the context class.

+ +
class MyEnvironment
+  # other stuff from above is still here...
+
+  set_methods_as_triggers
+
+  def shove_it
+    employee.quit
+  end
+
+  role :employee do
+    def quit
+      say("I'm sick of this place, #{boss.name}!")
+      stomp
+      throw_papers
+      say("I quit!")
+    end
+  end
+end
+
+ +

This will allow you to write methods like you normally would. They are aliased internally with a prefix and the method name that you use is rewritten to add and remove the context for the objects in this context. The public API of your class remains the same, but the extra feature of wrapping your method is handled for you.

+ +

This will treat all instance methods defined on your context the same way, so be aware of that.

+ +

+Where roles exist

+ +

By using Surrounded::Context you are declaring a relationship between the objects inside playing your defined roles.

+ +

Because all the behavior is defined internally and only relevant internally, those relationships don't exist outside of the environment.

+ +

Surrounded makes all of your role modules and classes private constants. It's not a good idea to try to reuse behavior defined for one context in another area.

+ +

+The role DSL

+ +

Using the role method to define modules and classes takes care of the setup for you. This way you can swap between implementations:

+ +
+  # this uses modules
+  role :source do
+    def transfer
+      self.balance -= amount
+      destination.balance += amount
+      self
+    end
+  end
+
+  # this uses SimpleDelegator and Surrounded
+  role :source, :wrap do
+    def transfer
+      self.balance -= amount
+      destination.balance += amount
+      __getobj__
+    end
+  end
+
+  # this uses a special interface object which pulls
+  # methods from a module and applies them to your object.
+  role :source, :interface do
+    def transfer
+      self.balance -= amount
+      destination.balance += amount
+      self
+    end
+  end
+
+ +

The :interface option is a special object which has all of its methods removed (excepting __send__ and object_id) so that other methods will be pulled from the ones that you define, or from the object it attempts to proxy.

+ +

Notice that the :interface allows you to return self whereas the :wrap acts more like a wrapper and forces you to deal with that shortcoming by using it's wrapped-object-accessor method: __getobj__.

+ +

If you'd like to choose one and use it all the time, you can set the default:

+ +
class MoneyTransfer
+  extend Surrounded::Context
+
+  self.default_role_type = :interface # also :wrap, :wrapper, or :module
+
+  role :source do
+    def transfer
+      self.balance -= amount
+      destination.balance += amount
+      self
+    end
+  end
+end
+
+ +

Or, if you like, you can choose the default for your entire project:

+ +
Surrounded::Context.default_role_type = :interface
+
+class MoneyTransfer
+  extend Surrounded::Context
+
+  role :source do
+    def transfer
+      self.balance -= amount
+      destination.balance += amount
+      self
+    end
+  end
+end
+
+

Policies for the application of role methods

@@ -214,7 +395,7 @@

Here's how it works:

-
class ActiviatingAccount
+
class ActiviatingAccount
   extend Surrounded::Context
 
   apply_roles_on(:trigger) # this is the default
@@ -222,7 +403,7 @@ 

initialize(:activator, :account) - module Activator + role :activator do def some_behavior; end end @@ -241,7 +422,7 @@

When you initialize a context and apply behavior at the same time, you'll need to remove that behavior. For example, if you are using Casting AND you apply roles on initialize:

-
context = ActiviatingAccount.new(current_user, Account.find(123))
+
context = ActiviatingAccount.new(current_user, Account.find(123))
 context.do_something
 current_user.some_behavior # this method is still available
 current_user.uncast # you'll have to manually cleanup
@@ -249,49 +430,72 @@ 

But if you go with the default and apply behaviors on trigger, your roles will be cleaned up automatically:

-
context = ActiviatingAccount.new(current_user, Account.find(123))
+
context = ActiviatingAccount.new(current_user, Account.find(123))
 context.do_something
 current_user.some_behavior # NoMethodError
 

-How's the performance?

+Overview in code

-

I haven't really tested yet, but there are several ways you can add behavior to your objects.

+

Here's a view of the possibilities in code.

-

There are a few defaults built in.

+
# set default role type for *all* contexts in your program
+Surrounded::Context.default_role_type = :module # also :wrap, :wrapper, or :interface
 
-
    -
  1. If you define modules for the added behavior, the code will run object.extend(RoleInterface) -
  2. -
  3. If you are using casting, the code will run object.cast_as(RoleInterface) -
  4. -
  5. If you would rather use wrappers you can define classes and the code will run RoleInterface.new(object) and assumes that the new method takes 1 argument. You'll need to remember to include Surrounded in your classes, however.
  6. -
  7. If you want to use wrappers but would rather not muck about with including modules and whatnot, you can define them like this:
  8. -
class SomeContext
-  extend Surrounded::Context
-
-  initialize(:admin, :user)
-
-  wrap :admin do
-    # special methods defined here
-  end
-
+class ActiviatingAccount + extend Surrounded::Context -

The wrap method will create a class of the given name (Admin in this case) and will inherit from SimpleDelegator from the Ruby standard library and will include Surrounded.

+ apply_roles_on(:trigger) # this is the default + # apply_roles_on(:initialize) # set this to apply behavior from the start -

Lastly, there's a 5th option if you're using Ruby 2.x: interface.

+ set_methods_as_triggers # allows you to skip the 'trigger' dsl -

The interface method acts similarly to the wrap method in that it returns an object that is not actually the object you want. But an interface is different in that it will apply methods from a module instead of using methods defined in a SimpleDelegator subclass. How is that important? Well you are free to use things like instance variables in your methods because they will be executed in the context of the object. This is unlike methods in a SimpleDelegator where the wrapper maintains its own instance variables.

+ # set the default role type only for this class + self.default_role_type = :module # also :wrap, :wrapper, or :interface -

Which should I use?

+ initialize(:activator, :account) + + role :activator do # module by default + def some_behavior; end + end + + # role :activator, :module do + # def some_behavior; end + # end + # + # role :activator, :wrap do + # def some_behavior; end + # end + # + # role :activator, :interface do + # def some_behavior; end + # end + # + # use your own classes if you don't want SimpleDelegator + # class SomeSpecialRole + # include Surrounded # you must remember this + # # Surrounded assumes SomeSpecialRole.new(some_special_role) + # def initialize(...); + # # ... your code here + # end + # end + + # works as a trigger (assigning the current context) only if set_methods_as_triggers is set + def regular_method + activator.some_behavior # behavior not available unless you apply roles on initialize + end -

Start with the default and see how it goes, then try another approach and measure the changes.

+ trigger :some_trigger_method do + activator.some_behavior # behavior always available + end +end +

Dependencies

-

The dependencies are minimal. The plan is to keep it that way but allow you to configure things as you need.

+

The dependencies are minimal. The plan is to keep it that way but allow you to configure things as you need. The Triad project was written specifically to manage the mapping of roles and objects to the modules which contain the behaviors.

If you're using Casting, for example, Surrounded will attempt to use that before extending an object, but it will still work without it.

@@ -300,7 +504,7 @@

Add this line to your application's Gemfile:

-
gem 'surrounded'
+
gem 'surrounded'
 

And then execute:

@@ -313,6 +517,11 @@

$ gem install surrounded
 
+

+Installation for Rails

+ +

See surrounded-rails

+

Contributing

@@ -323,15 +532,13 @@

  • Push to the branch (git push origin my-new-feature)
  • Create new Pull Request
  • -

    - - - - -
    + +
    + + \ No newline at end of file diff --git a/javascripts/scale.fix.js b/javascripts/scale.fix.js new file mode 100644 index 0000000..87a40ca --- /dev/null +++ b/javascripts/scale.fix.js @@ -0,0 +1,17 @@ +var metas = document.getElementsByTagName('meta'); +var i; +if (navigator.userAgent.match(/iPhone/i)) { + for (i=0; i [:shove_it]\r\n```\r\n\r\nYou might find that useful for dynamically defining user interfaces.\r\n\r\n## Policies for the application of role methods\r\n\r\nThere are 2 approaches to applying new behavior to your objects.\r\n\r\nBy default your context will add methods to an object before a trigger is run\r\nand behaviors will be removed after the trigger is run.\r\n\r\nAlternatively you may set the behaviors to be added during the initialize method\r\nof your context.\r\n\r\nHere's how it works:\r\n\r\n```ruby\r\nclass ActiviatingAccount\r\n extend Surrounded::Context\r\n\r\n apply_roles_on(:trigger) # this is the default\r\n # apply_roles_on(:initialize) # set this to apply behavior from the start\r\n\r\n initialize(:activator, :account)\r\n\r\n module Activator\r\n def some_behavior; end\r\n end\r\n\r\n def non_trigger_method\r\n activator.some_behavior # not available unless you apply roles on initialize\r\n end\r\n\r\n trigger :some_trigger_method do\r\n activator.some_behavior # always available\r\n end\r\nend\r\n```\r\n\r\n_Why are those options there?_\r\n\r\nWhen you initialize a context and apply behavior at the same time, you'll need\r\nto remove that behavior. For example, if you are using Casting AND you apply roles on initialize:\r\n\r\n```ruby\r\ncontext = ActiviatingAccount.new(current_user, Account.find(123))\r\ncontext.do_something\r\ncurrent_user.some_behavior # this method is still available\r\ncurrent_user.uncast # you'll have to manually cleanup\r\n```\r\n\r\nBut if you go with the default and apply behaviors on trigger, your roles will be cleaned up automatically:\r\n\r\n```ruby\r\ncontext = ActiviatingAccount.new(current_user, Account.find(123))\r\ncontext.do_something\r\ncurrent_user.some_behavior # NoMethodError\r\n```\r\n\r\n## How's the performance?\r\n\r\nI haven't really tested yet, but there are several ways you can add behavior to your objects.\r\n\r\nThere are a few defaults built in.\r\n\r\n1. If you define modules for the added behavior, the code will run `object.extend(RoleInterface)`\r\n2. If you are using [casting](http://github.com/saturnflyer/casting), the code will run `object.cast_as(RoleInterface)`\r\n3. If you would rather use wrappers you can define classes and the code will run `RoleInterface.new(object)` and assumes that the `new` method takes 1 argument. You'll need to remember to `include Surrounded` in your classes, however.\r\n4. If you want to use wrappers but would rather not muck about with including modules and whatnot, you can define them like this:\r\n\r\n```\r\nclass SomeContext\r\n extend Surrounded::Context\r\n\r\n initialize(:admin, :user)\r\n\r\n wrap :admin do\r\n # special methods defined here\r\n end\r\n```\r\n\r\nThe `wrap` method will create a class of the given name (`Admin` in this case) and will inherit from `SimpleDelegator` from the Ruby standard library _and_ will `include Surrounded`.\r\n\r\nLastly, there's a 5th option if you're using Ruby 2.x: `interface`.\r\n\r\nThe `interface` method acts similarly to the `wrap` method in that it returns an object that is not actually the object you want. But an `interface` is different in that it will apply methods from a module instead of using methods defined in a SimpleDelegator subclass. How is that important? Well you are free to use things like instance variables in your methods because they will be executed in the context of the object. This is unlike methods in a SimpleDelegator where the wrapper maintains its own instance variables.\r\n\r\n_Which should I use?_\r\n\r\nStart with the default and see how it goes, then try another approach and measure the changes.\r\n\r\n## Dependencies\r\n\r\nThe dependencies are minimal. The plan is to keep it that way but allow you to configure things as you need.\r\n\r\nIf 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.\r\n\r\n## Installation\r\n\r\nAdd this line to your application's Gemfile:\r\n\r\n```ruby\r\ngem 'surrounded'\r\n```\r\n\r\nAnd then execute:\r\n\r\n $ bundle\r\n\r\nOr install it yourself as:\r\n\r\n $ gem install surrounded\r\n\r\n## Contributing\r\n\r\n1. Fork it\r\n2. Create your feature branch (`git checkout -b my-new-feature`)\r\n3. Commit your changes (`git commit -am 'Add some feature'`)\r\n4. Push to the branch (`git push origin my-new-feature`)\r\n5. Create new Pull Request\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."} \ No newline at end of file +{"name":"Surrounded","tagline":"Gives an object implicit access to other objects in it's environment.","body":"# ![Surrounded](http://saturnflyer.github.io/surrounded/images/surrounded.png \"Surrounded\")\r\n## Bring your own complexity\r\n\r\n[![Build Status](https://travis-ci.org/saturnflyer/surrounded.png?branch=master)](https://travis-ci.org/saturnflyer/surrounded)\r\n[![Code Climate](https://codeclimate.com/github/saturnflyer/surrounded.png)](https://codeclimate.com/github/saturnflyer/surrounded)\r\n[![Coverage Status](https://coveralls.io/repos/saturnflyer/surrounded/badge.png)](https://coveralls.io/r/saturnflyer/surrounded)\r\n[![Gem Version](https://badge.fury.io/rb/surrounded.png)](http://badge.fury.io/rb/surrounded)\r\n\r\n# Surrounded aims to make things simple and get out of your way.\r\n\r\nMost of what you care about is defining the behavior of objects. How they interact is important.\r\nThe 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.\r\n\r\nThere are two main parts to this library. \r\n\r\n1. `Surrounded` gives objects an implicit awareness of other objects in their environments.\r\n2. `Surrounded::Context` helps you create objects which encapsulate other objects. These *are* the environments.\r\n\r\nFirst, take a look at creating contexts. This is where you'll spend most of your time.\r\n\r\n## Easily create encapsulated environments for your objects.\r\n\r\nTypical initialization of an environment, or a Context in DCI, has a lot of code. For example:\r\n\r\n```ruby\r\nclass MyEnvironment\r\n\r\n attr_reader :employee, :boss\r\n private :employee, :boss\r\n def initialize(employee, boss)\r\n @employee = employee.extend(Employee)\r\n @boss = boss\r\n end\r\n\r\n module Employee\r\n # extra behavior here...\r\n end\r\nend\r\n```\r\n\r\nThis 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.\r\n\r\nThe `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.\r\n\r\nMost of the time you'll follow a pattern like this. Some objects will get extra behavior and some won't. The modules that you use to provide the behavior will match the names you use for the roles to which you assign objects.\r\n\r\nBy adding `Surrounded::Context` you can shortcut all this work.\r\n\r\n```ruby\r\nclass MyEnvironment\r\n extend Surrounded::Context\r\n \r\n initialize(:employee, :boss)\r\n\r\n module Employee\r\n # extra behavior here...\r\n end\r\nend\r\n```\r\n\r\nSurrounded gives you an `initialize` class method which does all the setup work for you.\r\n\r\n## Managing Roles\r\n\r\n_I don't want to use modules. Can't I use something like SimpleDelegator?_\r\n\r\nWell, it just so happens that you can. This code will work just fine:\r\n\r\n```ruby\r\nclass MyEnvironment\r\n extend Surrounded::Context\r\n \r\n initialize(:employee, :boss)\r\n\r\n class Employee < SimpleDelegator\r\n # extra behavior here...\r\n end\r\nend\r\n```\r\n\r\nInstead of extending the `employee` object, Surrounded will run `Employee.new(employee)` to create the wrapper for you. You'll need to include the `Surrounded` module in your wrapper, but we'll get to that.\r\n\r\nBut the syntax can be even simpler than that if you want.\r\n\r\n```ruby\r\nclass MyEnvironment\r\n extend Surrounded::Context\r\n \r\n initialize(:employee, :boss)\r\n\r\n role :employee do\r\n # extra behavior here...\r\n end\r\nend\r\n```\r\n\r\nBy default, this code will create a module for you named `Employee`. If you want to use a wrapper, you can do this:\r\n\r\n```ruby\r\nclass MyEnvironment\r\n extend Surrounded::Context\r\n \r\n initialize(:employee, :boss)\r\n\r\n wrap :employee do\r\n # extra behavior here...\r\n end\r\nend\r\n```\r\n\r\nBut 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:\r\n\r\n```ruby\r\nclass MyEnvironment\r\n extend Surrounded::Context\r\n \r\n initialize(:employee, :boss)\r\n\r\n role :employee, :wrapper do\r\n # extra behavior here...\r\n end\r\nend\r\n```\r\n\r\nThe default available types are `:module`, `:wrap` or `:wrapper`, and `:interface`. We'll get to `interface` below. The `:wrap` and `:wrapper` types are the same and they'll both create classes which inherit from SimpleDelegator _and_ include Surrounded for you.\r\n\r\nThese are minor little changes which highlight how simple it is to use Surrounded.\r\n\r\n_Well... I want to use [Casting](https://github.com/saturnflyer/casting) so I get the benefit of modules without extending objects. Can I do that?_\r\n\r\nYup. The ability to use Casting is built-in. If the objects you provide to your context respond to `cast_as` then Surrounded will use that.\r\n\r\n_Ok. So is that it?_\r\n\r\nThere's a lot more. Let's look at the individual objects and what they need for this to be valuable...\r\n\r\n## Objects' access to their environments\r\n\r\nAdd `Surrounded` to your objects to give them awareness of other objects.\r\n\r\n```ruby\r\nclass User\r\n include Surrounded\r\nend\r\n```\r\n\r\nNow the `User` instances will be able to implicitly access objects in their environment.\r\n\r\nVia `method_missing` those `User` instances can access a `context` object it stores in an internal collection. \r\n\r\nInside of the `MyEnvironment` context we saw above, the `employee` and `boss` objects are instances of `User` for this example.\r\n\r\nBecause the `User` class includes `Surrounded`, the instances of that class will be able to access other objects in the same context implicitly.\r\n\r\nLet's make our context look like this:\r\n\r\n```ruby\r\nclass MyEnvironment\r\n # other stuff from above is still here...\r\n\r\n def shove_it\r\n employee.quit\r\n end\r\n\r\n role :employee do\r\n def quit\r\n say(\"I'm sick of this place, #{boss.name}!\")\r\n stomp\r\n throw_papers\r\n say(\"I quit!\")\r\n end\r\n end\r\nend\r\n```\r\n\r\nWhat'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.\r\n\r\nThe 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.\r\n\r\nWhat `Surrounded` does for us is to make the relationship between objects and gives them the ability to access each other. Adding new or different roles to the context now only requires that we add them to the context and nothing else. No explicit references must be passed to each individual method. The objects are aware of the other objects around them and can refer to them by their role name.\r\n\r\nI didn't mention how the context is set, however.\r\n\r\n## Tying objects together\r\n\r\nYour context will have methods of it's own which will trigger actions on the objects inside, but we need those trigger methods to set the accessible context for each of the contained objects.\r\n\r\nHere's an example of what we want:\r\n\r\n```ruby\r\nclass MyEnvironment\r\n # other stuff from above is still here...\r\n\r\n def shove_it\r\n employee.store_context(self)\r\n employee.quit\r\n employee.remove_context\r\n end\r\n\r\n role :employee do\r\n def quit\r\n say(\"I'm sick of this place, #{boss.name}!\")\r\n stomp\r\n throw_papers\r\n say(\"I quit!\")\r\n end\r\n end\r\nend\r\n```\r\n\r\nNow that the `employee` has a reference to the context, it won't blow up when it hits `boss` inside that `quit` method.\r\n\r\nWe 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:\r\n\r\n```ruby\r\nclass MyEnvironment\r\n # other stuff from above is still here...\r\n\r\n trigger :shove_it do\r\n employee.quit\r\n end\r\n\r\n role :employee do\r\n def quit\r\n say(\"I'm sick of this place, #{boss.name}!\")\r\n stomp\r\n throw_papers\r\n say(\"I quit!\")\r\n end\r\n end\r\nend\r\n```\r\n\r\nBy using this `trigger` keyword, our block is the code we care about, but internally the method is created to first set all the objects' current contexts.\r\n\r\nThe context will also store the triggers so that you can, for example, provide details outside of the environment about what triggers exist.\r\n\r\n```ruby\r\ncontext = MyEnvironment.new(current_user, the_boss)\r\ncontext.triggers #=> [:shove_it]\r\n```\r\n\r\nYou might find that useful for dynamically defining user interfaces.\r\n\r\nSometimes I'd rather not use this DSL, however. I want to just write regular methods. \r\n\r\nWe can do that too. You'll need to opt in to this by specifying `set_methods_as_triggers` for the context class.\r\n\r\n```ruby\r\nclass MyEnvironment\r\n # other stuff from above is still here...\r\n \r\n set_methods_as_triggers\r\n\r\n def shove_it\r\n employee.quit\r\n end\r\n\r\n role :employee do\r\n def quit\r\n say(\"I'm sick of this place, #{boss.name}!\")\r\n stomp\r\n throw_papers\r\n say(\"I quit!\")\r\n end\r\n end\r\nend\r\n```\r\n\r\nThis will allow you to write methods like you normally would. They are aliased internally with a prefix and the method name that you use is rewritten to add and remove the context for the objects in this context. The public API of your class remains the same, but the extra feature of wrapping your method is handled for you.\r\n\r\nThis will treat all instance methods defined on your context the same way, so be aware of that.\r\n\r\n## Where roles exist\r\n\r\nBy using `Surrounded::Context` you are declaring a relationship between the objects inside playing your defined roles.\r\n\r\nBecause all the behavior is defined internally and only relevant internally, those relationships don't exist outside of the environment.\r\n\r\nSurrounded makes all of your role modules and classes private constants. It's not a good idea to try to reuse behavior defined for one context in another area.\r\n\r\n## The role DSL\r\n\r\nUsing the `role` method to define modules and classes takes care of the setup for you. This way you can swap between implementations:\r\n\r\n```ruby\r\n\r\n # this uses modules\r\n role :source do\r\n def transfer\r\n self.balance -= amount\r\n destination.balance += amount\r\n self\r\n end\r\n end\r\n\r\n # this uses SimpleDelegator and Surrounded\r\n role :source, :wrap do\r\n def transfer\r\n self.balance -= amount\r\n destination.balance += amount\r\n __getobj__\r\n end\r\n end\r\n\r\n # this uses a special interface object which pulls\r\n # methods from a module and applies them to your object.\r\n role :source, :interface do\r\n def transfer\r\n self.balance -= amount\r\n destination.balance += amount\r\n self\r\n end\r\n end\r\n```\r\n\r\nThe `:interface` option is a special object which has all of its methods removed (excepting `__send__` and `object_id`) so that other methods will be pulled from the ones that you define, or from the object it attempts to proxy.\r\n\r\nNotice that the `:interface` allows you to return `self` whereas the `:wrap` acts more like a wrapper and forces you to deal with that shortcoming by using it's wrapped-object-accessor method: `__getobj__`.\r\n\r\nIf you'd like to choose one and use it all the time, you can set the default:\r\n\r\n```ruby\r\nclass MoneyTransfer\r\n extend Surrounded::Context\r\n\r\n self.default_role_type = :interface # also :wrap, :wrapper, or :module\r\n\r\n role :source do\r\n def transfer\r\n self.balance -= amount\r\n destination.balance += amount\r\n self\r\n end\r\n end\r\nend\r\n```\r\n\r\nOr, if you like, you can choose the default for your entire project:\r\n\r\n```ruby\r\nSurrounded::Context.default_role_type = :interface\r\n\r\nclass MoneyTransfer\r\n extend Surrounded::Context\r\n\r\n role :source do\r\n def transfer\r\n self.balance -= amount\r\n destination.balance += amount\r\n self\r\n end\r\n end\r\nend\r\n```\r\n\r\n## Policies for the application of role methods\r\n\r\nThere are 2 approaches to applying new behavior to your objects.\r\n\r\nBy default your context will add methods to an object before a trigger is run\r\nand behaviors will be removed after the trigger is run.\r\n\r\nAlternatively you may set the behaviors to be added during the initialize method\r\nof your context.\r\n\r\nHere's how it works:\r\n\r\n```ruby\r\nclass ActiviatingAccount\r\n extend Surrounded::Context\r\n\r\n apply_roles_on(:trigger) # this is the default\r\n # apply_roles_on(:initialize) # set this to apply behavior from the start\r\n\r\n initialize(:activator, :account)\r\n\r\n role :activator do\r\n def some_behavior; end\r\n end\r\n\r\n def non_trigger_method\r\n activator.some_behavior # not available unless you apply roles on initialize\r\n end\r\n\r\n trigger :some_trigger_method do\r\n activator.some_behavior # always available\r\n end\r\nend\r\n```\r\n\r\n_Why are those options there?_\r\n\r\nWhen you initialize a context and apply behavior at the same time, you'll need\r\nto remove that behavior. For example, if you are using Casting AND you apply roles on initialize:\r\n\r\n```ruby\r\ncontext = ActiviatingAccount.new(current_user, Account.find(123))\r\ncontext.do_something\r\ncurrent_user.some_behavior # this method is still available\r\ncurrent_user.uncast # you'll have to manually cleanup\r\n```\r\n\r\nBut if you go with the default and apply behaviors on trigger, your roles will be cleaned up automatically:\r\n\r\n```ruby\r\ncontext = ActiviatingAccount.new(current_user, Account.find(123))\r\ncontext.do_something\r\ncurrent_user.some_behavior # NoMethodError\r\n```\r\n\r\n## Overview in code\r\n\r\nHere's a view of the possibilities in code.\r\n\r\n```ruby\r\n# set default role type for *all* contexts in your program\r\nSurrounded::Context.default_role_type = :module # also :wrap, :wrapper, or :interface\r\n\r\nclass ActiviatingAccount\r\n extend Surrounded::Context\r\n\r\n apply_roles_on(:trigger) # this is the default\r\n # apply_roles_on(:initialize) # set this to apply behavior from the start\r\n \r\n set_methods_as_triggers # allows you to skip the 'trigger' dsl\r\n \r\n # set the default role type only for this class\r\n self.default_role_type = :module # also :wrap, :wrapper, or :interface\r\n\r\n initialize(:activator, :account)\r\n\r\n role :activator do # module by default\r\n def some_behavior; end\r\n end\r\n\r\n # role :activator, :module do\r\n # def some_behavior; end\r\n # end\r\n #\r\n # role :activator, :wrap do\r\n # def some_behavior; end\r\n # end\r\n #\r\n # role :activator, :interface do\r\n # def some_behavior; end\r\n # end\r\n #\r\n # use your own classes if you don't want SimpleDelegator\r\n # class SomeSpecialRole\r\n # include Surrounded # you must remember this\r\n # # Surrounded assumes SomeSpecialRole.new(some_special_role)\r\n # def initialize(...);\r\n # # ... your code here\r\n # end\r\n # end\r\n\r\n # works as a trigger (assigning the current context) only if set_methods_as_triggers is set\r\n def regular_method\r\n activator.some_behavior # behavior not available unless you apply roles on initialize\r\n end\r\n\r\n trigger :some_trigger_method do\r\n activator.some_behavior # behavior always available\r\n end\r\nend\r\n```\r\n\r\n## Dependencies\r\n\r\nThe 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.\r\n\r\nIf 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.\r\n\r\n## Installation\r\n\r\nAdd this line to your application's Gemfile:\r\n\r\n```ruby\r\ngem 'surrounded'\r\n```\r\n\r\nAnd then execute:\r\n\r\n $ bundle\r\n\r\nOr install it yourself as:\r\n\r\n $ gem install surrounded\r\n \r\n## Installation for Rails\r\n\r\nSee [surrounded-rails](https://github.com/saturnflyer/surrounded-rails)\r\n\r\n## Contributing\r\n\r\n1. Fork it\r\n2. Create your feature branch (`git checkout -b my-new-feature`)\r\n3. Commit your changes (`git commit -am 'Add some feature'`)\r\n4. Push to the branch (`git push origin my-new-feature`)\r\n5. Create new Pull Request\r\n","google":"","note":"Don't delete this file! It's used internally to help with page regeneration."} \ No newline at end of file diff --git a/stylesheets/styles.css b/stylesheets/styles.css new file mode 100644 index 0000000..ff32137 --- /dev/null +++ b/stylesheets/styles.css @@ -0,0 +1,255 @@ +@import url(https://fonts.googleapis.com/css?family=Lato:300italic,700italic,300,700); + +body { + padding:50px; + font:14px/1.5 Lato, "Helvetica Neue", Helvetica, Arial, sans-serif; + color:#777; + font-weight:300; +} + +h1, h2, h3, h4, h5, h6 { + color:#222; + margin:0 0 20px; +} + +p, ul, ol, table, pre, dl { + margin:0 0 20px; +} + +h1, h2, h3 { + line-height:1.1; +} + +h1 { + font-size:28px; +} + +h2 { + color:#393939; +} + +h3, h4, h5, h6 { + color:#494949; +} + +a { + color:#39c; + font-weight:400; + text-decoration:none; +} + +a small { + font-size:11px; + color:#777; + margin-top:-0.6em; + display:block; +} + +.wrapper { + width:860px; + margin:0 auto; +} + +blockquote { + border-left:1px solid #e5e5e5; + margin:0; + padding:0 0 0 20px; + font-style:italic; +} + +code, pre { + font-family:Monaco, Bitstream Vera Sans Mono, Lucida Console, Terminal; + color:#333; + font-size:12px; +} + +pre { + padding:8px 15px; + background: #f8f8f8; + border-radius:5px; + border:1px solid #e5e5e5; + overflow-x: auto; +} + +table { + width:100%; + border-collapse:collapse; +} + +th, td { + text-align:left; + padding:5px 10px; + border-bottom:1px solid #e5e5e5; +} + +dt { + color:#444; + font-weight:700; +} + +th { + color:#444; +} + +img { + max-width:100%; +} + +header { + width:270px; + float:left; + position:fixed; +} + +header ul { + list-style:none; + height:40px; + + padding:0; + + background: #eee; + background: -moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%); + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd)); + background: -webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); + background: -o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); + background: -ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%); + background: linear-gradient(top, #f8f8f8 0%,#dddddd 100%); + + border-radius:5px; + border:1px solid #d2d2d2; + box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0; + width:270px; +} + +header li { + width:89px; + float:left; + border-right:1px solid #d2d2d2; + height:40px; +} + +header ul a { + line-height:1; + font-size:11px; + color:#999; + display:block; + text-align:center; + padding-top:6px; + height:40px; +} + +strong { + color:#222; + font-weight:700; +} + +header ul li + li { + width:88px; + border-left:1px solid #fff; +} + +header ul li + li + li { + border-right:none; + width:89px; +} + +header ul a strong { + font-size:14px; + display:block; + color:#222; +} + +section { + width:500px; + float:right; + padding-bottom:50px; +} + +small { + font-size:11px; +} + +hr { + border:0; + background:#e5e5e5; + height:1px; + margin:0 0 20px; +} + +footer { + width:270px; + float:left; + position:fixed; + bottom:50px; +} + +@media print, screen and (max-width: 960px) { + + div.wrapper { + width:auto; + margin:0; + } + + header, section, footer { + float:none; + position:static; + width:auto; + } + + header { + padding-right:320px; + } + + section { + border:1px solid #e5e5e5; + border-width:1px 0; + padding:20px 0; + margin:0 0 20px; + } + + header a small { + display:inline; + } + + header ul { + position:absolute; + right:50px; + top:52px; + } +} + +@media print, screen and (max-width: 720px) { + body { + word-wrap:break-word; + } + + header { + padding:0; + } + + header ul, header p.view { + position:static; + } + + pre, code { + word-wrap:normal; + } +} + +@media print, screen and (max-width: 480px) { + body { + padding:15px; + } + + header ul { + display:none; + } +} + +@media print { + body { + padding:0.4in; + font-size:12pt; + color:#444; + } +}