blocks 3.0.2 → 4.0.0
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 -5
- data/.ruby-version +1 -1
- data/.travis.yml +61 -17
- data/CHANGELOG.rdoc +27 -0
- data/Gemfile +11 -10
- data/Guardfile +1 -0
- data/README.md +2 -0
- data/bin/deploy_docs +1 -1
- data/blocks.gemspec +3 -1
- data/docs/_includes/configuration.md +3 -6
- data/docs/_includes/defining.md +7 -7
- data/docs/_includes/{introduction.md → features.md} +2 -2
- data/docs/_includes/helper-blocks.md +5 -0
- data/docs/_includes/helper-blocks/content_tag.md +44 -0
- data/docs/_includes/installation.md +16 -2
- data/docs/_includes/overview.md +21 -0
- data/docs/_includes/templating.md +1 -1
- data/docs/_includes/templating/bootstrap_4_cards.md +9 -9
- data/docs/_layouts/slate.html +11 -9
- data/docs/index.md +3 -1
- data/gemfiles/Gemfile.rails-3-0-stable +1 -0
- data/gemfiles/Gemfile.rails-3-1-stable +1 -0
- data/gemfiles/Gemfile.rails-3-2-stable +1 -0
- data/gemfiles/Gemfile.rails-4-0-stable +2 -1
- data/gemfiles/Gemfile.rails-4-1-stable +2 -1
- data/gemfiles/Gemfile.rails-4-2-stable +2 -1
- data/gemfiles/Gemfile.rails-5-0-stable +4 -3
- data/gemfiles/Gemfile.rails-5-1-stable +4 -3
- data/gemfiles/Gemfile.rails-5-2-stable +13 -0
- data/lib/blocks.rb +37 -29
- data/lib/blocks/builders/block_definition.rb +45 -43
- data/lib/blocks/builders/builder.rb +96 -60
- data/lib/blocks/builders/hook_definition.rb +19 -4
- data/lib/blocks/engine.rb +14 -0
- data/lib/blocks/helpers/controller_extensions.rb +13 -0
- data/lib/blocks/helpers/haml_capture.rb +44 -0
- data/lib/blocks/{action_view_extensions → helpers}/view_extensions.rb +10 -4
- data/lib/blocks/renderers/adjacent_blocks_renderer.rb +9 -7
- data/lib/blocks/renderers/block_placeholder.rb +2 -0
- data/lib/blocks/renderers/block_renderer.rb +26 -5
- data/lib/blocks/renderers/block_with_hooks_renderer.rb +29 -19
- data/lib/blocks/renderers/collection_renderer.rb +18 -6
- data/lib/blocks/renderers/nesting_blocks_renderer.rb +9 -11
- data/lib/blocks/renderers/partial_renderer.rb +16 -14
- data/lib/blocks/renderers/renderer.rb +9 -24
- data/lib/blocks/renderers/runtime_context.rb +175 -147
- data/lib/blocks/renderers/wrapper_renderer.rb +21 -10
- data/lib/blocks/utilities/configurator.rb +30 -6
- data/lib/blocks/utilities/hash_with_caller.rb +36 -32
- data/lib/blocks/utilities/hash_with_render_strategy.rb +67 -19
- data/lib/blocks/utilities/options_set.rb +38 -63
- data/lib/blocks/version.rb +3 -1
- metadata +23 -22
- data/docs/_includes/wip.md +0 -34
- data/lib/blocks/experimental/builder_permissions.rb +0 -52
- data/lib/blocks/experimental/invalid_permissions_handler.rb +0 -27
- data/lib/blocks/renderers/abstract_renderer.rb +0 -69
- data/lib/blocks/utilities/dynamic_configuration.rb +0 -71
@@ -1,5 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
|
-
class HookDefinition <
|
4
|
+
class HookDefinition < HashWithRenderStrategy
|
3
5
|
BEFORE_ALL = :before_all
|
4
6
|
AROUND_ALL = :around_all
|
5
7
|
BEFORE = :before
|
@@ -14,12 +16,25 @@ module Blocks
|
|
14
16
|
AROUND_ALL, AROUND, SURROUND,
|
15
17
|
APPEND, AFTER, AFTER_ALL]
|
16
18
|
|
17
|
-
attr_accessor :block_definition, :hook_type
|
19
|
+
attr_accessor :block_definition, :hook_type, :name, :runtime_block, :block_to_render
|
18
20
|
|
19
|
-
def initialize(block_definition, hook_type,
|
21
|
+
def initialize(block_definition, hook_type, options, &block)
|
20
22
|
self.block_definition = block_definition
|
21
23
|
self.hook_type = hook_type
|
22
|
-
super
|
24
|
+
super &nil
|
25
|
+
reverse_merge! options
|
26
|
+
self.block_to_render = self.delete(:render) || self[RENDER_WITH_PROXY]
|
27
|
+
self.name = "#{block_to_render.to_s + ' block ' if block_to_render}#{hook_type} #{block_definition.name} options"
|
28
|
+
# name = self[:render] || "#{hook_type} #{block_definition.name} options"
|
29
|
+
# super name, *args, &block
|
30
|
+
|
31
|
+
if block
|
32
|
+
if render_strategy
|
33
|
+
self.runtime_block = block
|
34
|
+
else
|
35
|
+
reverse_merge! block: block
|
36
|
+
end
|
37
|
+
end
|
23
38
|
end
|
24
39
|
end
|
25
40
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails/engine'
|
4
|
+
require 'rails/version'
|
5
|
+
|
6
|
+
module Blocks
|
7
|
+
class Railtie < Rails::Engine
|
8
|
+
if Rails::VERSION::MAJOR >= 4
|
9
|
+
config.eager_load_namespaces << Blocks
|
10
|
+
else
|
11
|
+
config.eager_load_paths += Blocks.autoloads.values
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'action_controller'
|
4
|
+
|
5
|
+
module Blocks
|
6
|
+
module ControllerExtensions
|
7
|
+
def blocks
|
8
|
+
@blocks ||= Blocks.builder_class.new(view_context)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
ActionController::Base.send :include, Blocks::ControllerExtensions
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Complete hack to get around issues with Haml
|
4
|
+
# Haml does some hacking to ActionView's with_output_buffer and
|
5
|
+
# output_buffer. In doing so, they make certain assumptions about
|
6
|
+
# the layout and the view template. (See:
|
7
|
+
# https://github.com/haml/haml/blob/master/lib/haml/helpers/action_view_mods.rb#L11,
|
8
|
+
# and https://github.com/haml/haml/blob/master/lib/haml/helpers.rb#L389)
|
9
|
+
# The Blocks gem doesn't capture
|
10
|
+
# blocks immediately but rather stores them for later capturing.
|
11
|
+
# This can produce an issue if a block that is stored was defined in Haml
|
12
|
+
# but the Layout is in ERB. Haml will think that any blocks it
|
13
|
+
# captures while rendering the layout will be in ERB format. However,
|
14
|
+
# the block would need to be captured in Haml using a Haml buffer.
|
15
|
+
# This workaround accomplishes that.
|
16
|
+
module Blocks
|
17
|
+
module HamlCapture
|
18
|
+
def initialize(view, *)
|
19
|
+
super
|
20
|
+
if defined?(::Haml) && !view.instance_variables.include?(:@haml_buffer)
|
21
|
+
class << view
|
22
|
+
include Haml::Helpers
|
23
|
+
end
|
24
|
+
view.init_haml_helpers
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def capture(*)
|
29
|
+
old_haml_buffer = view.instance_variable_get(:@haml_buffer)
|
30
|
+
if old_haml_buffer
|
31
|
+
was_active = old_haml_buffer.active?
|
32
|
+
old_haml_buffer.active = false
|
33
|
+
else
|
34
|
+
haml_buffer = Haml::Buffer.new(nil, Haml::Options.new.for_buffer)
|
35
|
+
haml_buffer.active = false
|
36
|
+
view.instance_variable_set(:@haml_buffer, haml_buffer)
|
37
|
+
end
|
38
|
+
super
|
39
|
+
ensure
|
40
|
+
old_haml_buffer.active = was_active if old_haml_buffer
|
41
|
+
view.instance_variable_set(:@haml_buffer, old_haml_buffer)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'action_view'
|
2
4
|
|
3
5
|
module Blocks
|
@@ -7,20 +9,24 @@ module Blocks
|
|
7
9
|
end
|
8
10
|
|
9
11
|
def render_with_overrides(*args, &block)
|
10
|
-
options = args.extract_options
|
12
|
+
options = args.extract_options!
|
11
13
|
partial = options.delete(:partial) || options.delete(:template) || args.first
|
12
14
|
if builder = options.delete(:builder)
|
13
15
|
builder.view = self
|
14
16
|
# builder = builder.clone
|
15
17
|
# TODO: figure out what to do here
|
16
18
|
else
|
17
|
-
# TODO: options shouldn't have to be passed both here and to the
|
19
|
+
# TODO: options shouldn't have to be passed both here and to the render call below - need it to be just one place
|
18
20
|
builder = Blocks.builder_class.new(self, options)
|
19
21
|
end
|
20
|
-
builder.
|
22
|
+
builder.render(options.merge(partial: partial), &block)
|
21
23
|
end
|
22
|
-
alias_method :with_template, :render_with_overrides
|
23
24
|
|
25
|
+
# <b>DEPRECATED:</b> Please use <tt>render_with_overrides</tt> instead.
|
26
|
+
def with_template(*args, &block)
|
27
|
+
warn "[DEPRECATION] `with_template` is deprecated. Please use `render_with_overrides` instead."
|
28
|
+
render_with_overrides(*args, &block)
|
29
|
+
end
|
24
30
|
end
|
25
31
|
end
|
26
32
|
|
@@ -1,13 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
|
-
class AdjacentBlocksRenderer
|
3
|
-
def render(hook, runtime_context)
|
4
|
-
|
5
|
-
if
|
6
|
-
|
4
|
+
class AdjacentBlocksRenderer
|
5
|
+
def self.render(hook, runtime_context)
|
6
|
+
hooks = runtime_context.hooks_for hook
|
7
|
+
if hooks.present?
|
8
|
+
output_buffer = runtime_context.output_buffer
|
7
9
|
hooks = hooks.reverse if hook.to_s.index("before") == 0 || hook.to_s.index("prepend") == 0
|
8
10
|
hooks.each do |hook_definition|
|
9
|
-
hook_runtime_context = runtime_context.
|
10
|
-
|
11
|
+
hook_runtime_context = runtime_context.extend_from_definition(hook_definition, &hook_definition.runtime_block)
|
12
|
+
output_buffer << BlockWithHooksRenderer.render(hook_runtime_context)
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
@@ -1,12 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
|
-
class BlockRenderer
|
3
|
-
def render(
|
4
|
+
class BlockRenderer
|
5
|
+
def self.render(runtime_context)
|
6
|
+
output_buffer = runtime_context.output_buffer
|
4
7
|
render_item = runtime_context.render_item
|
8
|
+
# TODO: should convert anything that passes a runtime context back to the user to a normal hash
|
5
9
|
if render_item.is_a?(String) || render_item.respond_to?(:to_partial_path)
|
6
|
-
output_buffer <<
|
10
|
+
output_buffer << PartialRenderer.render(runtime_context)
|
11
|
+
|
12
|
+
# TODO: should the same approach be utilized for Procs & Methods?
|
13
|
+
# Can the capture method both invoke a method and pass a block to it?
|
7
14
|
elsif render_item.is_a?(Proc)
|
8
|
-
|
9
|
-
|
15
|
+
# TODO: should the runtime_context be the first argument?
|
16
|
+
args = runtime_context.runtime_args + [runtime_context.to_hash]
|
17
|
+
if runtime_context.runtime_block && runtime_context.runtime_block != render_item
|
18
|
+
args = args.unshift runtime_context.runtime_block
|
19
|
+
end
|
20
|
+
output_buffer << runtime_context.capture(*args, &render_item)
|
21
|
+
|
22
|
+
elsif render_item.is_a?(Method)
|
23
|
+
# TODO: should the runtime_context be the first argument?
|
24
|
+
args = runtime_context.runtime_args + [runtime_context.to_hash]
|
25
|
+
if render_item.arity >= 0
|
26
|
+
args = args[0, render_item.arity]
|
27
|
+
end
|
28
|
+
output_buffer << runtime_context.capture do
|
29
|
+
render_item.call(*args, &runtime_context.runtime_block)
|
30
|
+
end
|
10
31
|
end
|
11
32
|
end
|
12
33
|
end
|
@@ -1,35 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
4
|
# TODO: Make this render order customizable
|
3
|
-
class BlockWithHooksRenderer
|
4
|
-
def render(
|
5
|
-
with_output_buffer do
|
6
|
-
runtime_context
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
5
|
+
class BlockWithHooksRenderer
|
6
|
+
def self.render(runtime_context)
|
7
|
+
runtime_context.with_output_buffer do
|
8
|
+
if runtime_context.skip_completely
|
9
|
+
# noop
|
10
|
+
elsif runtime_context.hooks_or_wrappers_present?
|
11
|
+
render_hooks(HookDefinition::BEFORE_ALL, runtime_context)
|
12
|
+
render_hooks(HookDefinition::AROUND_ALL, runtime_context) do
|
13
|
+
WrapperRenderer.render(runtime_context.wrap_all, :wrap_all, runtime_context) do
|
14
|
+
CollectionRenderer.render(runtime_context) do |runtime_context|
|
15
|
+
WrapperRenderer.render(runtime_context.wrap_each, :wrap_each, runtime_context) do
|
16
|
+
render_hooks(HookDefinition::AROUND, runtime_context) do
|
17
|
+
render_hooks(HookDefinition::BEFORE, runtime_context)
|
15
18
|
if !runtime_context.skip_content
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
WrapperRenderer.render(runtime_context.wrap_with, :wrapper, runtime_context) do
|
20
|
+
render_hooks(HookDefinition::SURROUND, runtime_context) do
|
21
|
+
render_hooks(HookDefinition::PREPEND, runtime_context)
|
22
|
+
BlockRenderer.render(runtime_context)
|
23
|
+
render_hooks(HookDefinition::APPEND, runtime_context)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
end
|
24
|
-
|
27
|
+
render_hooks(HookDefinition::AFTER, runtime_context)
|
25
28
|
end
|
26
29
|
end
|
27
30
|
end
|
28
31
|
end
|
29
32
|
end
|
30
|
-
|
33
|
+
render_hooks(HookDefinition::AFTER_ALL, runtime_context)
|
34
|
+
elsif !runtime_context.skip_content
|
35
|
+
BlockRenderer.render(runtime_context)
|
31
36
|
end
|
32
37
|
end
|
33
38
|
end
|
39
|
+
|
40
|
+
def self.render_hooks(hook_type, runtime_context, &block)
|
41
|
+
renderer_class = block ? NestingBlocksRenderer : AdjacentBlocksRenderer
|
42
|
+
renderer_class.render(hook_type, runtime_context, &block)
|
43
|
+
end
|
34
44
|
end
|
35
45
|
end
|
@@ -1,14 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
|
-
class CollectionRenderer
|
3
|
-
def render(runtime_context)
|
4
|
+
class CollectionRenderer
|
5
|
+
def self.render(runtime_context)
|
4
6
|
collection = runtime_context.collection
|
5
7
|
if collection
|
6
|
-
|
8
|
+
original_collection_item = runtime_context.collection_item
|
9
|
+
original_collection_item_index = runtime_context.collection_item_index
|
10
|
+
original_runtime_args = runtime_context.runtime_args
|
7
11
|
collection.each_with_index do |item, index|
|
8
|
-
|
9
|
-
|
10
|
-
|
12
|
+
runtime_context.collection_item = item
|
13
|
+
runtime_context.collection_item_index = index
|
14
|
+
|
15
|
+
if Blocks.collection_item_passed_to_block_as_first_arg
|
16
|
+
runtime_context.runtime_args = [item, *original_runtime_args]
|
17
|
+
end
|
18
|
+
|
19
|
+
yield runtime_context
|
11
20
|
end
|
21
|
+
runtime_context.collection_item = original_collection_item
|
22
|
+
runtime_context.collection_item_index = original_collection_item_index
|
23
|
+
runtime_context.runtime_args = original_runtime_args
|
12
24
|
else
|
13
25
|
yield runtime_context
|
14
26
|
end
|
@@ -1,25 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
|
-
class NestingBlocksRenderer
|
3
|
-
def render(hook, runtime_context)
|
4
|
-
|
5
|
-
hooks = block.try(:hooks_for, hook)
|
4
|
+
class NestingBlocksRenderer
|
5
|
+
def self.render(hook, runtime_context)
|
6
|
+
hooks = runtime_context.hooks_for(hook)
|
6
7
|
if hooks.present?
|
7
|
-
content_block = Proc.new { with_output_buffer { yield } }
|
8
|
+
content_block = Proc.new { runtime_context.with_output_buffer { yield } }
|
8
9
|
|
9
10
|
renderer = hooks.inject(content_block) do |inner_content, hook_definition|
|
10
|
-
hook_runtime_context = runtime_context.
|
11
|
+
hook_runtime_context = runtime_context.extend_from_definition(hook_definition, &inner_content)
|
11
12
|
Proc.new {
|
12
|
-
|
13
|
-
block_renderer.render(inner_content, hook_runtime_context)
|
14
|
-
end
|
13
|
+
BlockWithHooksRenderer.render(hook_runtime_context)
|
15
14
|
}
|
16
15
|
end
|
17
16
|
|
18
|
-
output_buffer << renderer.call
|
17
|
+
runtime_context.output_buffer << renderer.call
|
19
18
|
else
|
20
19
|
yield
|
21
20
|
end
|
22
|
-
|
23
21
|
end
|
24
22
|
end
|
25
23
|
end
|
@@ -1,21 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
|
-
class PartialRenderer
|
3
|
-
def render(
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
class PartialRenderer
|
5
|
+
def self.render(runtime_context)
|
6
|
+
builder = runtime_context.builder
|
7
|
+
view = builder.view
|
8
|
+
partial = runtime_context.render_item
|
9
|
+
partial = partial.to_partial_path if partial.respond_to?(:to_partial_path)
|
10
|
+
runtime_block = runtime_context.runtime_block
|
11
|
+
|
12
|
+
options = runtime_context.to_hash
|
13
|
+
overrides_and_provided_content = builder.capture(builder, options, &runtime_context.runtime_block) if runtime_block
|
14
|
+
|
8
15
|
locals = options.merge(
|
9
|
-
(
|
16
|
+
(runtime_context[:builder_variable] || :builder) => builder,
|
10
17
|
)
|
11
|
-
locals = if locals.respond_to?(:deep_symbolize_keys)
|
12
|
-
locals.deep_symbolize_keys
|
13
|
-
else
|
14
|
-
locals.symbolize_keys
|
15
|
-
end
|
16
|
-
partial = partial.to_partial_path if partial.respond_to?(:to_partial_path)
|
17
18
|
locals[:options] = options
|
18
|
-
|
19
|
+
|
20
|
+
builder.view.render(layout: partial, locals: locals) do |*args|
|
19
21
|
if overrides_and_provided_content
|
20
22
|
overrides_and_provided_content.to_str.gsub(/PLACEHOLDER_FOR_([\w]+)/) do |s|
|
21
23
|
builder.render $1, *args
|
@@ -1,38 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
4
|
class Renderer
|
3
|
-
|
4
|
-
delegate :render, to: :block_with_hooks_renderer
|
5
|
-
|
6
|
-
def initialize(builder)
|
7
|
-
self.builder = builder
|
8
|
-
end
|
9
|
-
|
10
|
-
# TODO: this needs to be handled by a new renderer
|
11
|
-
def render_with_overrides(*args, &block)
|
5
|
+
def self.render(builder, *args, &default_definition)
|
12
6
|
options = args.extract_options!
|
13
|
-
|
14
|
-
|
15
|
-
# TODO: block needs to be handled differently so as to provide overrides
|
16
|
-
block_with_hooks_renderer.render(*args, options, &block)
|
17
|
-
elsif options[:partial]
|
18
|
-
partial_renderer.render(options.delete(:partial), options, &block)
|
7
|
+
runtime_context = if !options.is_a?(RuntimeContext)
|
8
|
+
RuntimeContext.build(builder, *args, options, &default_definition)
|
19
9
|
else
|
20
|
-
|
10
|
+
options
|
21
11
|
end
|
12
|
+
|
13
|
+
BlockWithHooksRenderer.render(runtime_context)
|
22
14
|
end
|
23
15
|
|
24
16
|
# TODO: this needs to be handled by a new renderer
|
25
17
|
# TODO: also get rid of BlockPlaceholder
|
26
|
-
def deferred_render(*args, &block)
|
18
|
+
def self.deferred_render(builder, *args, &block)
|
27
19
|
block_definition = builder.define(*args, &block)
|
28
20
|
Blocks::BlockPlaceholder.new(block_definition)
|
29
21
|
end
|
30
|
-
|
31
|
-
AbstractRenderer::RENDERERS.each do |klass|
|
32
|
-
name = klass.to_s.demodulize.underscore.to_sym
|
33
|
-
define_method name do
|
34
|
-
klass.new(self)
|
35
|
-
end
|
36
|
-
end
|
37
22
|
end
|
38
23
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Blocks
|
2
4
|
class RuntimeContext < HashWithRenderStrategy
|
3
5
|
CONTROL_VARIABLES = {
|
@@ -8,199 +10,225 @@ module Blocks
|
|
8
10
|
as: []
|
9
11
|
}
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
13
|
+
PROTECTED_OPTIONS = RENDERING_STRATEGIES +
|
14
|
+
(CONTROL_VARIABLES.keys + CONTROL_VARIABLES.values).flatten.compact
|
15
|
+
|
16
|
+
attr_accessor *CONTROL_VARIABLES.keys,
|
17
|
+
:block_name,
|
18
|
+
:runtime_block,
|
19
|
+
:builder,
|
20
|
+
:render_item,
|
21
|
+
:runtime_args,
|
22
|
+
:merged_block_options,
|
23
|
+
:collection_item,
|
24
|
+
:collection_item_index,
|
25
|
+
:skip_completely,
|
26
|
+
:skip_content,
|
27
|
+
:hooks
|
28
|
+
|
29
|
+
delegate :block_defined?,
|
30
|
+
:block_for,
|
31
|
+
:output_buffer,
|
32
|
+
:with_output_buffer,
|
33
|
+
:capture,
|
34
|
+
to: :builder
|
35
|
+
|
36
|
+
def self.build(builder, *runtime_args, &runtime_block)
|
37
|
+
new.tap do |runtime_context|
|
38
|
+
runtime_context.builder = builder
|
39
|
+
runtime_context.runtime_block = runtime_block
|
40
|
+
runtime_context.compute(*runtime_args)
|
41
|
+
end
|
42
|
+
end
|
39
43
|
|
44
|
+
# TODO: change the method signature of this method to def compute(block_identifier, options={}, &runtime_block)
|
45
|
+
# Get rid of most uses of the *
|
46
|
+
def compute(*runtime_args)
|
40
47
|
render_options = runtime_args.extract_options!
|
41
|
-
|
42
|
-
|
43
|
-
convert_render_options(render_options)
|
44
|
-
identify_block(block_name)
|
45
|
-
|
48
|
+
build_block_context(runtime_args.shift, render_options)
|
49
|
+
# TODO: runtime args should be specified as a reserved keyword in the hash
|
46
50
|
self.runtime_args = runtime_args
|
47
|
-
merge_options_and_identify_render_item
|
48
51
|
extract_control_options
|
49
52
|
end
|
50
53
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
54
|
+
def extend_from_definition(definition, options={}, &runtime_block)
|
55
|
+
RuntimeContext.build(
|
56
|
+
builder,
|
57
|
+
definition,
|
58
|
+
# TODO: don't pass runtime args here?
|
59
|
+
*runtime_args,
|
60
|
+
options.merge(parent_runtime_context: self),
|
61
|
+
&runtime_block
|
62
|
+
)
|
56
63
|
end
|
57
64
|
|
58
65
|
# TODO: this method needs to be rewritten to output a proper hash
|
59
|
-
def to_s
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
66
|
+
# def to_s
|
67
|
+
# description = []
|
68
|
+
# if block_name
|
69
|
+
# block_name = self.block_name.to_s
|
70
|
+
# if block_name.include?(" ")
|
71
|
+
# block_name = ":\"#{block_name}\""
|
72
|
+
# else
|
73
|
+
# block_name = ":#{block_name}"
|
74
|
+
# end
|
75
|
+
# description << "Block Name: #{block_name}"
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# if render_item.is_a?(String)
|
79
|
+
# description << "Renders with partial \"#{render_item}\""
|
80
|
+
# elsif render_item.is_a?(Proc)
|
81
|
+
# description << "Renders with block defined at #{render_item.source_location}"
|
82
|
+
# elsif render_item.is_a?(Method)
|
83
|
+
# description << "Renders with method defined at #{render_item.source_location}"
|
84
|
+
# end
|
85
|
+
#
|
86
|
+
#
|
87
|
+
# CONTROL_VARIABLES.each do |control_variable, *|
|
88
|
+
# if value = send(control_variable)
|
89
|
+
# # description << "#{control_variable}: #{value} [#{callers[control_variable]}]"
|
90
|
+
# end
|
91
|
+
# end
|
92
|
+
#
|
93
|
+
# description << super
|
94
|
+
# description.join("\n")
|
95
|
+
# end
|
96
|
+
|
97
|
+
def hooks_or_wrappers_present?
|
98
|
+
hooks.present? || wrap_all || wrap_each || wrap_with || collection.present?
|
99
|
+
end
|
100
|
+
|
101
|
+
def hooks_for(hook_name)
|
102
|
+
hooks[hook_name] if hooks.try(:key?, hook_name)
|
103
|
+
end
|
70
104
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
105
|
+
def to_hash
|
106
|
+
hash = super
|
107
|
+
if collection_item_index
|
108
|
+
object_name = as || :object
|
109
|
+
hash.merge!(object_name => collection_item, current_index: collection_item_index)
|
75
110
|
end
|
111
|
+
hash
|
112
|
+
end
|
76
113
|
|
114
|
+
private
|
77
115
|
|
78
|
-
|
79
|
-
|
80
|
-
|
116
|
+
def add_hooks(block_definition)
|
117
|
+
if block_definition.hooks.present?
|
118
|
+
self.hooks = Hash.new {|hash, key| hash[key] = [] } if !hooks
|
119
|
+
block_definition.hooks.each do |hook_name, hooks|
|
120
|
+
self.hooks[hook_name].concat hooks
|
81
121
|
end
|
82
122
|
end
|
83
|
-
|
84
|
-
description << super
|
85
|
-
description.join("\n")
|
86
123
|
end
|
87
124
|
|
88
|
-
|
125
|
+
def build_block_context(identifier, render_options)
|
126
|
+
parent_runtime_context = render_options.delete(:parent_runtime_context)
|
89
127
|
|
90
|
-
|
91
|
-
|
92
|
-
|
128
|
+
self.block_name = identifier if identifier.is_a?(String) || identifier.is_a?(Symbol)
|
129
|
+
|
130
|
+
# Support legacy behavior - i.e. in versions 3.1 and earlier of Blocks,
|
131
|
+
# default render options were given precedence over block-level defaults
|
132
|
+
if !Blocks.default_render_options_take_precedence_over_block_defaults
|
133
|
+
default_render_options = render_options.delete(:defaults)
|
93
134
|
end
|
94
|
-
self.parent_runtime_context = render_options.delete(:parent_runtime_context)
|
95
|
-
self.render_options_set = OptionsSet.new(
|
96
|
-
"Render Options",
|
97
|
-
defaults: render_options.delete(:defaults),
|
98
|
-
runtime: render_options
|
99
|
-
)
|
100
|
-
end
|
101
135
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
# TODO:
|
109
|
-
|
110
|
-
|
136
|
+
default_options = merge_definition render_options, description: 'Render Options'
|
137
|
+
merge_definition identifier, default_options: default_options, merge_default_options: true
|
138
|
+
merge_definition default_render_options, description: 'Default Render Options', merge_default_options: true
|
139
|
+
merge_definition({ block: runtime_block }, description: 'Runtime Block') if runtime_block
|
140
|
+
if parent_runtime_context
|
141
|
+
merge_definition parent_runtime_context.merged_block_options, description: "Parent Runtime Context"
|
142
|
+
# TODO: setup a configuration to only pull in parent collection item if flag on
|
143
|
+
if parent_runtime_context.collection_item_index
|
144
|
+
object_name = parent_runtime_context.as || :object
|
145
|
+
merge_definition({
|
146
|
+
object_name => parent_runtime_context.collection_item,
|
147
|
+
current_index: parent_runtime_context.collection_item_index
|
148
|
+
}, description: "Parent Collection Item")
|
149
|
+
end
|
111
150
|
end
|
151
|
+
|
152
|
+
# Store the options as a hash usable in child runtime contexts.
|
153
|
+
# We do this before merging builder and global options as child runtime contexts will themselves merge in builder and global options
|
154
|
+
self.merged_block_options = to_hash.except!(*PROTECTED_OPTIONS)
|
155
|
+
|
156
|
+
merge_definition builder.options, description: 'Builder Options', merge_default_options: true
|
157
|
+
merge_definition Blocks.global_options, description: 'Global Options', merge_default_options: true
|
112
158
|
end
|
113
159
|
|
114
|
-
def
|
115
|
-
|
116
|
-
|
160
|
+
def merge_definition(definition, description: nil, default_options: [], follow_recursion: false, merge_default_options: false)
|
161
|
+
had_render_strategy = render_strategy_item.present?
|
162
|
+
follow_recursion ||= !had_render_strategy
|
117
163
|
|
118
|
-
|
164
|
+
if definition.present?
|
119
165
|
|
120
|
-
|
166
|
+
if definition.is_a?(Hash)
|
167
|
+
default_options << definition.delete(:defaults) if definition.key?(:defaults)
|
168
|
+
|
169
|
+
self.block_name = definition.block_to_render if definition.is_a?(HookDefinition)
|
170
|
+
reverse_merge! description, definition
|
121
171
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
render_item
|
126
|
-
end
|
172
|
+
if follow_recursion && renders_with_proxy?
|
173
|
+
merge_definition(render_strategy_item, default_options: default_options, follow_recursion: true)
|
174
|
+
end
|
127
175
|
|
128
|
-
|
129
|
-
|
130
|
-
options = args.extract_options!
|
131
|
-
runtime_block = runtime_block || options[:block]
|
132
|
-
builder.send(proxy_block_name, *args, options, &runtime_block)
|
133
|
-
end
|
134
|
-
end
|
135
|
-
end
|
176
|
+
elsif block_defined?(definition)
|
177
|
+
proxy_block = block_for(definition)
|
136
178
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
block_options_set,
|
143
|
-
parent_runtime_context.block_options_set.try(:clone),
|
144
|
-
# TODO: figure out how to deal with these - they don't technically belong here
|
145
|
-
parent_runtime_context.proxy_options_set.clone,
|
146
|
-
builder_options_set,
|
147
|
-
Blocks.global_options_set
|
148
|
-
].compact
|
149
|
-
else
|
150
|
-
[
|
151
|
-
render_options_set,
|
152
|
-
block_options_set,
|
153
|
-
# TODO: consider having the runtime_block be merged into the
|
154
|
-
# default render options set
|
155
|
-
OptionsSet.new("Runtime Block", block: self.runtime_block),
|
156
|
-
builder_options_set,
|
157
|
-
Blocks.global_options_set
|
158
|
-
].compact
|
159
|
-
end
|
179
|
+
self.skip_content = true if proxy_block.skip_content
|
180
|
+
self.skip_completely = true if proxy_block.skip_completely
|
181
|
+
|
182
|
+
add_hooks proxy_block
|
183
|
+
reverse_merge! proxy_block
|
160
184
|
|
161
|
-
|
162
|
-
|
163
|
-
map(&:render_strategies_and_items).
|
164
|
-
transpose.
|
165
|
-
detect do |options_for_level|
|
166
|
-
options_set_with_render_strategy_index = options_for_level.index(&:present?)
|
167
|
-
if options_set_with_render_strategy_index.present?
|
168
|
-
self.render_strategy, self.render_item =
|
169
|
-
options_for_level[options_set_with_render_strategy_index]
|
170
|
-
true
|
185
|
+
if proxy_block.default_options
|
186
|
+
default_options << proxy_block.default_options
|
171
187
|
end
|
172
|
-
end
|
173
188
|
|
174
|
-
|
175
|
-
|
176
|
-
|
189
|
+
proxy_render_item = proxy_block.render_strategy_item
|
190
|
+
|
191
|
+
if proxy_block.renders_with_proxy?
|
192
|
+
merge_definition proxy_render_item, default_options: default_options, follow_recursion: true if follow_recursion
|
193
|
+
elsif follow_recursion
|
194
|
+
# reverse_merge! default_options
|
195
|
+
# TODO: this should be based on a configuration - whether to use methods
|
196
|
+
if proxy_render_item.nil? && builder.respond_to?(definition)
|
197
|
+
self.render_item = builder.method(definition)
|
177
198
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
add_options options_for_level
|
199
|
+
else
|
200
|
+
self.render_item = proxy_render_item
|
201
|
+
end
|
182
202
|
|
183
|
-
if index == options_set_with_render_strategy_index
|
184
|
-
options_for_level = proxy_options_set.send(option_level)
|
185
|
-
add_options options_for_level
|
186
203
|
end
|
204
|
+
|
205
|
+
elsif builder.respond_to?(definition)
|
206
|
+
# TODO: is ||= necessary here?
|
207
|
+
self.render_item ||= builder.method(definition)
|
208
|
+
|
187
209
|
end
|
210
|
+
|
211
|
+
# TODO: is this line necessary? Should it be the else clause above
|
212
|
+
self.render_item ||= render_strategy_item if !renders_with_proxy?
|
188
213
|
end
|
189
214
|
|
190
|
-
if
|
191
|
-
|
215
|
+
if merge_default_options
|
216
|
+
default_options.each do |options|
|
217
|
+
merge_definition options, merge_default_options: true
|
218
|
+
end
|
192
219
|
end
|
220
|
+
|
221
|
+
default_options
|
193
222
|
end
|
194
223
|
|
195
224
|
def extract_control_options
|
196
225
|
CONTROL_VARIABLES.each do |control_variable, synonyms|
|
197
226
|
variant = (Array(synonyms) + Array(control_variable)).detect {|variant| key?(variant)}
|
198
|
-
value = delete(variant)
|
199
|
-
callers[control_variable] = callers[variant] if value
|
227
|
+
value = delete(variant) if variant
|
200
228
|
self.send("#{control_variable}=", value)
|
201
229
|
end
|
202
230
|
|
203
|
-
RENDERING_STRATEGIES
|
231
|
+
except!(*RENDERING_STRATEGIES)
|
204
232
|
end
|
205
233
|
end
|
206
234
|
end
|