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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +61 -17
  4. data/CHANGELOG.rdoc +27 -0
  5. data/Gemfile +11 -10
  6. data/Guardfile +1 -0
  7. data/README.md +2 -0
  8. data/bin/deploy_docs +1 -1
  9. data/blocks.gemspec +3 -1
  10. data/docs/_includes/configuration.md +3 -6
  11. data/docs/_includes/defining.md +7 -7
  12. data/docs/_includes/{introduction.md → features.md} +2 -2
  13. data/docs/_includes/helper-blocks.md +5 -0
  14. data/docs/_includes/helper-blocks/content_tag.md +44 -0
  15. data/docs/_includes/installation.md +16 -2
  16. data/docs/_includes/overview.md +21 -0
  17. data/docs/_includes/templating.md +1 -1
  18. data/docs/_includes/templating/bootstrap_4_cards.md +9 -9
  19. data/docs/_layouts/slate.html +11 -9
  20. data/docs/index.md +3 -1
  21. data/gemfiles/Gemfile.rails-3-0-stable +1 -0
  22. data/gemfiles/Gemfile.rails-3-1-stable +1 -0
  23. data/gemfiles/Gemfile.rails-3-2-stable +1 -0
  24. data/gemfiles/Gemfile.rails-4-0-stable +2 -1
  25. data/gemfiles/Gemfile.rails-4-1-stable +2 -1
  26. data/gemfiles/Gemfile.rails-4-2-stable +2 -1
  27. data/gemfiles/Gemfile.rails-5-0-stable +4 -3
  28. data/gemfiles/Gemfile.rails-5-1-stable +4 -3
  29. data/gemfiles/Gemfile.rails-5-2-stable +13 -0
  30. data/lib/blocks.rb +37 -29
  31. data/lib/blocks/builders/block_definition.rb +45 -43
  32. data/lib/blocks/builders/builder.rb +96 -60
  33. data/lib/blocks/builders/hook_definition.rb +19 -4
  34. data/lib/blocks/engine.rb +14 -0
  35. data/lib/blocks/helpers/controller_extensions.rb +13 -0
  36. data/lib/blocks/helpers/haml_capture.rb +44 -0
  37. data/lib/blocks/{action_view_extensions → helpers}/view_extensions.rb +10 -4
  38. data/lib/blocks/renderers/adjacent_blocks_renderer.rb +9 -7
  39. data/lib/blocks/renderers/block_placeholder.rb +2 -0
  40. data/lib/blocks/renderers/block_renderer.rb +26 -5
  41. data/lib/blocks/renderers/block_with_hooks_renderer.rb +29 -19
  42. data/lib/blocks/renderers/collection_renderer.rb +18 -6
  43. data/lib/blocks/renderers/nesting_blocks_renderer.rb +9 -11
  44. data/lib/blocks/renderers/partial_renderer.rb +16 -14
  45. data/lib/blocks/renderers/renderer.rb +9 -24
  46. data/lib/blocks/renderers/runtime_context.rb +175 -147
  47. data/lib/blocks/renderers/wrapper_renderer.rb +21 -10
  48. data/lib/blocks/utilities/configurator.rb +30 -6
  49. data/lib/blocks/utilities/hash_with_caller.rb +36 -32
  50. data/lib/blocks/utilities/hash_with_render_strategy.rb +67 -19
  51. data/lib/blocks/utilities/options_set.rb +38 -63
  52. data/lib/blocks/version.rb +3 -1
  53. metadata +23 -22
  54. data/docs/_includes/wip.md +0 -34
  55. data/lib/blocks/experimental/builder_permissions.rb +0 -52
  56. data/lib/blocks/experimental/invalid_permissions_handler.rb +0 -27
  57. data/lib/blocks/renderers/abstract_renderer.rb +0 -69
  58. 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 < OptionsSet
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, *args, &block)
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 "#{hook_type} #{block_definition.name} options", *args, &block
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!.with_indifferent_access
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 render_with_overrides call below - need it to be just one place
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.render_with_overrides(options.merge(partial: partial), &block)
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 < AbstractRenderer
3
- def render(hook, runtime_context)
4
- block = block_for(runtime_context.block_name)
5
- if block
6
- hooks = block.hooks_for hook
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.extend_to_block_definition(hook_definition)
10
- block_renderer.render(hook_runtime_context)
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Blocks
2
4
  class BlockPlaceholder
3
5
  attr_accessor :block_definition
@@ -1,12 +1,33 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Blocks
2
- class BlockRenderer < AbstractRenderer
3
- def render(*args, runtime_context)
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 << partial_renderer.render(render_item, runtime_context)
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
- args = args + runtime_context.runtime_args
9
- output_buffer << capture(*args, runtime_context, &render_item)
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 < AbstractRenderer
4
- def render(*args, &default_definition)
5
- with_output_buffer do
6
- runtime_context = RuntimeContext.new(builder, *args, &default_definition)
7
- if !runtime_context.skip_completely
8
- adjacent_blocks_renderer.render(HookDefinition::BEFORE_ALL, runtime_context)
9
- nesting_blocks_renderer.render(HookDefinition::AROUND_ALL, runtime_context) do
10
- wrapper_renderer.render(runtime_context.wrap_all, runtime_context) do
11
- collection_renderer.render(runtime_context) do |runtime_context|
12
- wrapper_renderer.render(runtime_context.wrap_each, runtime_context) do
13
- nesting_blocks_renderer.render(HookDefinition::AROUND, runtime_context) do
14
- adjacent_blocks_renderer.render(HookDefinition::BEFORE, runtime_context)
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
- wrapper_renderer.render(runtime_context.wrap_with, runtime_context) do
17
- nesting_blocks_renderer.render(HookDefinition::SURROUND, runtime_context) do
18
- adjacent_blocks_renderer.render(HookDefinition::PREPEND, runtime_context)
19
- block_renderer.render(runtime_context)
20
- adjacent_blocks_renderer.render(HookDefinition::APPEND, runtime_context)
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
- adjacent_blocks_renderer.render(HookDefinition::AFTER, runtime_context)
27
+ render_hooks(HookDefinition::AFTER, runtime_context)
25
28
  end
26
29
  end
27
30
  end
28
31
  end
29
32
  end
30
- adjacent_blocks_renderer.render(HookDefinition::AFTER_ALL, runtime_context)
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 < AbstractRenderer
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
- object_name = runtime_context.as || :object
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
- item_runtime_context = runtime_context.merge(object_name => item, current_index: index)
9
- item_runtime_context.runtime_args = [item] + item_runtime_context.runtime_args
10
- yield item_runtime_context
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 < AbstractRenderer
3
- def render(hook, runtime_context)
4
- block = block_for(runtime_context.block_name)
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.extend_to_block_definition(hook_definition)
11
+ hook_runtime_context = runtime_context.extend_from_definition(hook_definition, &inner_content)
11
12
  Proc.new {
12
- with_output_buffer do
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 < AbstractRenderer
3
- def render(partial, options={}, &block)
4
- if !options.is_a?(Blocks::RuntimeContext)
5
- options = RuntimeContext.new(builder, options).to_hash.with_indifferent_access
6
- end
7
- overrides_and_provided_content = capture(builder, options, &block) if block_given?
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
- (options[:builder_variable] || :builder) => builder,
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
- view.render(layout: partial, locals: locals) do |*args|
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
- attr_accessor :builder
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
- name = args.first
14
- if name.is_a?(Symbol) || name.is_a?(String)
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
- # TODO: handle other possible rendering methods such as a custom renderer
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
- attr_accessor(*CONTROL_VARIABLES.keys)
12
- attr_accessor :block_name,
13
- :runtime_block,
14
- :builder,
15
- :render_item,
16
- :runtime_args,
17
- :render_options_set,
18
- :block_options_set,
19
- :proxy_options_set,
20
- :parent_runtime_context
21
-
22
- delegate :skip_content, :skip_completely, to: :block_options_set, allow_nil: true
23
-
24
- delegate :block_definitions, :block_defined?, to: :builder
25
-
26
- delegate :runtime_options,
27
- :standard_options,
28
- :default_options,
29
- :options_set,
30
- prefix: :builder,
31
- to: :builder
32
-
33
- def initialize(builder, *runtime_args, &runtime_block)
34
- super(&nil)
35
-
36
- self.builder = builder
37
- self.runtime_block = runtime_block
38
- self.proxy_options_set = OptionsSet.new("Proxy Options Set")
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
- block_name = runtime_args.shift
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
- # TODO: this method needs to clone without context, i.e. with render_strategy, item, etc
52
- def extend_to_block_definition(block_definition)
53
- RuntimeContext.new(builder, block_definition, parent_runtime_context: self).tap do |rc|
54
- rc.runtime_args = self.runtime_args
55
- end
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
- description = []
61
- if block_name
62
- block_name = self.block_name.to_s
63
- if block_name.include?(" ")
64
- block_name = ":\"#{block_name}\""
65
- else
66
- block_name = ":#{block_name}"
67
- end
68
- description << "Block Name: #{block_name}"
69
- end
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
- if render_item.is_a?(String)
72
- description << "Renders with partial \"#{render_item}\""
73
- elsif render_item.is_a?(Proc)
74
- description << "Renders with block defined at #{render_item.source_location}"
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
- CONTROL_VARIABLES.each do |control_variable, *|
79
- if value = send(control_variable)
80
- description << "#{control_variable}: #{value} [#{callers[control_variable]}]"
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
- private
125
+ def build_block_context(identifier, render_options)
126
+ parent_runtime_context = render_options.delete(:parent_runtime_context)
89
127
 
90
- def convert_render_options(render_options)
91
- if !render_options.is_a?(HashWithIndifferentAccess)
92
- render_options = render_options.with_indifferent_access
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
- def identify_block(block_identifier)
103
- self.block_name, self.block_options_set = if block_identifier.is_a?(OptionsSet)
104
- [block_identifier.name, block_identifier]
105
- elsif block_defined?(block_identifier)
106
- [block_identifier, block_definitions[block_identifier]]
107
- elsif block_identifier.is_a?(Proc)
108
- # TODO: figure out how to do this
109
- else
110
- [block_identifier, nil]
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 add_proxy_options(proxy_block_name)
115
- if block_defined?(proxy_block_name)
116
- proxy_block = block_definitions[proxy_block_name]
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
- proxy_options_set.add_options(proxy_block)
164
+ if definition.present?
119
165
 
120
- render_strategy, render_item = proxy_block.current_render_strategy_and_item
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
- if render_strategy == RENDER_WITH_PROXY
123
- add_proxy_options render_item
124
- else
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
- elsif builder.respond_to?(proxy_block_name)
129
- Proc.new do |*args|
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
- def merge_options_and_identify_render_item
138
- determined_render_item = false
139
- all_options_sets = if parent_runtime_context
140
- [
141
- parent_runtime_context.render_options_set.clone,
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
- options_set_with_render_strategy_index = nil
162
- all_options_sets.
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
- if self.render_strategy == RENDER_WITH_PROXY
175
- self.render_item = add_proxy_options render_item
176
- end
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
- [:runtime_options, :standard_options, :default_options].each do |option_level|
179
- all_options_sets.each_with_index do |options_set, index|
180
- options_for_level = options_set.send(option_level)
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 render_item.blank?
191
- self.render_item = runtime_block
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.each {|rs| delete(rs) }
231
+ except!(*RENDERING_STRATEGIES)
204
232
  end
205
233
  end
206
234
  end