dress_up 1.0.0a

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright (c) 2012 Morgan Brown
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # Dress Up [![Build Status](https://secure.travis-ci.org/mhgbrown/dress_up.png)](http://travis-ci.org/mhgbrown/dress_up)
2
+ Let's play dress-up! Dress Up allows you to specify named sets of method overrides that you can selectively enable and disable.
3
+
4
+ ## Compatibility
5
+ Dress Up relies on the Ruby 1.9.X ordering of Hash entries.
6
+
7
+ ## Example
8
+ Below is the Duck class that has defined two costumes: dog and robosoldier. The dog costume overrides the ```name=``` method to append "Dog" and the ```speak``` method to return "Woof!". The robosoldier costume overrides ```speak``` as well and adds a new method, ```terminations```.
9
+
10
+ class Duck
11
+ # include the Dress Up functionality
12
+ include DressUp::Interface
13
+
14
+ attr_accessor :name, :age
15
+
16
+ # define a dog costume
17
+ costume :dog, :name= => lambda {|name| @name = name + " Dog"}, :speak => "Woof!"
18
+ # define a robosoldier costume
19
+ costume :robosoldier, :speak => lambda {"I will terminate you! " + super()}, :terminations => 23
20
+
21
+ def initialize(name, age)
22
+ @name, @age = name, age
23
+ end
24
+
25
+ def speak
26
+ "Quack!"
27
+ end
28
+ end
29
+
30
+ When the dog costume is applied, it overrides the methods it specifies:
31
+
32
+ duck = Duck.new("Horace", 2)
33
+ duck.put_on(:dog)
34
+ duck.name = "George"
35
+ puts duck.name
36
+ => "George Dog"
37
+ puts duck.speak
38
+ => "Woof!"
39
+
40
+ When the robosoldier costume is applied, its overrides are merged with dog's overrides and reapplied.
41
+
42
+ duck.put_on(:robosoldier)
43
+ puts duck.speak
44
+ => "I will terminate you! Quack!"
45
+ puts duck.terminations
46
+ => 23
47
+
48
+ When the dog and robosoldier costumes are removed, their overrides are removed.
49
+
50
+ duck.take_off(:dog)
51
+ duck.name = "T1000"
52
+ puts duck.name
53
+ => "T1000"
54
+ duck.take_off(:robosoldier)
55
+ puts duck.speak
56
+ => "Quack!"
57
+ puts duck.terminations
58
+ => NoMethodError
59
+
60
+ All the costumes can be applied at once:
61
+
62
+ duck.dress_up
63
+ duck.name = "Bob"
64
+ puts duck.name
65
+ => "Bob Dog"
66
+ puts duck.terminations
67
+ => 23
68
+
69
+ They can also all be removed at once:
70
+
71
+ duck.dress_down
72
+ puts duck.speak
73
+ => "Quack!"
74
+
75
+ Access all of Duck's costumes:
76
+
77
+ Duck.closet
78
+ => {:dog => <DressUp::Costume ... >, ...}
79
+
80
+ Access a duck's current outfit:
81
+
82
+ duck.put_on(:dog)
83
+ duck.outfit
84
+ => <DressUp::Outfit ... >
85
+
86
+ ## Future Considerations
87
+ * Instead of merging overrides, just define overrides in sequence
88
+
89
+
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ desc "Run all examples"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
data/dress_up.gemspec ADDED
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "dress_up/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "dress_up"
7
+ s.version = DressUp::VERSION
8
+ s.authors = ["Morgan Brown"]
9
+ s.email = ["brown.mhg@gmail.com"]
10
+ s.homepage = "https://github.com/discom4rt/dress_up"
11
+ s.summary = "Enable and disable sets of method overrides"
12
+ s.description = "Dress Up allows you to specify named sets of method overrides that you can selectively enable and disable."
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_dependency "rake"
20
+ s.add_development_dependency "rspec"
21
+ end
@@ -0,0 +1,27 @@
1
+ module DressUp
2
+ # Costume represents a named set of method overrides.
3
+ class Costume
4
+
5
+ attr_reader :name, :overrides
6
+
7
+ # Create a new costume with the given name and overrides. Overrides are
8
+ # converted into lambdas if necessary.
9
+ def initialize(name, overrides={})
10
+ @name = name
11
+ @overrides = overrides.inject({}) do |hash, (method_name, value)|
12
+ unless value.respond_to? :call
13
+ hash[method_name] = lambda { value }
14
+ else
15
+ hash[method_name] = value
16
+ end
17
+ hash
18
+ end
19
+ end
20
+
21
+ # Retreive an override by its identifier
22
+ def [](override)
23
+ @overrides[override]
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module DressUp
2
+ # Errors holds DressUp specific errors.
3
+ module Errors
4
+
5
+ # UndefinedCostumeError is raised when an operation
6
+ # is performed with a costume that has not been defined.
7
+ class UndefinedCostumeError < StandardError
8
+
9
+ # The default error message highlights the absence
10
+ # of the costume
11
+ def initialize(costume)
12
+ super("#{costume} has not been defined")
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,84 @@
1
+ module DressUp
2
+ # The Interface module provides the methods to
3
+ # define, put on, and take off costumes.
4
+ module Interface
5
+ def self.included(base)
6
+ base.send :extend, ClassMethods
7
+ base.send :include, InstanceMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ # When extedended, define closet reader
12
+ def self.extended(base)
13
+ class << base
14
+ attr_reader :closet
15
+ end
16
+ base.instance_variable_set("@closet", {})
17
+ end
18
+
19
+ # Define a costume by giving it a name and a set of overrides
20
+ def costume(name, overrides={})
21
+ unless name.is_a?(Symbol) || name.is_a?(String)
22
+ raise "#{name.inspect} is not a valid costume name"
23
+ end
24
+
25
+ @closet ||= {}
26
+ if overrides.empty?
27
+ @closet[name]
28
+ else
29
+ @closet[name] = DressUp::Costume.new(name, overrides)
30
+ end
31
+ end
32
+ end
33
+
34
+ module InstanceMethods
35
+ # When included, define the outfit reader
36
+ def self.included(base)
37
+ base.class_eval do
38
+ attr_reader :outfit
39
+ end
40
+ end
41
+
42
+ # Apply the costume with the given name to this object and
43
+ # set up and outfit if necessary. If the costume does not exist,
44
+ # an error is raised.
45
+ def put_on(costume_name)
46
+ costume = self.class.closet[costume_name]
47
+ if costume
48
+ @outfit ||= DressUp::Outfit.new(self)
49
+ @outfit.apply(costume)
50
+ costume
51
+ else
52
+ raise DressUp::Errors::UndefinedCostumeError, costume_name
53
+ end
54
+ end
55
+
56
+ # Apply all the costumes defined for this object's class. Set up an
57
+ # oufit if necessary.
58
+ def dress_up
59
+ @outfit ||= DressUp::Outfit.new(self)
60
+ @outfit.apply(*self.class.closet.values)
61
+ end
62
+
63
+ # Remove the costume with the given name from this object and clear an
64
+ # existing outfit if necessary. If the costume does not exist, an
65
+ # error is raised.
66
+ def take_off(costume_name)
67
+ costume = self.class.closet[costume_name]
68
+ if costume
69
+ @outfit && @outfit.remove(costume)
70
+ @outfit = nil if @outfit && @outfit.empty?
71
+ else
72
+ raise DressUp::Errors::UndefinedCostumeError, costume_name
73
+ end
74
+ end
75
+
76
+ # Remove all the costumes defined for this object's class. Clear an
77
+ # existing outfit if necessary.
78
+ def dress_down
79
+ @outfit && @outfit.remove(*self.class.closet.values)
80
+ @outfit = nil if @outfit && @outfit.empty?
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,82 @@
1
+ module DressUp
2
+ # Outfit represents a set of costumes in use by a given object.
3
+ class Outfit
4
+
5
+ attr_reader :costumes, :getup, :object
6
+
7
+ # Create a new outfit with the given object. Method overrides
8
+ # provided by costumes associated with this outfit will
9
+ # be applied to this object.
10
+ def initialize(object)
11
+ @costumes = {}
12
+ @getup = {}
13
+ @object = object
14
+ end
15
+
16
+ # Apply the given costumes to this outfit's object. Costume overrides
17
+ # are applied in the order that costumes are put on.
18
+ def apply(*other_costumes)
19
+ change_getup do
20
+ other_costumes.each do |costume|
21
+ @costumes[costume.name] = costume.overrides
22
+ end
23
+ end
24
+ end
25
+
26
+ # Remove the given costumes from this outfit's object.
27
+ def remove(*other_costumes)
28
+ change_getup do
29
+ other_costumes.each do |costume|
30
+ @costumes.delete(costume.name)
31
+ end
32
+ end
33
+ end
34
+
35
+ # Determine if this outfit has no costumes.
36
+ def empty?
37
+ @costumes.empty?
38
+ end
39
+
40
+ private
41
+
42
+ # Change the getup of this outfit safely by removing
43
+ # all of the overrides, rebuilding the getup and finally
44
+ # reapplying the overrides. Takes a block in which it is assumed
45
+ # changes to the costumes will be made.
46
+ def change_getup(&block)
47
+ peel_down
48
+ yield if block_given?
49
+ rebuild
50
+ suit_up
51
+ end
52
+
53
+ # Rebuild the final getup from all the active costumes.
54
+ def rebuild
55
+ @getup = @costumes.inject({}) do |hash, (name, overrides)|
56
+ hash.merge!(overrides)
57
+ end
58
+ end
59
+
60
+ # Apply the getup (overrides) to the object.
61
+ def suit_up
62
+ @getup.each do |method_name, callable|
63
+ @object.define_singleton_method(method_name, &callable)
64
+ end
65
+ end
66
+
67
+ # Remove the overrides from the object.
68
+ def peel_down
69
+ @getup.each do |method_name, _|
70
+ remove_singleton_method(method_name)
71
+ end
72
+ end
73
+
74
+ # Remove a singleton method from the object. Does not completely
75
+ # undefine the method from the context.
76
+ def remove_singleton_method(name)
77
+ metaclass = @object.instance_eval "class << self; self; end"
78
+ metaclass.send(:remove_method, name)
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ module DressUp
2
+ VERSION = "1.0.0a"
3
+ end
data/lib/dress_up.rb ADDED
@@ -0,0 +1,8 @@
1
+ require "dress_up/version"
2
+ require "dress_up/interface"
3
+ require "dress_up/costume"
4
+ require "dress_up/outfit"
5
+ require "dress_up/errors"
6
+
7
+ module DressUp
8
+ end
@@ -0,0 +1,148 @@
1
+ require 'spec_helper'
2
+
3
+ describe DressUp do
4
+
5
+ before(:each) do
6
+ class Duck
7
+ include DressUp::Interface
8
+
9
+ attr_accessor :name, :age
10
+
11
+ def initialize(name, age)
12
+ @name, @age = name, age
13
+ end
14
+
15
+ def speak
16
+ "Quack!"
17
+ end
18
+ end
19
+ end
20
+
21
+ after(:each) do
22
+ Object.send(:remove_const, :Duck)
23
+ end
24
+
25
+ let(:duck) { Duck.new("Horace", 5) }
26
+
27
+ it "should provide a method to declare a costume" do
28
+ Duck.respond_to?(:costume).should == true
29
+ end
30
+
31
+ it "should raise an error if a costume is declared without a name" do
32
+ lambda { Duck.costume(:speak => "Woof!") }.should raise_error
33
+ end
34
+
35
+ describe "when some costumes have been declared" do
36
+ before(:each) do
37
+ Duck.costume(:dog, :speak => "Woof!", :name= => lambda {|value| @name = value + " Dog"})
38
+ Duck.costume(:robosoldier, :speak => "You will be terminated!", :kills => 57)
39
+ end
40
+
41
+ it "there should be a way to get the declared costumes individually" do
42
+ dog_costume = Duck.costume(:dog)
43
+ dog_costume.class.should == DressUp::Costume
44
+ dog_costume.name.should == :dog
45
+ dog_costume.overrides[:speak].call.should == "Woof!"
46
+ dog_costume.overrides[:name=].call("Joe").should == "Joe Dog"
47
+ end
48
+
49
+ it "there should be a way to get all the costumes" do
50
+ Duck.closet.length.should == 2
51
+ Duck.closet[:dog].class.should == DressUp::Costume
52
+ Duck.closet[:robosoldier].class.should == DressUp::Costume
53
+ end
54
+
55
+ it "should provide a method to put on a costume, which will override the specified costume methods to return the specified values" do
56
+ duck.put_on(:dog)
57
+ duck.speak.should == "Woof!"
58
+ duck.name = "Fluffy"
59
+ duck.name.should == "Fluffy Dog"
60
+ end
61
+
62
+ it "should return a costume when your put on a costume" do
63
+ duck.put_on(:dog).class.should == DressUp::Costume
64
+ end
65
+
66
+ it "should provide raise an error when you try to put on a costume that doesn't exist" do
67
+ lambda { duck.put_on(:cow) }.should raise_error(DressUp::Errors::UndefinedCostumeError)
68
+ end
69
+
70
+ it "should provide a way to take off a costume" do
71
+ duck.put_on(:dog)
72
+ duck.take_off(:dog)
73
+ duck.speak.should == "Quack!"
74
+ end
75
+
76
+ it "should return nothing if you try to take off a costume that is not on" do
77
+ duck.take_off(:dog).should == nil
78
+ end
79
+
80
+ it "should raise an error if you try to take off a costume that does not exist" do
81
+ lambda { duck.take_off(:cow) }.should raise_error(DressUp::Errors::UndefinedCostumeError)
82
+ end
83
+
84
+ it "should create a method supplied by the costume when it does not exist on the target object" do
85
+ Duck.costume(:robosoldier, :kills => 57)
86
+ duck.put_on(:robosoldier)
87
+ duck.kills.should == 57
88
+ end
89
+
90
+ it "should replace an old costume with a new costume of the same name" do
91
+ Duck.costume(:dog, :speak => "Bark!")
92
+ duck.take_off(:dog)
93
+ duck.put_on(:dog)
94
+ duck.speak.should == "Bark!"
95
+ duck.name = "Jeffrey"
96
+ duck.name.should == "Jeffrey"
97
+ end
98
+
99
+ describe "when a costume is already on" do
100
+ before(:each) do
101
+ duck.put_on(:dog)
102
+ end
103
+
104
+ # or should this somehow merge the return values?
105
+ it "any new costume that is put on should override the current costume's return values where they overlap" do
106
+ duck.put_on(:robosoldier)
107
+ duck.speak.should == "You will be terminated!"
108
+ duck.name = "T2600"
109
+ duck.name.should == "T2600 Dog"
110
+ duck.kills.should == 57
111
+ end
112
+
113
+ it "and then when the costume is taken off, the object's method should return what they originally did" do
114
+ duck.put_on(:robosoldier)
115
+ duck.take_off(:robosoldier)
116
+ duck.speak.should == "Woof!"
117
+ duck.respond_to?(:kills).should == false
118
+ end
119
+
120
+ it "should provide a way to access all the costumes that are on (an outfit)" do
121
+ duck.outfit.class.should == DressUp::Outfit
122
+ duck.outfit.costumes[:dog][:name=].should == Duck.closet[:dog][:name=]
123
+ duck.outfit.costumes[:dog][:speak].should == Duck.closet[:dog][:speak]
124
+ end
125
+ end
126
+
127
+ it "should return nil if you try to get an outfit with no costumes on" do
128
+ duck.outfit.should == nil
129
+ end
130
+
131
+ it "should provde a way to put on all the costumes" do
132
+ duck.dress_up
133
+ duck.speak.should == "You will be terminated!"
134
+ duck.name = "George"
135
+ duck.name.should == "George Dog"
136
+ duck.kills.should == 57
137
+ end
138
+
139
+ it "should provide a way to take off all costumes" do
140
+ duck.dress_up
141
+ duck.dress_down
142
+ duck.speak.should == "Quack!"
143
+ duck.name = "Daffy"
144
+ duck.name.should == "Daffy"
145
+ duck.respond_to?(:kills).should == false
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
5
+ require 'dress_up'
6
+
7
+ RSpec.configure do |config|
8
+ # nothing
9
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dress_up
3
+ version: !ruby/object:Gem::Version
4
+ hash: 58
5
+ prerelease: 5
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ - a
11
+ version: 1.0.0a
12
+ platform: ruby
13
+ authors:
14
+ - Morgan Brown
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2012-03-29 00:00:00 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rake
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: rspec
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ description: Dress Up allows you to specify named sets of method overrides that you can selectively enable and disable.
50
+ email:
51
+ - brown.mhg@gmail.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - .gitignore
60
+ - .travis.yml
61
+ - Gemfile
62
+ - MIT-LICENSE
63
+ - README.md
64
+ - Rakefile
65
+ - dress_up.gemspec
66
+ - lib/dress_up.rb
67
+ - lib/dress_up/costume.rb
68
+ - lib/dress_up/errors.rb
69
+ - lib/dress_up/interface.rb
70
+ - lib/dress_up/outfit.rb
71
+ - lib/dress_up/version.rb
72
+ - spec/dress_up/dress_up_spec.rb
73
+ - spec/spec_helper.rb
74
+ homepage: https://github.com/discom4rt/dress_up
75
+ licenses: []
76
+
77
+ post_install_message:
78
+ rdoc_options: []
79
+
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ hash: 3
88
+ segments:
89
+ - 0
90
+ version: "0"
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">"
95
+ - !ruby/object:Gem::Version
96
+ hash: 25
97
+ segments:
98
+ - 1
99
+ - 3
100
+ - 1
101
+ version: 1.3.1
102
+ requirements: []
103
+
104
+ rubyforge_project:
105
+ rubygems_version: 1.8.21
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Enable and disable sets of method overrides
109
+ test_files:
110
+ - spec/dress_up/dress_up_spec.rb
111
+ - spec/spec_helper.rb