wrap_it 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7f693b3a5912624587905a8f0ed50eed00090900
4
+ data.tar.gz: 454f1f68671d59f445b878c52b0cbc690f9e5dfb
5
+ SHA512:
6
+ metadata.gz: 045a82e459f67f7883c5609789c8bdbb74714eabeae1cf174e9b5979963e62fb24818db29d3d3e8652197705dd2d71110c75a84866907fc78f844604c91b6723
7
+ data.tar.gz: 650c0ecd43f82eaefe4075d4bb3828fc0dd76774b6fa533b5a7e7965b6b60324dc386eab34417524f806f65edcfba5592c3ca3639a7ce54e5903e24011312042
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ .rspec
2
+ Gemfile.lock
3
+
4
+
5
+ # Ignore bundler config.
6
+ /.bundle
7
+
8
+ # Ignore all logfiles and tempfiles.
9
+ /log/*.log
10
+ /tmp
11
+
data/.rubocop.yml ADDED
@@ -0,0 +1,5 @@
1
+ MethodLength:
2
+ Max: 20
3
+
4
+ CyclomaticComplexity:
5
+ Max: 7
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test, :development do
6
+ gem 'rails', require: false
7
+ end
data/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # WrapIt
2
+
3
+ This library provides set of classes and modules with simple DSL for quick and easy creating html helpers with your own DSL. It's usefull for implementing CSS frameworks, or making your own.
4
+
5
+ For example, your designer makes perfect button style for some site. This element will appears in many places of site in some variations. The button have `danger`, `success` and `default` look, and can have `active` state. Button can have some icon. So, you make some CSS styles, and now you should place HTML markup of this element in many places of site. With `wrap_it` library you can do it with following code:
6
+
7
+ ```ruby
8
+ class PerfectButton < WrapIt::Container
9
+ include TextContainer
10
+ html_class 'button'
11
+ enum :look, [:default, :success, :danger], html_class_prefix: 'button-'
12
+ switch :active, html_class: 'button-active'
13
+ child :icon, [tag: 'img', class: 'button-icon']
14
+ end
15
+
16
+ WrapIt.register :p_button, 'PerfectButton'
17
+ ```
18
+
19
+ Now, include this helper into you template engine. For Rails:
20
+
21
+ ```ruby
22
+ class MyController < ApplicationController
23
+ helper WrapIt.helpers
24
+ ...
25
+ end
26
+ ```
27
+
28
+ And you can use it in you ERB:
29
+
30
+ ```html
31
+ <%= p_button %>button 1<% end %>
32
+ <%= p_button 'button 2', :active, :success %>
33
+ <%= p_button active: true, look: :success, body: 'button 3' %>
34
+ <%= p_button :danger do |b| %>
35
+ <%= b.icon src: '/path/to/icon.png' %>
36
+ button 4
37
+ <% end %>
38
+ ```
39
+
40
+ This will produce following code:
41
+
42
+ ```html
43
+ <div class="button">button 1</div>
44
+ <div class="button button-active button-success">button 2</div>
45
+ <div class="button button-active button-success">button 3</div>
46
+ <div class="button button-danger">
47
+ <img class="button-icon" src="/path/to/icon.png">
48
+ button 4
49
+ </div>
50
+ ```
51
+
52
+ Note, that lines 2 and 3 produces same html markup.
53
+
54
+ # Status
55
+
56
+ Project in pre-release state. First release version `1.0.0` planned to February of 2014.
57
+
58
+ # Installation
59
+
60
+ Library have a gem. So, just install it:
61
+
62
+ ```sh
63
+ gem install wrap_it
64
+ ```
65
+
66
+ or include in your `Gemfile`
67
+
68
+ ```ruby
69
+ gem 'wrap_it'
70
+ ```
71
+
72
+ and run
73
+
74
+ ```sh
75
+ bundle install
76
+ ```
77
+
78
+ # Configuration
79
+
80
+ Library have no specific configuration.
81
+
82
+ # Usage
83
+
84
+ All helpers classes derived from `WrapIt::Base` class, that provides allmost all functionality. For helpers, thats includes other helpers, use `WrapIt::Container` class. Where are some library-specific methods, defined directly in `WrapIt` module.
85
+
86
+ Simple example explained above. More complex usage is to provide some logic to initalization, capturing and rendering process. To do this, use `after` or `before` `initialize`, `capture` and `reder` callbacks respectively. Usually `after` callbacks used. `initialize` callbacks runs around arguments and optioins parsed, `capture` callbacks runs around capturing content of element and `render` callbacks runs around wrapping content into element tag.
87
+
88
+ Inside callbacks some usefull instance variables available.
89
+
90
+ `@tag` contains tag name for element.
91
+
92
+ `@options` contains creation options hash. This hash also contains `:class` key with current set of HTML classes. But its recommended to use class-aware methods to manipulate html classes (see below). **Warning!** You **MUST** remove from this hash all your class-specific user options, because this hash will be used as list of HTML attributes of element.
93
+
94
+ `@arguments` array available only in `after_initialize` callback and contains creation arguments. Its recommended to extract arguments, related to your class from this array if you plan to subclass your helper in future, so when subclasses `after_initialize` called these arguments will not available there.
95
+
96
+ `@content` string available in `capture` and `render` callbacks and contains captured content. You can change it to any value. If you want to render some html markup with `@content`, use `html_safe` method (see below) to prevent HTML escaping.
97
+
98
+ `@template` contains rendering template. Use this variable carefully, so if you call `@template.content_tag` or something else Rails-related, your library will not be portable to other frameworks. So, if you use this gem in user-end application, or Rails-only library, you are free to use all of `@template` methods.
99
+
100
+ *Examples*
101
+
102
+ Prevent user from changing element tag:
103
+
104
+ ```ruby
105
+ class Helper < WrapIt::Base
106
+ after_initialize { @tag = 'table' }
107
+ end
108
+ ```
109
+
110
+ Including some simple HTML into content
111
+
112
+ ```ruby
113
+ class Helper < WrapIt::Base
114
+ after_initialize do
115
+ @icon = optioins.delete(:icon)
116
+ end
117
+
118
+ after_capture do
119
+ unless @icon.nil?
120
+ @content = html_safe("<i class=\"#{@icon}\"></i>") + @content
121
+ end
122
+ end
123
+ ```
124
+
125
+ ## WrapIt
126
+
127
+ #### WrapIt.register(*args)
128
+
129
+ Registers helper class. In arguments, first specify helper method names as `Symbols` and in last argument fully qualified helper class name as `String`.
130
+
131
+ #### WrapIt.unregister(*args)
132
+
133
+ Unregisters helper class. Just pass list of method names as `Symbols`.
134
+
135
+ #### WrapIt.helpers
136
+
137
+ Returns a module, that contains all registered helpers. Usefull to provide all helpers to template engine.
138
+
139
+ ## WrapIt::Base
140
+
141
+ ### DSL methods
142
+
143
+ #### default_tag(name)
144
+
145
+ Use `default_tag` DSL method inside your class to specify HTML tag name for element. This tag can be changed soon by you or user. `name` can be `Symbol` or `String` and it converted to `String`.
146
+
147
+ #### html_class(*args)
148
+
149
+ Use `html_class` DSL method to add default html classes, thats are automatically added when element created.
150
+
151
+ #### omit_content
152
+
153
+ Once this method called from class, this class will ommit any text content, captured from template. For example, `<%= element do %><p>Any content</p><% end %>` normally will produce `<div><p>Any content</p></div>`. In some cases you whant to drop `<p>Any content</p>`, for exmaple, inside tables.
154
+
155
+ #### switch(name, options = {}, &block)
156
+
157
+ Adds `switch`. Switch is a boolean flag. When element created, creation arguments will be scanned for `Symbol`, that equals to `name`. If it founded, switch turned on. Also creation options inspected. If its contains `name: true` key-value pair, this pair removed from options and switch also turned on. `name` can be `Symbol` or `String` and it converted to `Symbol`.
158
+
159
+ This method also adds getter and setter for this switch in form `name?` and `name=` respectively.
160
+
161
+ You can pass `html_class` option. If it presend, this class will be added or removed to element when switch changes its state.
162
+
163
+ Also `aliases` option available. So if some of aliases founded in arguments it also changes switch state. You should pass only `Symbol` or `Array` if symbols to this optioin.
164
+
165
+ If block given, it will be called each time switch changes its state in context of element with the switch state as argument.
166
+
167
+ #### enum(name, options = {}, &block)
168
+
169
+ Adds `enum`. When element created, creation arguments will be scanned for `Symbol`, that included contains in `values`. If it founded, enum takes this value. Also creation options inspected. If its contains `name: value` key-value pair with valid value, this pair removed from options and enum takes this value.
170
+
171
+ This method also adds getter and setter for this enum.
172
+
173
+ You can pass `html_class_prefix` option. If it present, HTML class will be combined from it and enum value and added or removed from element HTML class.
174
+
175
+ Also `aliases` option available. So if some of aliases founded in creation options keys it also changes enum value. You should pass only `Symbol` or `Array` if symbols to this optioin.
176
+
177
+ `default` option sets default value for enum. This value will used if nil or invalid value assigned to enum.
178
+
179
+ If block given, it will be called each time enum changes its value in context of element with the new value as argument.
180
+
181
+ ### Instance methods
182
+
183
+ #### html_class
184
+
185
+ Returns array of html classes
186
+
187
+ #### html_class=(*args)
188
+
189
+ Sets html class(es) for element. Arguments can be `String`, `Symbol` or `Array` of it. All converted to plain array of `Symbols`. Duplicated classes removed.
190
+
191
+ #### add_html_class(*args)
192
+
193
+ Adds html class. For args see `#html_class=`
194
+
195
+ #### remove_html_class(*args)
196
+
197
+ Removes html class. For args see `#html_class=`
198
+
199
+ #### html_class?(*args, &block)
200
+
201
+ Determines whether element contains class, satisfied by conditions, specified in method arguments.
202
+
203
+ There are two forms of method call: with list of conditions as arguments and with block for comparing. Method makes comparison with html class untill first `true` return value or end of list. All conditions should be satisfied for `true` return of this method.
204
+
205
+ In first form, each argument treated as condition. Condition can be a `Regexp`, so html classes of element tested for matching to that regular expression. If condition is an `Array` then every class will be tested for presence in this array. If condition is `Symbol` or `String` classes will be compared with it via equality operator `==`.
206
+
207
+ In second form all arguments are ignored and for each comparison given block called with html class as argument. Block return value then used.
208
+
209
+ *Examples*
210
+
211
+ ```ruby
212
+ # with `Symbol` or `String` conditions
213
+ element.html_class = [:a, :b, :c]
214
+ element.html_class?(:a) #=> true
215
+ element.html_class?(:d) #=> false
216
+ element.html_class?(:a, 'b') #=> true
217
+ element.html_class?(:a, :d) #=> false
218
+
219
+ # with `Regexp` conditions
220
+ element.html_class = [:some, :test]
221
+ element.html_class?(/some/) #=> true
222
+ element.html_class?(/some/, /bad/) #=> false
223
+ element.html_class?(/some/, :test) #=> true
224
+
225
+ # with `Array` conditions
226
+ element.html_class = [:a, :b, :c]
227
+ element.html_class?(%w(a d)) #=> true
228
+ element.html_class?(%w(e d)) #=> false
229
+
230
+ # with block
231
+ element.html_class = [:a, :b, :c]
232
+ element.html_class? { |x| x == 'a' } #=> true
233
+ ```
234
+
235
+ #### no_html_class?(*args, &block)
236
+
237
+ Determines whether element doesn't contains class, satisfied by conditions, specified in method arguments. See `html_class?`.
238
+
239
+ ## WrapIt::Container
240
+
241
+ ### DSL methods
242
+
243
+ #### child(*args, &block)
244
+
245
+ Creates your own DSL method to create child items. In arguments, you should specify list of method names (aliases if more that one). Then you can specify class name for shild. If ommited, `WrapIt::Base` will be used. and as last argument you can specify array of arguments, that will be passed to child constructor. This array will be mixed with arguments, specified by user, with higher priority. At last, you can define block, that will be called after creating child, but before its rendering. This child passed as argument to block.
246
+
247
+ # Todo
248
+
249
+ * Enlarge library functionality
250
+ * Strong testing
251
+ * Finish Sinatra integration
252
+ * Rubydoc documentation
253
+
254
+ # License
255
+
256
+ The MIT License (MIT)
257
+
258
+ Copyright (c) 2014 Alexey Ovchinnikov
259
+
260
+ Permission is hereby granted, free of charge, to any person obtaining a copy
261
+ of this software and associated documentation files (the "Software"), to deal
262
+ in the Software without restriction, including without limitation the rights
263
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
264
+ copies of the Software, and to permit persons to whom the Software is
265
+ furnished to do so, subject to the following conditions:
266
+
267
+ The above copyright notice and this permission notice shall be included in
268
+ all copies or substantial portions of the Software.
269
+
270
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
271
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
272
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
273
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
274
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
275
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
276
+ THE SOFTWARE.
277
+
data/Rakefile ADDED
@@ -0,0 +1,14 @@
1
+ require 'bundler/gem_tasks'
2
+ # require 'yard'
3
+ require 'rspec/core/rake_task'
4
+ # require 'rake/testtask'
5
+
6
+ # Rake::TestTask.new do |t|
7
+ # t.name = :spec_rails
8
+ # t.libs.push "spec_rails"
9
+ # t.test_files = FileList['spec_rails/**/*_spec.rb']
10
+ # t.verbose = true
11
+ # end
12
+
13
+ #YARD::Rake::YardocTask.new
14
+ RSpec::Core::RakeTask.new
data/config.ru ADDED
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
@@ -0,0 +1,128 @@
1
+ module WrapIt
2
+ #
3
+ # Adds #extract! and #extarct_first! methods to array. Theese methods are
4
+ # extracts items from array by some condirions and returns its as separate
5
+ # array for #extract! and as first item for #extract_first!.
6
+ #
7
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
8
+ #
9
+ module ArgumentsArray
10
+ REQUIRED_METHODS = %i(reject! find_index delete_at)
11
+
12
+ def self.included(base)
13
+ methods = base.methods
14
+ # avoid including in classes thats doen't have methods, used in
15
+ # inplementation
16
+ REQUIRED_METHODS.all? { |m| methods.include?(m) } || fail(
17
+ TypeError,
18
+ "#{self.class.name} can't be included into #{base.class.name}"
19
+ )
20
+ end
21
+ #
22
+ # Extracts elements from array by conditions, passed in arguments nad
23
+ # returns theese elements as new array.
24
+ #
25
+ # Condition can be Regexp, class, Array and any other value. If condition
26
+ # is `Regexp`, all elements of array are tested for matching to this
27
+ # regexp, previously converted to String by their `to_s` method. If
28
+ # condition is an `Array`, all elements tested if it included in these
29
+ # array. If the condition is a class, then elements are tested via `is_a?`
30
+ # method for this class. For any other value, elements are tested with
31
+ # equality operator `==`.
32
+ #
33
+ # You can provide a block. In this case, all arguments are ignored, and
34
+ # block yielded for each element of array. If block returns `true`,
35
+ # element extracted from array.
36
+ #
37
+ # All conditions, passed as arguments are `or`-ed so `String, Symbol` means
38
+ # select Symbol or String elements.
39
+ #
40
+ # @overload extract!([condition, ..., options])
41
+ # @param [Object] condition one of `or`-ed conditions for comparing
42
+ # @param [Hash] options options for axtracting
43
+ # @options options [Object, Array] :and one or array of `and`-ed conditions
44
+ #
45
+ # @overload extract!(&block)
46
+ # @yield [element] Gives each element of array to block. You should return
47
+ # `true` to extract this element or `false` to keep it in array.
48
+ # @yieldparam [Object] element element of array to inspect
49
+ # @yieldreturn [Boolean] whether exclude this element or not
50
+ #
51
+ # @return [Array] array of extracted elements
52
+ #
53
+ # @example extract by class
54
+ # arr = [1, 2, 3, 'and', 'string']
55
+ # arr.extend WrapIt::ArgumentsArray
56
+ # arr.extract(String) #=> ['and', 'string']
57
+ # arr #=> [1, 2, 3]
58
+ #
59
+ # @example extract by value
60
+ # arr = [1, 2, 3, 'and', 'string']
61
+ # arr.extend WrapIt::ArgumentsArray
62
+ # arr.extract(1, 2) #=> [1, 2]
63
+ # arr #=> [3, 'and', 'string']
64
+ #
65
+ # @example extract by Regexp
66
+ # arr = [1, 2, 3, 'and', 'string', :str]
67
+ # arr.extend WrapIt::ArgumentsArray
68
+ # arr.extract(/^str/) #=> ['string', :str]
69
+ # arr #=> [1, 2, 3, 'and']
70
+ #
71
+ # @example extract by Array
72
+ # arr = [1, 2, 3, 'and', 'string']
73
+ # arr.extend WrapIt::ArgumentsArray
74
+ # arr.extract([1, 10, 'and']) #=> [1, 'and']
75
+ # arr #=> [2, 3, 'string']
76
+ #
77
+ # @example extract by block
78
+ # arr = [1, 2, 3, 'and', 'string']
79
+ # arr.extend WrapIt::ArgumentsArray
80
+ # arr.extract {|x| x < 3} #=> [1, 2]
81
+ # arr #=> [3, 'and', 'string']
82
+ #
83
+ # @example extract with `and` condition
84
+ # arr = [1, 2, 3, 'and', 'string', :str]
85
+ # arr.extend WrapIt::ArgumentsArray
86
+ # arr.extract(String, and: [/^str/]) #=> ['string']
87
+ # arr #=> [1, 2, 3, 'and', :str]
88
+ def extract!(*args, &block)
89
+ extracted = []
90
+ reject! do |arg|
91
+ do_compare(arg, *args, &block) && extracted << arg && true
92
+ end
93
+ extracted
94
+ end
95
+
96
+ #
97
+ # Extracts first element from array that is satisfy conditions, passed in
98
+ # arguments and returns these element.
99
+ #
100
+ # @see #extract!
101
+ def extract_first!(*args, &block)
102
+ index = find_index { |arg| do_compare(arg, *args, &block) }
103
+ index.nil? ? nil : delete_at(index)
104
+ end
105
+
106
+ private
107
+
108
+ def do_compare(target, *compare_args)
109
+ if block_given?
110
+ yield target
111
+ else
112
+ options = compare_args.extract_options!
113
+ result = compare_args.any? do |dest|
114
+ case
115
+ when dest.is_a?(Array) then dest.include?(target)
116
+ when dest.is_a?(Regexp) then dest.match(target.to_s)
117
+ when dest.is_a?(Class) then target.is_a?(dest)
118
+ else dest == target
119
+ end
120
+ end
121
+ if options[:and].is_a?(Array)
122
+ result &&= do_compare(target, *options[:and])
123
+ end
124
+ result
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,108 @@
1
+ module WrapIt
2
+ #
3
+ # Base class for all HTML helper classes
4
+ #
5
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
+ #
7
+ class Base
8
+ include DerivedAttributes
9
+ include Callbacks
10
+
11
+ callback :initialize, :capture, :render
12
+
13
+ include HTMLClass
14
+ include Switches
15
+ include Enums
16
+ include Renderer
17
+
18
+ @omit_content = false
19
+
20
+ attr_reader :tag
21
+ attr_reader :options
22
+
23
+ def initialize(template, *args, &block)
24
+ @template, @arguments, @block = template, args, block
25
+ self.options = @arguments.extract_options!
26
+ @arguments.extend ArgumentsArray
27
+ add_default_classes
28
+ run_callbacks :initialize do
29
+ @tag = @options.delete(:tag) ||
30
+ self.class.get_derived(:@default_tag) || 'div'
31
+ @helper_name = @options.delete(:helper_name)
32
+ @helper_name.is_a?(String) && @helper_name = @helper_name.to_sym
33
+ end
34
+ @argument = nil
35
+ end
36
+
37
+ def omit_content?
38
+ self.class.get_derived(:@omit_content)
39
+ end
40
+
41
+ def render(*args, &render_block)
42
+ # return cached copy if it available
43
+ return @content unless @content.nil?
44
+ @content = empty_html
45
+
46
+ # capture content from block
47
+ do_capture
48
+ # add to content string args and block result if its present
49
+ args.flatten.each { |a| @content << a if a.is_a? String }
50
+ block_given? && @content << instance_exec(self, &render_block)
51
+
52
+ # cleanup options from empty values
53
+ @options.select! { |k, v| !v.nil? && !v.empty? }
54
+ # render element
55
+ run_callbacks :render do
56
+ @content = content_tag(@tag, @content, @options)
57
+ end
58
+
59
+ # @content = @wrapper.render(@content.html_safe) if @wrapper.is_a?(Base)
60
+ if @template.output_buffer.nil?
61
+ # when render called from code, just return content as a String
62
+ @content
63
+ else
64
+ # in template context, write content to templates buffer
65
+ concat(@content)
66
+ empty_html
67
+ end
68
+ end
69
+
70
+ protected
71
+
72
+ #
73
+ # @dsl
74
+ # Defines default tag name for element. This tag can be changed soon.
75
+ # @param name [<Symbol, String>] Tag name. Converted to `String`.
76
+ #
77
+ # @return [void]
78
+ def self.default_tag(name)
79
+ name.is_a?(String) || name.is_a?(Symbol) ||
80
+ fail(ArgumentError, 'Tag name should be a String or Symbol')
81
+ @default_tag = name.to_s
82
+ end
83
+
84
+ def self.omit_content
85
+ @omit_content = true
86
+ end
87
+
88
+ def options=(hash)
89
+ hash.is_a?(Hash) || return
90
+ hash.symbolize_keys!
91
+
92
+ # sanitize class
93
+ hash[:class] ||= []
94
+ hash[:class] = [hash[:class]] unless hash[:class].is_a?(Array)
95
+ hash[:class] = hash[:class].map { |c| c.to_s }.uniq
96
+ @options = hash
97
+ end
98
+
99
+ def do_capture
100
+ run_callbacks :capture do
101
+ @content ||= empty_html
102
+ unless @block.nil? || omit_content?
103
+ @content << (capture(self, &@block) || empty_html)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,63 @@
1
+ module WrapIt
2
+ #
3
+ # Callbacks implementation
4
+ #
5
+ # @author Alexey Ovchinnikov <alexiss@cybernetlab.ru>
6
+ #
7
+ module Callbacks
8
+ def self.included(base)
9
+ extend DerivedAttributes
10
+ base.extend ClassMethods
11
+ end
12
+
13
+ def run_callbacks(name)
14
+ self.class.collect_derived("@before_#{name}".to_sym).each do |cb|
15
+ if cb.is_a?(Symbol)
16
+ send(cb)# if respond_to?(cb)
17
+ else
18
+ instance_eval(&cb)
19
+ end
20
+ end
21
+ yield if block_given?
22
+ self.class.collect_derived("@after_#{name}".to_sym).reverse.each do |cb|
23
+ if cb.is_a?(Symbol)
24
+ send(cb)# if respond_to?(cb)
25
+ else
26
+ instance_eval(&cb)
27
+ end
28
+ end
29
+ end
30
+
31
+ #
32
+ # Class methods to include
33
+ #
34
+ module ClassMethods
35
+ def callback(*args)
36
+ args.each do |name|
37
+ instance_eval(&Callbacks.define_callback(:before, name))
38
+ instance_eval(&Callbacks.define_callback(:after, name))
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def self.define_callback(time, name)
46
+ m_name = "#{time}_#{name}".to_sym
47
+ var = "@#{m_name}".to_sym
48
+ proc do
49
+ define_singleton_method(m_name) do |method = nil, &block|
50
+ return if block.nil? && !method.is_a?(Symbol)
51
+ arr =
52
+ if instance_variable_defined?(var)
53
+ instance_variable_get(var)
54
+ else
55
+ instance_variable_set(var, [])
56
+ end
57
+ arr << (block || method)
58
+ instance_variable_set(var, arr)
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end