bottled_decorators 0.1.4 → 0.1.5.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/README.md +178 -4
- data/bottled_decorators.gemspec +1 -1
- data/lib/bottled_decorators/bottled_decorator.rb +106 -3
- data/lib/bottled_decorators/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1068d0c42b10129b8bedfb154f11da7bcdde0756
|
4
|
+
data.tar.gz: e60b90f42294d96277e394d77a7e6647991649db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b3cea6e900b254ae28a34626fba8e1e938493d9105a1d5c50c6bbff950686a451e8ef8478c0e73c1dc602450ed28331c4e8b2c2a54c5955ddf7e3b96bc97c548
|
7
|
+
data.tar.gz: bb0e8a2148394e1aeaa679f99b5b5852e8312e11243400b25f7fa1ed6f2a18f270f547a10955da88afefcdb7937a5496603ab4cb84c4f6ebda6624f6dbab6794
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
## The best thing to happen since bottled water
|
4
4
|
|
5
5
|
Wait a minute, I thought that was [bottled_services?](https://github.com/John-Hayes-Reed/bottled_services), well nevermind.
|
6
|
-
bottled_decorators are here to make your life easier, and provide decorators that are actually decorators and not just View Objects / View Helpers with the name 'decorator'. bottled_decorators encourage the use of DRY and reusable code by stopping the direct model relation
|
6
|
+
bottled_decorators are here to make your life easier, and provide decorators that are actually decorators and not just View Objects / View Helpers with the name 'decorator'. bottled_decorators encourage the use of DRY and reusable code by stopping the direct model relation and 'my job is to prepare something for the view' mentality seen in some gems and implementations, and to bring decorators back to what they should be, a reusable and stackable extra layer of functionality to be used **anywhere** and not just in views.
|
7
7
|
Creating your decorators are also as easy as pie with the botted_decorator generator. All you need to worry about is your method logic, let bottled_decorators do the rest for you!
|
8
8
|
|
9
9
|
## Installation
|
@@ -24,7 +24,180 @@ Or install it yourself as:
|
|
24
24
|
|
25
25
|
## Usage
|
26
26
|
|
27
|
-
|
27
|
+
#### Giving birth to decorators
|
28
|
+
|
29
|
+
Creating new decorators is as easy as pie with the BottledDecorator Generator.
|
30
|
+
just add the decorator name, and if you want the decorator methods, to the generator command:
|
31
|
+
|
32
|
+
```
|
33
|
+
rails g bottled_decorator LargeFoodItem
|
34
|
+
```
|
35
|
+
|
36
|
+
```
|
37
|
+
rails g bottled_decorator PiratizedUser name greeting
|
38
|
+
```
|
39
|
+
|
40
|
+
Just like that we have two new decorators ready for use, a currently empty decorator for decorating food as large food items, and a decorator with two methods prepared for us, name and greeting, that decorates users with pirate like behaviour.
|
41
|
+
|
42
|
+
#### Decorator Methods
|
43
|
+
|
44
|
+
A possible implementation of the two decorators we generated above could be:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class LargeFoodItem
|
48
|
+
include BottledDecorator
|
49
|
+
|
50
|
+
# A large food item has 50 cents added onto its cost
|
51
|
+
def cost
|
52
|
+
super + 50
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
```
|
57
|
+
```ruby
|
58
|
+
class PiratizedUser
|
59
|
+
include BottledDecorator
|
60
|
+
|
61
|
+
# Pirates all dream of being the captain of their own ship
|
62
|
+
def name
|
63
|
+
"Cap'n #{super}"
|
64
|
+
end
|
65
|
+
|
66
|
+
# And its a well studied fact that Rum is a pirates poison of choice
|
67
|
+
def greeting
|
68
|
+
"Yo ho ho and a bottle of rum!"
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
#### A Decorator's first steps
|
75
|
+
|
76
|
+
Once generated, the next things to do is decorate loads of stuff!
|
77
|
+
|
78
|
+
Unlike some other implementations of decorators (many of which I dare to say are not actually decorators but just View Objects / Helpers that are hijacking the word *decorator*), BottledDecorators do not use an extension style of decorating. Instead they wrap up our objects in a lovely cosy blanket of decoration:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
pirate_user = PiratizedUser.(@user)
|
82
|
+
|
83
|
+
# or
|
84
|
+
|
85
|
+
@large_burger = LargeFoodItem.(burger)
|
86
|
+
```
|
87
|
+
|
88
|
+
You can also wrap a collection, to get an Array of decorated objects:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
@pirates = PiratizedUser.(@users)
|
92
|
+
# returns an Array
|
93
|
+
```
|
94
|
+
|
95
|
+
As can be seen above, this wrapping is done using the decorator class' `::call` method, so a simple `.()` will suffice to get the job done, or any other preferred choice of envoking the call method.
|
96
|
+
|
97
|
+
#### Bottled Decorators in action
|
98
|
+
|
99
|
+
Once we have our decorated object, its just a case of invoking your methods:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
@user.name
|
103
|
+
# => "John Hayes-Reed"
|
104
|
+
@user.age
|
105
|
+
# => 27
|
106
|
+
|
107
|
+
@user = PiratizedUser.(@user)
|
108
|
+
@user.name
|
109
|
+
# => "Cap'n John Hayes-Reed"
|
110
|
+
@user.greeting
|
111
|
+
# => "Yo ho ho and a bottle of rum!"
|
112
|
+
|
113
|
+
# of course we can still access the components original methods as well
|
114
|
+
@user.age
|
115
|
+
# => 27
|
116
|
+
```
|
117
|
+
|
118
|
+
Because BottledDecorators wrap instead of extend, we can also keep wrapping on multiple layers, adding on functionality to overridden methods indefinately, because each layer looks at its own component for its `super`, and not the original instance:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
class DrunkUser
|
122
|
+
include BottledDecorator
|
123
|
+
|
124
|
+
def name
|
125
|
+
"#{super}, the drunkard!"
|
126
|
+
end
|
127
|
+
|
128
|
+
def greeting
|
129
|
+
"#{super} (hiccup)"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
```
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
@user.name
|
136
|
+
# => "John Hayes-Reed"
|
137
|
+
|
138
|
+
@user = PiratizedUser.(@user)
|
139
|
+
@user.name
|
140
|
+
# => "Cap'n John Hayes-Reed"
|
141
|
+
@user.greeting
|
142
|
+
# => "Yo ho ho and a bottle of rum!"
|
143
|
+
|
144
|
+
@user = DrunkUser.(@user)
|
145
|
+
@user.name
|
146
|
+
# => "Cap'n John Hayes-Reed, the drunkard!"
|
147
|
+
@user.greeting
|
148
|
+
# => "Yo ho ho and a bottle of rum! (hiccup)"
|
149
|
+
```
|
150
|
+
|
151
|
+
Because of this layering ability, we can use a single decorator to represent multiple possible states of objects:
|
152
|
+
|
153
|
+
```ruby
|
154
|
+
# a regular burger
|
155
|
+
@burger.cost
|
156
|
+
# => 100
|
157
|
+
|
158
|
+
# a large burger
|
159
|
+
LargeFoodItem.(@burger).cost
|
160
|
+
# => 150
|
161
|
+
|
162
|
+
# an extra large burger
|
163
|
+
LargeFoodItem.(LargeFoodItem.(@burger)).cost
|
164
|
+
# => 200
|
165
|
+
|
166
|
+
# SUPERSIIIIIZE
|
167
|
+
LargeFoodItem.(LargeFoodItem.(LargeFoodItem.(@burger))).cost
|
168
|
+
# => 250
|
169
|
+
```
|
170
|
+
|
171
|
+
This can be done with multiple decorators to build up the cost of a whole variety of possibilites:
|
172
|
+
|
173
|
+
```ruby
|
174
|
+
class WithDrink
|
175
|
+
include BottledDecorator
|
176
|
+
|
177
|
+
def cost
|
178
|
+
super + 15
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
```
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
# a regular burger
|
186
|
+
@burger.cost
|
187
|
+
# => 100
|
188
|
+
|
189
|
+
# with a side order drink
|
190
|
+
WithDrink.(@burger).cost
|
191
|
+
# => 115
|
192
|
+
|
193
|
+
# a large burger with a side order drink
|
194
|
+
LargeFoodItem.(WithDrink.(@burger)).cost
|
195
|
+
# => 165
|
196
|
+
```
|
197
|
+
|
198
|
+
#### Extras
|
199
|
+
|
200
|
+
Currently BottledDecorators come with a few serializer methods and checker methods to add all decorated methods onto the converted object as well, currently supported are `#to_json`, `#as_json`, `#to_h`, `#respond_to?`, each of which will drill down through each component layer all the way to the root component to provide / access the whole scope available.
|
28
201
|
|
29
202
|
## Development
|
30
203
|
|
@@ -34,10 +207,11 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
34
207
|
|
35
208
|
## Contributing
|
36
209
|
|
37
|
-
|
210
|
+
All and any contributions to improve Bottled Decorators and make them useful for as many people as possible are gratefully welcomed. I hope all contributions can keep in line with the philosophy of Bottled Technology, which is to make simple and easy-to-use tools.
|
211
|
+
|
212
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/John-Hayes-Reed/bottled_decorators. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
38
213
|
|
39
214
|
|
40
215
|
## License
|
41
216
|
|
42
217
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
43
|
-
|
data/bottled_decorators.gemspec
CHANGED
@@ -21,6 +21,6 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.add_development_dependency "railties"
|
23
23
|
spec.add_development_dependency "bundler", "~> 1.13"
|
24
|
-
spec.add_development_dependency "rake", "~>
|
24
|
+
spec.add_development_dependency "rake", "~> 12.0"
|
25
25
|
spec.add_development_dependency "rspec", "~> 3.0"
|
26
26
|
end
|
@@ -1,33 +1,81 @@
|
|
1
|
+
# Public: A module to turn classes into bottled decorators, for the decorator
|
2
|
+
# design pattern, wrapping a component with new functionality, while still
|
3
|
+
# allowing the original functionality and access for the wrapped component.
|
1
4
|
module BottledDecorator
|
2
|
-
|
5
|
+
# Internal: Runs when the module is included, makes the including class extend
|
6
|
+
# The ClassMethod module below to include a class ::call method.
|
7
|
+
#
|
8
|
+
# Returns nothing.
|
3
9
|
def self.included(base)
|
4
10
|
base.extend(ClassMethods)
|
5
11
|
end
|
6
12
|
|
13
|
+
# Internal: initializes a new decorator instance. Sets the component and
|
14
|
+
# options attibutes and reader methods for each.
|
15
|
+
#
|
16
|
+
# Returns nothing.
|
7
17
|
def initialize(component, **options)
|
8
18
|
class << self
|
9
19
|
attr_reader :component
|
10
20
|
end
|
11
21
|
@component = component
|
12
22
|
options.each do |option_key, option_val|
|
13
|
-
self.
|
23
|
+
(class << self; self; end).class_eval { attr_reader option_key }
|
24
|
+
self.instance_variable_set(:"@#{option_key}", option_val)
|
14
25
|
end
|
15
26
|
end
|
16
27
|
|
28
|
+
# Internal: Sends any method requests unknown to the decorator back down to
|
29
|
+
# the component. This allows the wrapped instances to still use the inner
|
30
|
+
# components methods as if they were being called on the original component
|
31
|
+
# before wrapping.
|
32
|
+
#
|
33
|
+
# Examples
|
34
|
+
#
|
35
|
+
# @user.name
|
36
|
+
# # => 'John'
|
37
|
+
# @decorated_user = ExampleDecorator.call @user
|
38
|
+
# @decorated_user.name
|
39
|
+
# # => 'John'
|
17
40
|
def method_missing(method, *args)
|
18
41
|
return @component.send(method, *args)
|
19
42
|
rescue NoMethodError => e
|
20
43
|
raise NoMethodError.new("Method #{method} was not found in the decorator, or the decorated objects", 'NoMethodError')
|
21
44
|
end
|
22
45
|
|
46
|
+
# Public: Takes the decorated component and serializes it into a JSON format
|
47
|
+
# by combining its original attributes and the decorated methods from the
|
48
|
+
# wrapping decorator class.
|
49
|
+
#
|
50
|
+
# Returns a JSON object representation of the decorated component.
|
23
51
|
def as_json(**args)
|
24
52
|
to_h(**args).as_json
|
25
53
|
end
|
26
54
|
|
55
|
+
# Public: Takes the decorated component and serializes it into a JSON format
|
56
|
+
# by combining its original attributes and the decorated methods from the
|
57
|
+
# wrapping decorator class.
|
58
|
+
#
|
59
|
+
# Returns a JSON object representation of the decorated component.
|
27
60
|
def to_json(**args)
|
28
61
|
to_h(**args).to_json
|
29
62
|
end
|
30
63
|
|
64
|
+
# Public: Takes the decorated component and serializes it into a Hash format
|
65
|
+
# by combining its original attributes and the decorated methods from the
|
66
|
+
# wrapping decorator class.
|
67
|
+
#
|
68
|
+
# Examples
|
69
|
+
#
|
70
|
+
# @user.to_h
|
71
|
+
# # => { first_name: 'John', last_name: 'Hayes-Reed' }
|
72
|
+
# @decorated_user = FullNameDecorator.call @user
|
73
|
+
# @decorated_user.to_h
|
74
|
+
# # => { first_name: 'John',
|
75
|
+
# last_name: 'Hayes-Reed',
|
76
|
+
# full_name: 'John Hayes-Reed' }
|
77
|
+
#
|
78
|
+
# Returns a Hash object representation of the decorated component.
|
31
79
|
def to_h(**args)
|
32
80
|
{}.tap do |hash|
|
33
81
|
if @component.respond_to?(:component)
|
@@ -41,13 +89,69 @@ module BottledDecorator
|
|
41
89
|
end
|
42
90
|
end
|
43
91
|
|
92
|
+
# Public: checks if either the decorator class, or the decorated component
|
93
|
+
# Responds to a method.
|
94
|
+
#
|
95
|
+
# Examples
|
96
|
+
#
|
97
|
+
# @user.respond_to? :name
|
98
|
+
# # => true
|
99
|
+
# @user.respond_to? :full_name
|
100
|
+
# # => false
|
101
|
+
#
|
102
|
+
# @decorated_user = FullNameDecorator.call @user
|
103
|
+
# @decorated_user.respond_to? :name
|
104
|
+
# # => true
|
105
|
+
# @decorated_user.respond_to? :full_name
|
106
|
+
# # => true
|
44
107
|
def respond_to?(tester)
|
45
108
|
response = @component.respond_to?(tester)
|
46
109
|
return true if response
|
47
110
|
self.class.instance_methods(false).map(&:to_s).include?("#{tester}")
|
48
111
|
end
|
49
112
|
|
113
|
+
# Public: Uses the decorated components #to_param method as its own. This
|
114
|
+
# allows for the natural use of decorated objects in things like Rails url
|
115
|
+
# helper methods.
|
116
|
+
#
|
117
|
+
# Returns the components parameter attibute. (eg: id).
|
118
|
+
def to_param
|
119
|
+
@component.to_param
|
120
|
+
end
|
121
|
+
|
122
|
+
# Public: Gets the root component in the case of multiple wrapped components
|
123
|
+
# by first searching if the current decorators component itself holds a
|
124
|
+
# component.
|
125
|
+
#
|
126
|
+
# Examples
|
127
|
+
#
|
128
|
+
# decorated_user.root_component
|
129
|
+
# # => #<User:00x0...>
|
130
|
+
#
|
131
|
+
# Returns whichever is the lowest component in the stack.
|
132
|
+
def root_component
|
133
|
+
return @component.root_component if @component.respond_to? :component
|
134
|
+
@component
|
135
|
+
end
|
136
|
+
|
137
|
+
# Internal: Allows methods found in the decorator, as well as methods found
|
138
|
+
# in the original decorated component to be recognised by #method method.
|
139
|
+
#
|
140
|
+
# Returns a Method object if found.
|
141
|
+
private def respond_to_missing?(method, include_private = false)
|
142
|
+
@component.respond_to?(method) ||
|
143
|
+
self.class.instance_methods(false).map(&:to_s).include?(method.to_s) ||
|
144
|
+
super
|
145
|
+
end
|
146
|
+
|
147
|
+
# Internal: The class methods to be added to the decorator class when
|
148
|
+
# including this module. Adds a class ::call method.
|
50
149
|
module ClassMethods
|
150
|
+
# Public: instantiates a new decorator class instance, creates an Array
|
151
|
+
# of instances if the first argument (the component), is an iterable
|
152
|
+
# group of objects.
|
153
|
+
#
|
154
|
+
# Returns a decorated object, or an Array of decorated objects.
|
51
155
|
def call(*args)
|
52
156
|
if args.first.respond_to?(:each)
|
53
157
|
[].tap do |arr|
|
@@ -60,5 +164,4 @@ module BottledDecorator
|
|
60
164
|
end
|
61
165
|
end
|
62
166
|
end
|
63
|
-
|
64
167
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bottled_decorators
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.5.beta.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John_Hayes-Reed
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-04-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -44,14 +44,14 @@ dependencies:
|
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '
|
47
|
+
version: '12.0'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '
|
54
|
+
version: '12.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: rspec
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,12 +104,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
104
|
version: '0'
|
105
105
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
106
|
requirements:
|
107
|
-
- - "
|
107
|
+
- - ">"
|
108
108
|
- !ruby/object:Gem::Version
|
109
|
-
version:
|
109
|
+
version: 1.3.1
|
110
110
|
requirements: []
|
111
111
|
rubyforge_project:
|
112
|
-
rubygems_version: 2.
|
112
|
+
rubygems_version: 2.6.10
|
113
113
|
signing_key:
|
114
114
|
specification_version: 4
|
115
115
|
summary: The easiest way to make decorators, the way they are supposed to be.
|