blocks 2.8.0 → 3.0.0.rc1
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 +5 -13
- data/.gitignore +4 -4
- data/.travis.yml +51 -0
- data/Gemfile +15 -2
- data/Guardfile +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +3 -7
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/blocks.gemspec +18 -11
- data/gemfiles/Gemfile.rails-3-0-stable +13 -0
- data/gemfiles/Gemfile.rails-3-1-stable +13 -0
- data/gemfiles/Gemfile.rails-3-2-stable +13 -0
- data/gemfiles/Gemfile.rails-4-0-stable +12 -0
- data/gemfiles/Gemfile.rails-4-1-stable +12 -0
- data/gemfiles/Gemfile.rails-4-2-stable +12 -0
- data/gemfiles/Gemfile.rails-5-0-stable +10 -0
- data/gemfiles/Gemfile.rails-5-1-stable +10 -0
- data/lib/blocks.rb +41 -20
- data/lib/blocks/action_view_extensions/view_extensions.rb +26 -0
- data/lib/blocks/builders/block_definition.rb +71 -0
- data/lib/blocks/builders/builder.rb +159 -0
- data/lib/blocks/builders/hook_definition.rb +25 -0
- data/lib/blocks/experimental/builder_permissions.rb +52 -0
- data/lib/blocks/experimental/invalid_permissions_handler.rb +27 -0
- data/lib/blocks/renderers/abstract_renderer.rb +69 -0
- data/lib/blocks/renderers/adjacent_blocks_renderer.rb +15 -0
- data/lib/blocks/renderers/block_placeholder.rb +13 -0
- data/lib/blocks/renderers/block_renderer.rb +13 -0
- data/lib/blocks/renderers/block_with_hooks_renderer.rb +35 -0
- data/lib/blocks/renderers/collection_renderer.rb +17 -0
- data/lib/blocks/renderers/nesting_blocks_renderer.rb +24 -0
- data/lib/blocks/renderers/partial_renderer.rb +18 -0
- data/lib/blocks/renderers/renderer.rb +43 -0
- data/lib/blocks/renderers/runtime_context.rb +193 -0
- data/lib/blocks/renderers/wrapper_renderer.rb +19 -0
- data/lib/blocks/utilities/configurator.rb +26 -0
- data/lib/blocks/utilities/dynamic_configuration.rb +71 -0
- data/lib/blocks/utilities/hash_with_caller.rb +73 -0
- data/lib/blocks/utilities/hash_with_render_strategy.rb +47 -0
- data/lib/blocks/utilities/options_set.rb +95 -0
- data/lib/blocks/version.rb +1 -1
- metadata +70 -80
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/README.rdoc +0 -99
- data/blocks_render_order.graffle +0 -1397
- data/blocks_render_order.png +0 -0
- data/lib/blocks/base.rb +0 -580
- data/lib/blocks/container.rb +0 -5
- data/lib/blocks/controller_additions.rb +0 -9
- data/lib/blocks/view_additions.rb +0 -10
- data/rails/init.rb +0 -1
- data/spec/blocks/base_spec.rb +0 -641
- data/spec/blocks/blocks_spec.rb +0 -12
- data/spec/blocks/view_additions_spec.rb +0 -22
- data/spec/spec_helper.rb +0 -22
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
|
3
|
+
module Blocks
|
4
|
+
module ViewExtensions
|
5
|
+
def blocks
|
6
|
+
@blocks ||= Blocks.builder_class.new(self)
|
7
|
+
end
|
8
|
+
|
9
|
+
def render_with_overrides(*args, &block)
|
10
|
+
options = args.extract_options!.with_indifferent_access
|
11
|
+
partial = options.delete(:partial) || options.delete(:template) || args.first
|
12
|
+
if builder = options.delete(:builder)
|
13
|
+
builder.view = self
|
14
|
+
# builder = builder.clone
|
15
|
+
# TODO: figure out what to do here
|
16
|
+
else
|
17
|
+
builder = Blocks.builder_class.new(self, options)
|
18
|
+
end
|
19
|
+
builder.render_with_overrides(options.merge(partial: partial), &block)
|
20
|
+
end
|
21
|
+
alias_method :with_template, :render_with_overrides
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
ActionView::Base.send :include, Blocks::ViewExtensions
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Blocks
|
2
|
+
class BlockDefinition < OptionsSet
|
3
|
+
attr_accessor :options_set,
|
4
|
+
:skip_content,
|
5
|
+
:skip_completely,
|
6
|
+
*HookDefinition::HOOKS.map {|hook| "#{hook}_hooks" }
|
7
|
+
|
8
|
+
|
9
|
+
def initialize(*args, &block)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def skip(completely=false)
|
14
|
+
self.skip_content = true
|
15
|
+
self.skip_completely = completely
|
16
|
+
end
|
17
|
+
|
18
|
+
def skip_content?
|
19
|
+
!!skip_content
|
20
|
+
end
|
21
|
+
|
22
|
+
def skip_completely?
|
23
|
+
!!skip_completely
|
24
|
+
end
|
25
|
+
|
26
|
+
def hooks_for(hook_name)
|
27
|
+
self.send("#{hook_name}_hooks")
|
28
|
+
end
|
29
|
+
|
30
|
+
HookDefinition::HOOKS.each do |hook|
|
31
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
32
|
+
def #{hook}_hooks
|
33
|
+
@#{hook}_hooks ||= []
|
34
|
+
end
|
35
|
+
RUBY
|
36
|
+
|
37
|
+
define_method(hook) do |*args, &block|
|
38
|
+
HookDefinition.new(self, hook, *args, &block).tap do |definition|
|
39
|
+
hooks_for(hook) << definition
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_s
|
45
|
+
description = []
|
46
|
+
description << super
|
47
|
+
options = [
|
48
|
+
runtime_options,
|
49
|
+
standard_options,
|
50
|
+
default_options
|
51
|
+
].detect(&:render_strategy)
|
52
|
+
|
53
|
+
strategy = options.try(:render_strategy)
|
54
|
+
render_strategy_name = if strategy == HashWithRenderStrategy::RENDER_WITH_PROXY
|
55
|
+
caller_id = options.callers[HashWithRenderStrategy::RENDER_WITH_PROXY]
|
56
|
+
"proxy block \"#{options[strategy]}\""
|
57
|
+
elsif strategy == HashWithRenderStrategy::RENDER_WITH_BLOCK
|
58
|
+
caller_id = options.callers[HashWithRenderStrategy::RENDER_WITH_BLOCK]
|
59
|
+
"block defined at #{options[strategy].source_location}"
|
60
|
+
elsif strategy == HashWithRenderStrategy::RENDER_WITH_PARTIAL
|
61
|
+
caller_id = options.callers[HashWithRenderStrategy::RENDER_WITH_PARTIAL]
|
62
|
+
"partial \"#{options[strategy]}\""
|
63
|
+
end
|
64
|
+
if render_strategy_name
|
65
|
+
description << "Renders with #{render_strategy_name} [#{caller_id}]"
|
66
|
+
end
|
67
|
+
|
68
|
+
description.join("\n")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
require 'call_with_params'
|
2
|
+
|
3
|
+
module Blocks
|
4
|
+
class Builder
|
5
|
+
include CallWithParams
|
6
|
+
|
7
|
+
# A pointer to the view context
|
8
|
+
attr_accessor :view
|
9
|
+
|
10
|
+
# A HashWithIndifferentAccess of block names to BlockDefinition mappings
|
11
|
+
attr_accessor :block_definitions
|
12
|
+
|
13
|
+
# Options provided during initialization of builder
|
14
|
+
attr_accessor :options_set
|
15
|
+
|
16
|
+
delegate :content_tag, to: :view
|
17
|
+
|
18
|
+
delegate :render,
|
19
|
+
:render_with_overrides,
|
20
|
+
:deferred_render,
|
21
|
+
to: :renderer
|
22
|
+
|
23
|
+
delegate :runtime_options,
|
24
|
+
:standard_options,
|
25
|
+
:default_options,
|
26
|
+
to: :options_set
|
27
|
+
|
28
|
+
CONTENT_TAG_WRAPPER_BLOCK = :content_tag_wrapper
|
29
|
+
|
30
|
+
def initialize(view, options={})
|
31
|
+
self.view = view
|
32
|
+
self.block_definitions = HashWithIndifferentAccess.new do |hash, key|
|
33
|
+
hash[key] = BlockDefinition.new(key); hash[key]
|
34
|
+
end
|
35
|
+
self.options_set = OptionsSet.new("Builder Options", options)
|
36
|
+
define_helper_blocks
|
37
|
+
end
|
38
|
+
|
39
|
+
def renderer
|
40
|
+
@renderer ||= Blocks.renderer_class.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def block_for(block_name)
|
44
|
+
block_definitions[block_name] if block_defined?(block_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def block_defined?(block_name)
|
48
|
+
block_definitions.key?(block_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
def define_each(collection, block_name_proc, *args, &block)
|
52
|
+
collection.map do |object|
|
53
|
+
define(call_with_params(block_name_proc, object, *args), object, *args, &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Define a block, unless a block by the same name is already defined.
|
58
|
+
# <%= blocks.define :some_block_name, :parameter1 => "1", :parameter2 => "2" do |options| %>
|
59
|
+
# <%= options[:parameter1] %> and <%= options[:parameter2] %>
|
60
|
+
# <% end %>
|
61
|
+
#
|
62
|
+
# Options:
|
63
|
+
# [+name+]
|
64
|
+
# The name of the block being defined (either a string or a symbol)
|
65
|
+
# [+options+]
|
66
|
+
# The default options for the block definition. Any or all of these options may be overridden by
|
67
|
+
# whomever calls "blocks.render" on this block.
|
68
|
+
# [+block+]
|
69
|
+
# The block that is to be rendered when "blocks.render" is called for this block.
|
70
|
+
def define(*args, &block)
|
71
|
+
options = args.extract_options!
|
72
|
+
|
73
|
+
if args.first
|
74
|
+
name = args.shift
|
75
|
+
block_definitions[name].tap do |block_definition|
|
76
|
+
block_definition.add_options options, &block
|
77
|
+
end
|
78
|
+
else
|
79
|
+
BlockDefinition.new(options, &block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Define a block, replacing an existing block by the same name if it is already defined.
|
84
|
+
# <%= blocks.define :some_block_name, :parameter1 => "1", :parameter2 => "2" do |options| %>
|
85
|
+
# <%= options[:parameter1] %> and <%= options[:parameter2] %>
|
86
|
+
# <% end %>
|
87
|
+
#
|
88
|
+
# <%= blocks.replace :some_block_name, :parameter3 => "3", :parameter4 => "4" do |options| %>
|
89
|
+
# <%= options[:parameter3] %> and <%= options[:parameter4] %>
|
90
|
+
# <% end %>
|
91
|
+
# Options:
|
92
|
+
# [+name+]
|
93
|
+
# The name of the block being defined (either a string or a symbol)
|
94
|
+
# [+options+]
|
95
|
+
# The default options for the block definition. Any or all of these options may be overridden by
|
96
|
+
# whomever calls "blocks.render" on this block.
|
97
|
+
# [+block+]
|
98
|
+
# The block that is to be rendered when "blocks.render" is called for this block.
|
99
|
+
def replace(name, options={}, &block)
|
100
|
+
block_definitions.delete(name)
|
101
|
+
define(name, options, &block)
|
102
|
+
end
|
103
|
+
|
104
|
+
def skip(name, completely=false)
|
105
|
+
block_definitions[name].skip(completely)
|
106
|
+
end
|
107
|
+
|
108
|
+
def skip_completely(name)
|
109
|
+
skip(name, true)
|
110
|
+
end
|
111
|
+
|
112
|
+
HookDefinition::HOOKS.each do |hook|
|
113
|
+
define_method(hook) do |name, options={}, &block|
|
114
|
+
block_definitions[name].send(hook, options, &block)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# TODO: move this logic elsewhere
|
119
|
+
def concatenating_merge(options, options2, *args)
|
120
|
+
options = call_each_hash_value_with_params(options, *args) || {}
|
121
|
+
options2 = call_each_hash_value_with_params(options2, *args) || {}
|
122
|
+
|
123
|
+
|
124
|
+
options.symbolize_keys.merge(options2.symbolize_keys) do |key, v1, v2|
|
125
|
+
if v1.is_a?(String) && v2.is_a?(String)
|
126
|
+
"#{v1} #{v2}"
|
127
|
+
else
|
128
|
+
v2
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
protected
|
134
|
+
|
135
|
+
# TODO: move this logic elsewhere
|
136
|
+
def define_helper_blocks
|
137
|
+
define CONTENT_TAG_WRAPPER_BLOCK, defaults: { wrapper_tag: :div } do |content_block, *args|
|
138
|
+
options = args.extract_options!
|
139
|
+
wrapper_options = if options[:wrapper_html_option]
|
140
|
+
if options[:wrapper_html_option].is_a?(Array)
|
141
|
+
wrapper_attribute = nil
|
142
|
+
options[:wrapper_html_option].each do |attribute|
|
143
|
+
if options[attribute].present?
|
144
|
+
wrapper_attribute = attribute
|
145
|
+
break
|
146
|
+
end
|
147
|
+
end
|
148
|
+
options[wrapper_attribute]
|
149
|
+
else
|
150
|
+
options[options[:wrapper_html_option]]
|
151
|
+
end
|
152
|
+
end
|
153
|
+
content_tag options[:wrapper_tag],
|
154
|
+
concatenating_merge(options[:wrapper_html], wrapper_options, *args, options),
|
155
|
+
&content_block
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Blocks
|
2
|
+
class HookDefinition < OptionsSet
|
3
|
+
BEFORE_ALL = :before_all
|
4
|
+
AROUND_ALL = :around_all
|
5
|
+
BEFORE = :before
|
6
|
+
AROUND = :around
|
7
|
+
SURROUND = :surround
|
8
|
+
PREPEND = :prepend
|
9
|
+
APPEND = :append
|
10
|
+
AFTER = :after
|
11
|
+
AFTER_ALL = :after_all
|
12
|
+
|
13
|
+
HOOKS = [BEFORE_ALL, BEFORE, PREPEND,
|
14
|
+
AROUND_ALL, AROUND, SURROUND,
|
15
|
+
APPEND, AFTER, AFTER_ALL]
|
16
|
+
|
17
|
+
attr_accessor :block_definition, :hook_type
|
18
|
+
|
19
|
+
def initialize(block_definition, hook_type, *args, &block)
|
20
|
+
self.block_definition = block_definition
|
21
|
+
self.hook_type = hook_type
|
22
|
+
super "#{hook_type} #{block_definition.name} options", *args, &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# WIP
|
2
|
+
module Blocks
|
3
|
+
module BuilderPermissions
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
METHODS_TO_PROTECT = [:render, :define, :deferred_render, :replace, :skip]
|
7
|
+
|
8
|
+
included do
|
9
|
+
attr_writer :permitted_blocks
|
10
|
+
attr_accessor :allow_all_blocks
|
11
|
+
attr_accessor :allow_anonymous_blocks
|
12
|
+
|
13
|
+
METHODS_TO_PROTECT.each do |method_name|
|
14
|
+
alias_method "original_#{method_name}", method_name
|
15
|
+
define_method(method_name) do |*args, &block|
|
16
|
+
if permitted?(args.first)
|
17
|
+
self.send("original_#{method_name}", *args, &block)
|
18
|
+
else
|
19
|
+
InvalidPermissionsHandler.build(method_name, args.first)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def restrict_blocks
|
26
|
+
self.allow_all_blocks = false
|
27
|
+
end
|
28
|
+
|
29
|
+
def permitted_blocks
|
30
|
+
@permitted_blocks ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def permit(*names)
|
34
|
+
self.permitted_blocks += names
|
35
|
+
end
|
36
|
+
|
37
|
+
def permit_all
|
38
|
+
self.allow_all_blocks = true
|
39
|
+
end
|
40
|
+
|
41
|
+
def permit_anonymous_blocks
|
42
|
+
self.allow_anonymous_blocks = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def permitted?(name)
|
46
|
+
allow_all_blocks ||
|
47
|
+
(block_definitions[name].try(:anonymous) && allow_anonymous_blocks) ||
|
48
|
+
!name.respond_to?(:to_sym) ||
|
49
|
+
permitted_blocks.any? {|e| e.to_sym == name.to_sym }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# WIP
|
2
|
+
module Blocks
|
3
|
+
class InvalidPermissionsHandler
|
4
|
+
LOG = :log
|
5
|
+
RAISE = :raise
|
6
|
+
|
7
|
+
def self.build(method_name, block_name)
|
8
|
+
message = "Cannot #{method_name} #{block_name}; #{block_name} is not in the permitted_blocks list"
|
9
|
+
new(message)
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(message)
|
14
|
+
send("handle_#{Blocks.invalid_permissions_approach}", message)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def handle_log(message)
|
20
|
+
Rails.logger.info message
|
21
|
+
end
|
22
|
+
|
23
|
+
def handle_raise(message)
|
24
|
+
raise message
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Blocks
|
2
|
+
class AbstractRenderer
|
3
|
+
RENDERERS = [
|
4
|
+
AdjacentBlocksRenderer,
|
5
|
+
BlockRenderer,
|
6
|
+
BlockWithHooksRenderer,
|
7
|
+
CollectionRenderer,
|
8
|
+
PartialRenderer,
|
9
|
+
NestingBlocksRenderer,
|
10
|
+
WrapperRenderer
|
11
|
+
]
|
12
|
+
|
13
|
+
attr_accessor :main_renderer
|
14
|
+
|
15
|
+
delegate :builder, *(RENDERERS.map {|r| r.to_s.demodulize.underscore }), to: :main_renderer
|
16
|
+
delegate :block_definitions, :block_for, :view, to: :builder
|
17
|
+
delegate :with_output_buffer, :output_buffer, to: :view
|
18
|
+
|
19
|
+
def initialize(main_renderer=nil)
|
20
|
+
self.main_renderer = main_renderer
|
21
|
+
end
|
22
|
+
|
23
|
+
def render(*args)
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def capture(*args, &block)
|
28
|
+
without_haml_interference do
|
29
|
+
if block.arity > 0
|
30
|
+
args = args[0, block.arity]
|
31
|
+
end
|
32
|
+
|
33
|
+
with_output_buffer do
|
34
|
+
output_buffer << view.capture(*args, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
# Complete hack to get around issues with Haml
|
42
|
+
# Haml does some hacking to ActionView's with_output_buffer and
|
43
|
+
# output_buffer. In doing so, they make certain assumptions about
|
44
|
+
# the layout and the view template. The Blocks gem doesn't capture
|
45
|
+
# blocks immediately but rather stores them for later capturing.
|
46
|
+
# This can produce an issue if a block that is stored was in Haml
|
47
|
+
# but the Layout is in ERB. Haml will think that any blocks it
|
48
|
+
# captures while rendering the layout will be in ERB format. However,
|
49
|
+
# the block would need to be captured in Haml using a Haml buffer.
|
50
|
+
# This workaround accomplishes that.
|
51
|
+
def without_haml_interference(&block)
|
52
|
+
if view.instance_variables.include?(:@haml_buffer)
|
53
|
+
haml_buffer = view.instance_variable_get(:@haml_buffer)
|
54
|
+
if haml_buffer
|
55
|
+
was_active = haml_buffer.active?
|
56
|
+
haml_buffer.active = false
|
57
|
+
else
|
58
|
+
haml_buffer = Haml::Buffer.new(nil, Haml::Options.new.for_buffer)
|
59
|
+
haml_buffer.active = false
|
60
|
+
kill_buffer = true
|
61
|
+
view.instance_variable_set(:@haml_buffer, haml_buffer)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
yield
|
65
|
+
ensure
|
66
|
+
haml_buffer.active = was_active if haml_buffer
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Blocks
|
2
|
+
class AdjacentBlocksRenderer < AbstractRenderer
|
3
|
+
def render(hook, runtime_context)
|
4
|
+
block = block_for(runtime_context.block_name)
|
5
|
+
if block
|
6
|
+
hooks = block.hooks_for hook
|
7
|
+
hooks = hooks.reverse if hook.to_s.index("before") == 0 || hook.to_s.index("prepend") == 0
|
8
|
+
hooks.each do |block_definition|
|
9
|
+
hook_runtime_context = runtime_context.extend_to_block_definition(block_definition)
|
10
|
+
block_renderer.render(hook_runtime_context)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|