locomotivecms-solid 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +10 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.md +152 -0
- data/Rakefile +7 -0
- data/lib/locomotivecms-solid.rb +2 -0
- data/lib/solid.rb +48 -0
- data/lib/solid/arguments.rb +26 -0
- data/lib/solid/block.rb +13 -0
- data/lib/solid/conditional_block.rb +35 -0
- data/lib/solid/context_error.rb +2 -0
- data/lib/solid/default_security_rules.rb +24 -0
- data/lib/solid/element.rb +51 -0
- data/lib/solid/engine.rb +4 -0
- data/lib/solid/extensions.rb +17 -0
- data/lib/solid/iterable.rb +18 -0
- data/lib/solid/liquid_extensions.rb +87 -0
- data/lib/solid/liquid_extensions/assign_tag.rb +21 -0
- data/lib/solid/liquid_extensions/for_tag.rb +102 -0
- data/lib/solid/liquid_extensions/if_tag.rb +44 -0
- data/lib/solid/liquid_extensions/unless_tag.rb +13 -0
- data/lib/solid/liquid_extensions/variable.rb +34 -0
- data/lib/solid/method_whitelist.rb +56 -0
- data/lib/solid/model_drop.rb +119 -0
- data/lib/solid/parser.rb +108 -0
- data/lib/solid/parser/ripper.rb +220 -0
- data/lib/solid/parser/ruby_parser.rb +88 -0
- data/lib/solid/tag.rb +11 -0
- data/lib/solid/template.rb +24 -0
- data/lib/solid/version.rb +3 -0
- data/locomotivecms-solid.gemspec +26 -0
- data/spec/solid/arguments_spec.rb +314 -0
- data/spec/solid/block_spec.rb +39 -0
- data/spec/solid/conditional_block_spec.rb +39 -0
- data/spec/solid/default_security_rules_spec.rb +180 -0
- data/spec/solid/element_examples.rb +67 -0
- data/spec/solid/liquid_extensions/assign_tag_spec.rb +27 -0
- data/spec/solid/liquid_extensions/for_tag_spec.rb +48 -0
- data/spec/solid/liquid_extensions/if_tag_spec.rb +64 -0
- data/spec/solid/liquid_extensions/unless_tag_spec.rb +54 -0
- data/spec/solid/liquid_extensions/variable_spec.rb +25 -0
- data/spec/solid/model_drop_spec.rb +26 -0
- data/spec/solid/parser/ripper_spec.rb +14 -0
- data/spec/solid/parser/ruby_parser_spec.rb +7 -0
- data/spec/solid/tag_spec.rb +26 -0
- data/spec/solid/template_spec.rb +37 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/class_highjacker_examples.rb +33 -0
- data/spec/support/method_whitelist_matchers.rb +17 -0
- data/spec/support/parser_examples.rb +261 -0
- data/spec/support/tag_highjacker_examples.rb +33 -0
- metadata +204 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
module Iterable
|
|
3
|
+
include Enumerable
|
|
4
|
+
|
|
5
|
+
def each(&block)
|
|
6
|
+
self.walk(&block)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
protected
|
|
10
|
+
def walk(nodes=nil, &block)
|
|
11
|
+
(nodes || self.nodelist).each do |node|
|
|
12
|
+
yield node
|
|
13
|
+
walk(node.nodelist || [], &block) if node.respond_to?(:nodelist)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
|
|
3
|
+
class << self
|
|
4
|
+
|
|
5
|
+
def extend_liquid!
|
|
6
|
+
LiquidExtensions.load!
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module LiquidExtensions
|
|
12
|
+
|
|
13
|
+
module ClassHighjacker
|
|
14
|
+
|
|
15
|
+
def load!
|
|
16
|
+
original_class = Liquid.send(:remove_const, demodulized_name)
|
|
17
|
+
original_classes[demodulized_name] = original_class unless original_classes.has_key?(demodulized_name) # avoid loosing reference to original class
|
|
18
|
+
Liquid.const_set(demodulized_name, self)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def unload!
|
|
22
|
+
if original_class = original_classes[demodulized_name]
|
|
23
|
+
Liquid.send(:remove_const, demodulized_name)
|
|
24
|
+
Liquid.const_set(demodulized_name, original_class)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def demodulized_name
|
|
29
|
+
@demodulized_name ||= self.name.split('::').last
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
def original_classes
|
|
34
|
+
@@original_classes ||= {}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module TagHighjacker
|
|
40
|
+
|
|
41
|
+
def load!
|
|
42
|
+
original_tag = Liquid::Template.tags[tag_name.to_s]
|
|
43
|
+
original_tags[tag_name] = original_tag unless original_tags.has_key?(tag_name) # avoid loosing reference to original class
|
|
44
|
+
Liquid::Template.register_tag(tag_name, self)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def unload!
|
|
48
|
+
Liquid::Template.register_tag(tag_name, original_tags[tag_name])
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def tag_name(name=nil)
|
|
52
|
+
@tag_name = name unless name.nil?
|
|
53
|
+
@tag_name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
protected
|
|
57
|
+
def original_tags
|
|
58
|
+
@@original_tags ||= {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
BASE_PATH = File.join(File.expand_path(File.dirname(__FILE__)), 'liquid_extensions')
|
|
64
|
+
|
|
65
|
+
# FIXME: Keep the original ForTag from being modified.
|
|
66
|
+
%w(if_tag unless_tag assign_tag variable).each do |mod|
|
|
67
|
+
require File.join(BASE_PATH, mod)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# FIXME: Keep the original ForTag from being modified.
|
|
71
|
+
ALL = [IfTag, UnlessTag, AssignTag, Variable]
|
|
72
|
+
|
|
73
|
+
class << self
|
|
74
|
+
|
|
75
|
+
def load!
|
|
76
|
+
ALL.each(&:load!)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def unload!
|
|
80
|
+
ALL.each(&:unload!)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
module LiquidExtensions
|
|
3
|
+
class AssignTag < Solid::Tag
|
|
4
|
+
extend TagHighjacker
|
|
5
|
+
|
|
6
|
+
tag_name :assign
|
|
7
|
+
|
|
8
|
+
def initialize(tag_name, assignment, tokens, context = {})
|
|
9
|
+
@assigned_variable, expression = assignment.split('=', 2)
|
|
10
|
+
@assigned_variable = @assigned_variable.strip
|
|
11
|
+
super(tag_name, expression, tokens, context)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def display(expression_result)
|
|
15
|
+
current_context.scopes.last[@assigned_variable] = expression_result
|
|
16
|
+
''
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
module LiquidExtensions
|
|
3
|
+
|
|
4
|
+
# This for block reimplementation is deliberately backward incompatible
|
|
5
|
+
# since all strange features supported by the original for loop like
|
|
6
|
+
# "reversed" or "limit: 20 offset: 40" are favourably replaced by pure ruby
|
|
7
|
+
# methods like `Array#reversed` or `Array#slice`
|
|
8
|
+
class ForTag < Solid::Block
|
|
9
|
+
extend TagHighjacker
|
|
10
|
+
|
|
11
|
+
tag_name :for
|
|
12
|
+
|
|
13
|
+
def initialize(tag_name, expression, tokens, context = {})
|
|
14
|
+
@variable_name, iterable_expression = expression.split(/\s+in\s+/, 2).map(&:strip)
|
|
15
|
+
super(tag_name, iterable_expression, tokens, context)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def display(collection)
|
|
19
|
+
forloop = loop_for(collection)
|
|
20
|
+
output = []
|
|
21
|
+
collection = [] unless collection.respond_to?(:each_with_index)
|
|
22
|
+
collection.each_with_index do |element, index|
|
|
23
|
+
current_context.stack do
|
|
24
|
+
current_context[@variable_name] = element.to_liquid
|
|
25
|
+
current_context['forloop'] = forloop
|
|
26
|
+
output << yield
|
|
27
|
+
forloop.inc!
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
output.join
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
protected
|
|
34
|
+
def loop_for(collection)
|
|
35
|
+
if paginated?(collection)
|
|
36
|
+
PaginatedForLoop.new(collection)
|
|
37
|
+
else
|
|
38
|
+
ForLoop.new(collection)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def paginated?(collection)
|
|
43
|
+
defined?(WillPaginate) and
|
|
44
|
+
(collection.singleton_class < WillPaginate::CollectionMethods or
|
|
45
|
+
collection.singleton_class < WillPaginate::Mongoid::CollectionMethods)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
class ForLoop < Liquid::Drop
|
|
51
|
+
|
|
52
|
+
def initialize(collection)
|
|
53
|
+
@collection = collection
|
|
54
|
+
@index0 = 0
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def index0
|
|
58
|
+
@index0
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def index
|
|
62
|
+
index0 + 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def rindex
|
|
66
|
+
length - index0
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def rindex0
|
|
70
|
+
length - index0 - 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def length
|
|
74
|
+
@collection.length
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def first
|
|
78
|
+
index0 == 0
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def last
|
|
82
|
+
index == length
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def inc!
|
|
86
|
+
@index0 += 1
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class PaginatedForLoop < ForLoop
|
|
92
|
+
|
|
93
|
+
def initialize(collection)
|
|
94
|
+
super
|
|
95
|
+
@index0 = collection.offset || 0
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
module LiquidExtensions
|
|
3
|
+
class IfTag < Liquid::Block
|
|
4
|
+
include Solid::Element
|
|
5
|
+
extend TagHighjacker
|
|
6
|
+
|
|
7
|
+
tag_name :if
|
|
8
|
+
|
|
9
|
+
def initialize(tag_name, expression, tokens, context = {})
|
|
10
|
+
@blocks = []
|
|
11
|
+
push_block!(expression)
|
|
12
|
+
super
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def render(context)
|
|
16
|
+
with_context(context) do
|
|
17
|
+
@blocks.each do |expression, blocks|
|
|
18
|
+
if expression.evaluate(context)
|
|
19
|
+
return render_all(blocks, context)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
''
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def unknown_tag(tag, expression, tokens, context = {})
|
|
27
|
+
if tag == 'elsif'
|
|
28
|
+
push_block!(expression)
|
|
29
|
+
elsif tag == 'else'
|
|
30
|
+
push_block!('true')
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
protected
|
|
35
|
+
|
|
36
|
+
def push_block!(expression)
|
|
37
|
+
block = []
|
|
38
|
+
@blocks.push([Solid::Parser.parse(expression), block])
|
|
39
|
+
@nodelist = block
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
module LiquidExtensions
|
|
3
|
+
class UnlessTag < Solid::LiquidExtensions::IfTag
|
|
4
|
+
|
|
5
|
+
tag_name :unless
|
|
6
|
+
|
|
7
|
+
def initialize(tag_name, expression, tokens, context = {})
|
|
8
|
+
super(tag_name, "!(#{expression})", tokens, context)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
module LiquidExtensions
|
|
3
|
+
class Variable < ::Liquid::Variable
|
|
4
|
+
extend ClassHighjacker
|
|
5
|
+
|
|
6
|
+
def initialize(markup, options={})
|
|
7
|
+
super
|
|
8
|
+
@expression = Solid::Parser.parse(@name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def render(context)
|
|
12
|
+
return '' if @name.nil?
|
|
13
|
+
value = @expression.evaluate(context)
|
|
14
|
+
apply_filters_on(value, context)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
protected
|
|
18
|
+
|
|
19
|
+
def apply_filters_on(value, context)
|
|
20
|
+
@filters.inject(value) do |output, filter|
|
|
21
|
+
filterargs = filter[1].to_a.collect do |a|
|
|
22
|
+
context[a]
|
|
23
|
+
end
|
|
24
|
+
begin
|
|
25
|
+
output = context.invoke(filter[0], output, *filterargs)
|
|
26
|
+
rescue FilterNotFound
|
|
27
|
+
raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Solid
|
|
2
|
+
module MethodWhitelist
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
METHODS_WHITELIST = {}
|
|
6
|
+
METHODS_BLACKLIST = {}
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
|
|
10
|
+
def allow(rules)
|
|
11
|
+
rules.each do |owner, method_names|
|
|
12
|
+
list = METHODS_WHITELIST[owner] ||= Set.new
|
|
13
|
+
[method_names].flatten.each do |method_name|
|
|
14
|
+
list.add(method_name.to_sym)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def deny(rules)
|
|
21
|
+
rules.each do |owner, method_names|
|
|
22
|
+
list = METHODS_BLACKLIST[owner] ||= Set.new
|
|
23
|
+
[method_names].flatten.each do |method_name|
|
|
24
|
+
list.add(method_name.to_sym)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
self
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def safely_respond_to?(object, method)
|
|
33
|
+
return false unless object.respond_to?(method, false)
|
|
34
|
+
method = object.method(method)
|
|
35
|
+
(!inherited?(object, method) || whitelisted?(method)) && !blacklisted?(method)
|
|
36
|
+
end
|
|
37
|
+
module_function :safely_respond_to?
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def whitelisted?(method)
|
|
42
|
+
METHODS_WHITELIST.has_key?(method.owner) && METHODS_WHITELIST[method.owner].include?(method.name)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def blacklisted?(method)
|
|
46
|
+
METHODS_BLACKLIST.has_key?(method.owner) && METHODS_BLACKLIST[method.owner].include?(method.name)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def inherited?(object, method)
|
|
50
|
+
method.owner != object.class && !object.methods(false).include?(method.name)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'default_security_rules')
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
class Solid::ModelDrop < Liquid::Drop
|
|
2
|
+
|
|
3
|
+
module ModelExtension
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
module ClassMethods
|
|
7
|
+
|
|
8
|
+
def to_drop
|
|
9
|
+
"#{self.name}Drop".constantize.new(current_scope || self)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class_attribute :dynamic_methods
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
|
|
20
|
+
def model(model_name=nil)
|
|
21
|
+
if model_name
|
|
22
|
+
@model_name = model_name
|
|
23
|
+
else
|
|
24
|
+
@model_name ||= self.name.gsub(/Drop$/, '')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def model_class
|
|
29
|
+
@model_class ||= self.model.to_s.camelize.constantize
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def immutable_method(method_name)
|
|
33
|
+
self.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
|
34
|
+
def #{method_name}_with_immutation(*args, &block)
|
|
35
|
+
self.dup.tap do |clone|
|
|
36
|
+
clone.#{method_name}_without_immutation(*args, &block)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
END_EVAL
|
|
40
|
+
self.alias_method_chain method_name, :immutation
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def respond(options={})
|
|
44
|
+
raise ArgumentError.new(":to option should be a Regexp") unless options[:to].is_a?(Regexp)
|
|
45
|
+
raise ArgumentError.new(":with option is mandatory") unless options[:with].present?
|
|
46
|
+
self.dynamic_methods ||= []
|
|
47
|
+
self.dynamic_methods += [[options[:to], options[:with]]]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def allow_scopes(*scopes)
|
|
51
|
+
@allowed_scopes = scopes
|
|
52
|
+
scopes.each do |scope_name|
|
|
53
|
+
self.class_eval <<-END_EVAL, __FILE__, __LINE__ + 1
|
|
54
|
+
def #{scope_name}(*args)
|
|
55
|
+
@scope = scope.public_send(:#{scope_name}, *args)
|
|
56
|
+
end
|
|
57
|
+
END_EVAL
|
|
58
|
+
self.immutable_method(scope_name)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
delegate :model_class, :to => 'self.class'
|
|
65
|
+
|
|
66
|
+
respond :to => /limited_to_(\d+)/, :with => :limit_to
|
|
67
|
+
|
|
68
|
+
def initialize(base_scope=nil, context=nil)
|
|
69
|
+
@scope = base_scope
|
|
70
|
+
@context ||= context
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def all
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def each(&block)
|
|
78
|
+
scope.each(&block)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def before_method(method_name, *args)
|
|
82
|
+
self.class.dynamic_methods.each do |pattern, method|
|
|
83
|
+
if match_data = pattern.match(method_name)
|
|
84
|
+
return self.send(method, *match_data[1..-1])
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
raise NoMethodError.new("undefined method `#{method_name}' for #{self.inspect}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
delegate :to_a, to: :each
|
|
91
|
+
delegate *(Array.public_instance_methods - self.public_instance_methods), to: :to_a
|
|
92
|
+
|
|
93
|
+
protected
|
|
94
|
+
|
|
95
|
+
def limit_to(size)
|
|
96
|
+
@scope = scope.limit(size.to_i)
|
|
97
|
+
end
|
|
98
|
+
immutable_method :limit_to
|
|
99
|
+
|
|
100
|
+
def scope
|
|
101
|
+
@scope ||= default_scope
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def default_scope
|
|
105
|
+
model_class
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def context
|
|
109
|
+
@context
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
if Rails.env.test? # Just for cleaner and simpler specs
|
|
115
|
+
def method_missing(name, *args, &block)
|
|
116
|
+
before_method(name.to_s)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|