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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e716ded139c4ff28b6f4029c5db454fb438227cf
4
- data.tar.gz: e2a14fd0f614b0ad3bd561fbeaefc9f60386babf
3
+ metadata.gz: 1068d0c42b10129b8bedfb154f11da7bcdde0756
4
+ data.tar.gz: e60b90f42294d96277e394d77a7e6647991649db
5
5
  SHA512:
6
- metadata.gz: 25196e1c60dfee280fbd1062e030b7d883332a66b089204dd2efff1c06f0944169aaeaf62a9c3d525358d391838db7e42d9d744fd59fcdccb4030249aaf36b6c
7
- data.tar.gz: d999c5e670f6c11a7c7d25594368c463902fd7afd23550dcd0c08e9f8946df544c434b7b97c6729e085bcbf2340f13dc741510436eb17d4e66fc268e6af6adc8
6
+ metadata.gz: b3cea6e900b254ae28a34626fba8e1e938493d9105a1d5c50c6bbff950686a451e8ef8478c0e73c1dc602450ed28331c4e8b2c2a54c5955ddf7e3b96bc97c548
7
+ data.tar.gz: bb0e8a2148394e1aeaa679f99b5b5852e8312e11243400b25f7fa1ed6f2a18f270f547a10955da88afefcdb7937a5496603ab4cb84c4f6ebda6624f6dbab6794
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+
11
+ .rvmrc
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, 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.
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
- TODO: Write usage instructions here
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
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/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.
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
-
@@ -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", "~> 10.0"
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.instance_variable_set("@#{option_key}", option_val)
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
@@ -1,3 +1,3 @@
1
1
  module BottledDecorators
2
- VERSION = "0.1.4"
2
+ VERSION = "0.1.5.beta.1"
3
3
  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
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-02-06 00:00:00.000000000 Z
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: '10.0'
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: '10.0'
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: '0'
109
+ version: 1.3.1
110
110
  requirements: []
111
111
  rubyforge_project:
112
- rubygems_version: 2.5.1
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.