blocks 3.0.4 → 4.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +19 -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 +174 -152
- 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 +22 -21
- 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,27 @@
|
|
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 = runtime_context.runtime_args.try(:clone) || []
|
17
|
+
runtime_context.runtime_args = [item] + Array(runtime_context.runtime_args)
|
18
|
+
end
|
19
|
+
|
20
|
+
yield runtime_context
|
11
21
|
end
|
22
|
+
runtime_context.collection_item = original_collection_item
|
23
|
+
runtime_context.collection_item_index = original_collection_item_index
|
24
|
+
runtime_context.runtime_args = original_runtime_args
|
12
25
|
else
|
13
26
|
yield runtime_context
|
14
27
|
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,205 +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
|
-
elsif render_item.nil? && builder.respond_to?(proxy_block_name)
|
125
|
-
Proc.new do |*args|
|
126
|
-
options = args.extract_options!
|
127
|
-
runtime_block = self.runtime_block || options[:block]
|
128
|
-
builder.send(proxy_block_name, *args, options, &runtime_block)
|
172
|
+
if follow_recursion && renders_with_proxy?
|
173
|
+
merge_definition(render_strategy_item, default_options: default_options, follow_recursion: true)
|
129
174
|
end
|
130
|
-
else
|
131
|
-
render_item
|
132
|
-
end
|
133
175
|
|
134
|
-
|
135
|
-
|
136
|
-
options = args.extract_options!
|
137
|
-
runtime_block = self.runtime_block || options[:block]
|
138
|
-
builder.send(proxy_block_name, *args, options, &runtime_block)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
end
|
176
|
+
elsif block_defined?(definition)
|
177
|
+
proxy_block = block_for(definition)
|
142
178
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
block_options_set,
|
149
|
-
parent_runtime_context.block_options_set.try(:clone),
|
150
|
-
# TODO: figure out how to deal with these - they don't technically belong here
|
151
|
-
parent_runtime_context.proxy_options_set.clone,
|
152
|
-
builder_options_set,
|
153
|
-
Blocks.global_options_set
|
154
|
-
].compact
|
155
|
-
else
|
156
|
-
[
|
157
|
-
render_options_set,
|
158
|
-
block_options_set,
|
159
|
-
# TODO: consider having the runtime_block be merged into the
|
160
|
-
# default render options set
|
161
|
-
OptionsSet.new("Runtime Block", block: self.runtime_block),
|
162
|
-
builder_options_set,
|
163
|
-
Blocks.global_options_set
|
164
|
-
].compact
|
165
|
-
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
|
166
184
|
|
167
|
-
|
168
|
-
|
169
|
-
map(&:render_strategies_and_items).
|
170
|
-
transpose.
|
171
|
-
detect do |options_for_level|
|
172
|
-
options_set_with_render_strategy_index = options_for_level.index(&:present?)
|
173
|
-
if options_set_with_render_strategy_index.present?
|
174
|
-
self.render_strategy, self.render_item =
|
175
|
-
options_for_level[options_set_with_render_strategy_index]
|
176
|
-
true
|
185
|
+
if proxy_block.default_options
|
186
|
+
default_options << proxy_block.default_options
|
177
187
|
end
|
178
|
-
end
|
179
188
|
|
180
|
-
|
181
|
-
|
182
|
-
|
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)
|
183
198
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
add_options options_for_level
|
199
|
+
else
|
200
|
+
self.render_item = proxy_render_item
|
201
|
+
end
|
188
202
|
|
189
|
-
if index == options_set_with_render_strategy_index
|
190
|
-
options_for_level = proxy_options_set.send(option_level)
|
191
|
-
add_options options_for_level
|
192
203
|
end
|
204
|
+
|
205
|
+
elsif builder.respond_to?(definition)
|
206
|
+
# TODO: is ||= necessary here?
|
207
|
+
self.render_item ||= builder.method(definition)
|
208
|
+
|
193
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?
|
194
213
|
end
|
195
214
|
|
196
|
-
if
|
197
|
-
|
215
|
+
if merge_default_options
|
216
|
+
default_options.each do |options|
|
217
|
+
merge_definition options, merge_default_options: true
|
218
|
+
end
|
198
219
|
end
|
220
|
+
|
221
|
+
default_options
|
199
222
|
end
|
200
223
|
|
201
224
|
def extract_control_options
|
202
225
|
CONTROL_VARIABLES.each do |control_variable, synonyms|
|
203
226
|
variant = (Array(synonyms) + Array(control_variable)).detect {|variant| key?(variant)}
|
204
|
-
value = delete(variant)
|
205
|
-
callers[control_variable] = callers[variant] if value
|
227
|
+
value = delete(variant) if variant
|
206
228
|
self.send("#{control_variable}=", value)
|
207
229
|
end
|
208
230
|
|
209
|
-
RENDERING_STRATEGIES
|
231
|
+
except!(*RENDERING_STRATEGIES)
|
210
232
|
end
|
211
233
|
end
|
212
234
|
end
|