blocks 2.8.0 → 3.0.0.rc1

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