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.
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 +19 -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 +174 -152
  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 +22 -21
  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,27 @@
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 = 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 < 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,205 +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
- 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
- elsif builder.respond_to?(proxy_block_name)
135
- Proc.new do |*args|
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
- def merge_options_and_identify_render_item
144
- determined_render_item = false
145
- all_options_sets = if parent_runtime_context
146
- [
147
- parent_runtime_context.render_options_set.clone,
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
- options_set_with_render_strategy_index = nil
168
- all_options_sets.
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
- if self.render_strategy == RENDER_WITH_PROXY
181
- self.render_item = add_proxy_options render_item
182
- 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)
183
198
 
184
- [:runtime_options, :standard_options, :default_options].each do |option_level|
185
- all_options_sets.each_with_index do |options_set, index|
186
- options_for_level = options_set.send(option_level)
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 render_item.blank?
197
- 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
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.each {|rs| delete(rs) }
231
+ except!(*RENDERING_STRATEGIES)
210
232
  end
211
233
  end
212
234
  end