blocks 2.8.0 → 3.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 -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