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,13 @@
1
+ module Blocks
2
+ class BlockPlaceholder
3
+ attr_accessor :block_definition
4
+
5
+ def initialize(block_definition)
6
+ self.block_definition = block_definition
7
+ end
8
+
9
+ def to_s
10
+ "PLACEHOLDER_FOR_#{block_definition.name} "
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Blocks
2
+ class BlockRenderer < AbstractRenderer
3
+ def render(*args, runtime_context)
4
+ render_item = runtime_context.render_item
5
+ if render_item.is_a?(String)
6
+ output_buffer << partial_renderer.render(render_item, runtime_context)
7
+ elsif render_item.is_a?(Proc)
8
+ args = args + runtime_context.runtime_args
9
+ output_buffer << capture(*args, runtime_context, &render_item)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,35 @@
1
+ module Blocks
2
+ # 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)
15
+ 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)
21
+ end
22
+ end
23
+ end
24
+ adjacent_blocks_renderer.render(HookDefinition::AFTER, runtime_context)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ adjacent_blocks_renderer.render(HookDefinition::AFTER_ALL, runtime_context)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ module Blocks
2
+ class CollectionRenderer < AbstractRenderer
3
+ def render(runtime_context)
4
+ collection = runtime_context.collection
5
+ if collection
6
+ object_name = runtime_context.as || :object
7
+ 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
11
+ end
12
+ else
13
+ yield runtime_context
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module Blocks
2
+ class NestingBlocksRenderer < AbstractRenderer
3
+ def render(hook, runtime_context, &block)
4
+ block = block_for(runtime_context.block_name)
5
+ if block
6
+ content_block = Proc.new { with_output_buffer { yield } }
7
+ hooks = block.hooks_for(hook)
8
+
9
+ renderer = hooks.inject(content_block) do |inner_content, block_definition|
10
+ hook_runtime_context = runtime_context.extend_to_block_definition(block_definition)
11
+ Proc.new {
12
+ with_output_buffer do
13
+ block_renderer.render(inner_content, hook_runtime_context)
14
+ end
15
+ }
16
+ end
17
+
18
+ output_buffer << renderer.call
19
+ else
20
+ yield
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module Blocks
2
+ class PartialRenderer < AbstractRenderer
3
+ def render(partial, options={}, &block)
4
+ overrides_and_provided_content = capture(builder, &block) if block_given?
5
+ locals = options.merge(
6
+ (options[:builder_variable] || :builder) => builder,
7
+ ).symbolize_keys
8
+ locals[:options] = options
9
+ view.render(layout: partial, locals: locals) do |*args|
10
+ if overrides_and_provided_content
11
+ overrides_and_provided_content.to_str.gsub(/PLACEHOLDER_FOR_([\w]+)/) do |s|
12
+ builder.render $1, *args
13
+ end.html_safe
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,43 @@
1
+ module Blocks
2
+ class Renderer
3
+ attr_accessor :builder
4
+
5
+ def initialize(builder)
6
+ self.builder = builder
7
+ end
8
+
9
+ def render(*args, &block)
10
+ block_with_hooks_renderer.render(*args, &block)
11
+ end
12
+
13
+ # TODO: this needs to be handled by a new renderer
14
+ def render_with_overrides(*args, &block)
15
+ options = args.extract_options!
16
+ name = args.first
17
+ if name.is_a?(Symbol) || name.is_a?(String)
18
+ block_with_hooks_renderer.render(*args, options, &block)
19
+ elsif options[:partial]
20
+ partial_renderer.render(options.delete(:partial), options, &block)
21
+ else
22
+ # TODO: handle other possible rendering methods such as a custom renderer
23
+ end
24
+ end
25
+
26
+ # TODO: this needs to be handled by a new renderer
27
+ # TODO: also get rid of BlockPlaceholder
28
+ def deferred_render(*args, &block)
29
+ block_definition = builder.define(*args, &block)
30
+ Blocks::BlockPlaceholder.new(block_definition)
31
+ end
32
+
33
+ AbstractRenderer::RENDERERS.each do |klass|
34
+ name = klass.to_s.demodulize.underscore
35
+
36
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
37
+ def #{name}
38
+ @#{name} ||= #{klass}.new(self)
39
+ end
40
+ RUBY
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,193 @@
1
+ module Blocks
2
+ class RuntimeContext < HashWithRenderStrategy
3
+ CONTROL_VARIABLES = {
4
+ wrap_all: [],
5
+ wrap_each: [:outer_wrapper],
6
+ wrap_with: [:wrap, :wrapper, :inner_wrapper],
7
+ collection: [],
8
+ as: []
9
+ }
10
+
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_options_set,
21
+ :merged_options_set
22
+
23
+ delegate :skip_content, :skip_completely, to: :block_options_set, allow_nil: true
24
+
25
+ delegate :block_definitions, :block_defined?, to: :builder
26
+
27
+ delegate :runtime_options,
28
+ :standard_options,
29
+ :default_options,
30
+ :options_set,
31
+ prefix: :builder,
32
+ to: :builder
33
+
34
+ def initialize(builder, *runtime_args, &runtime_block)
35
+ super(&nil)
36
+
37
+ self.builder = builder
38
+ self.runtime_block = runtime_block
39
+ self.proxy_options_set = OptionsSet.new("Proxy Options Set")
40
+
41
+ convert_render_options(runtime_args.extract_options!)
42
+ identify_block(runtime_args.shift)
43
+
44
+ self.runtime_args = runtime_args
45
+ merge_options_and_identify_render_item
46
+ extract_control_options
47
+ end
48
+
49
+ def extend_to_block_definition(block_definition)
50
+ RuntimeContext.new(builder, block_definition, parent_options_set: merged_options_set.clone).tap do |rc|
51
+ rc.runtime_args = self.runtime_args
52
+ end
53
+ end
54
+
55
+ # TODO: this method needs to be rewritten to output a proper hash
56
+ def to_s
57
+ description = []
58
+ if block_name
59
+ block_name = self.block_name.to_s
60
+ if block_name.include?(" ")
61
+ block_name = ":\"#{block_name}\""
62
+ else
63
+ block_name = ":#{block_name}"
64
+ end
65
+ description << "Block Name: #{block_name}"
66
+ end
67
+
68
+ if render_item.is_a?(String)
69
+ description << "Renders with partial \"#{render_item}\""
70
+ elsif render_item.is_a?(Proc)
71
+ description << "Renders with block defined at #{render_item.source_location}"
72
+ end
73
+
74
+
75
+ CONTROL_VARIABLES.each do |control_variable, *|
76
+ if value = send(control_variable)
77
+ description << "#{control_variable}: #{value} [#{callers[control_variable]}]"
78
+ end
79
+ end
80
+
81
+ description << super
82
+ description.join("\n")
83
+ end
84
+
85
+ private
86
+
87
+ def convert_render_options(render_options)
88
+ if !render_options.is_a?(HashWithIndifferentAccess)
89
+ render_options = render_options.with_indifferent_access
90
+ end
91
+ self.parent_options_set = render_options.delete(:parent_options_set)
92
+ self.render_options_set = OptionsSet.new(
93
+ "Render Options",
94
+ defaults: render_options.delete(:defaults),
95
+ runtime: render_options
96
+ )
97
+ end
98
+
99
+ def identify_block(block_identifier)
100
+ self.block_name, self.block_options_set = if block_identifier.is_a?(OptionsSet)
101
+ [block_identifier.name, block_identifier]
102
+ elsif block_defined?(block_identifier)
103
+ [block_identifier, block_definitions[block_identifier]]
104
+ elsif block_identifier.is_a?(Proc)
105
+ # TODO: figure out how to do this
106
+ else
107
+ [block_identifier, nil]
108
+ end
109
+ end
110
+
111
+ def add_proxy_options(proxy_block_name)
112
+ if block_defined?(proxy_block_name)
113
+ proxy_block = block_definitions[proxy_block_name]
114
+
115
+ proxy_options_set.add_options(proxy_block)
116
+
117
+ render_strategy, render_item = proxy_block.current_render_strategy_and_item
118
+
119
+ if render_strategy == RENDER_WITH_PROXY
120
+ add_proxy_options render_item
121
+ else
122
+ render_item
123
+ end
124
+
125
+ elsif builder.respond_to?(proxy_block_name)
126
+ Proc.new do |*args|
127
+ options = args.extract_options!
128
+ runtime_block = runtime_block || options[:block]
129
+ builder.send(proxy_block_name, *args, options, &runtime_block)
130
+ end
131
+ end
132
+ end
133
+
134
+ def merge_options_and_identify_render_item
135
+ determined_render_item = false
136
+ all_options_sets = [
137
+ render_options_set,
138
+ block_options_set,
139
+ OptionsSet.new("Runtime Block", block: self.runtime_block),
140
+ builder_options_set,
141
+ Blocks.global_options_set,
142
+ parent_options_set
143
+ ].compact
144
+
145
+ options_set_with_render_strategy_index = nil
146
+ all_options_sets.
147
+ map(&:render_strategies_and_items).
148
+ transpose.
149
+ detect do |options_for_level|
150
+ options_set_with_render_strategy_index = options_for_level.index(&:present?)
151
+ if options_set_with_render_strategy_index.present?
152
+ self.render_strategy, self.render_item =
153
+ options_for_level[options_set_with_render_strategy_index]
154
+ true
155
+ end
156
+ end
157
+
158
+ if self.render_strategy == RENDER_WITH_PROXY
159
+ self.render_item = add_proxy_options render_item
160
+ end
161
+
162
+ self.merged_options_set = OptionsSet.new("Parent Options Set")
163
+ [:runtime_options, :standard_options, :default_options].each do |option_level|
164
+ all_options_sets.each_with_index do |options_set, index|
165
+ options_for_level = options_set.send(option_level)
166
+ merged_options_set.send(option_level).add_options(options_for_level)
167
+ add_options options_for_level
168
+
169
+ if index == options_set_with_render_strategy_index
170
+ options_for_level = proxy_options_set.send(option_level)
171
+ merged_options_set.send(option_level).add_options(options_for_level)
172
+ add_options options_for_level
173
+ end
174
+ end
175
+ end
176
+
177
+ if render_item.blank?
178
+ self.render_item = runtime_block
179
+ end
180
+ end
181
+
182
+ def extract_control_options
183
+ CONTROL_VARIABLES.each do |control_variable, synonyms|
184
+ variant = (Array(synonyms) + Array(control_variable)).detect {|variant| key?(variant)}
185
+ value = delete(variant)
186
+ callers[control_variable] = callers[variant] if value
187
+ self.send("#{control_variable}=", value)
188
+ end
189
+
190
+ RENDERING_STRATEGIES.each {|rs| delete(rs) }
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,19 @@
1
+ module Blocks
2
+ class WrapperRenderer < AbstractRenderer
3
+ def render(wrapper, runtime_context, &block)
4
+ content_block = Proc.new { with_output_buffer { yield } }
5
+ if wrapper.nil?
6
+ yield
7
+ elsif wrapper.is_a?(Proc)
8
+ output_buffer << capture(content_block, *(runtime_context.runtime_args), runtime_context, &wrapper)
9
+ elsif block_definition = block_for(wrapper)
10
+ runtime_context = runtime_context.extend_to_block_definition(block_definition)
11
+ block_renderer.render(content_block, runtime_context)
12
+ elsif builder.respond_to?(wrapper)
13
+ output_buffer << builder.send(wrapper, runtime_context, &content_block)
14
+ else
15
+ yield
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ module Blocks
2
+ module Configurator
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ include DynamicConfiguration
7
+
8
+ add_config :builder_class
9
+ add_config :renderer_class
10
+ add_config :global_options_set
11
+ add_config :lookup_caller_location, instance_predicate: true
12
+ reset_config
13
+ end
14
+
15
+ module ClassMethods
16
+ def reset_config
17
+ configure do |config|
18
+ config.builder_class = Builder
19
+ config.renderer_class = Renderer
20
+ config.lookup_caller_location = true
21
+ config.global_options_set = OptionsSet.new("Global Options")
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,71 @@
1
+ module Blocks
2
+ module DynamicConfiguration
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def add_config(*attrs)
7
+ options = attrs.extract_options!
8
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
9
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
10
+ instance_predicate = options.fetch(:instance_predicate, true)
11
+ default_value = options.fetch(:default, nil)
12
+
13
+ attrs.each do |name|
14
+ define_singleton_method(name) do |value="UNDEFINED"|
15
+ if value == "UNDEFINED"
16
+ default_value
17
+ else
18
+ public_send("#{name}=", value)
19
+ end
20
+ end
21
+ define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
22
+
23
+ ivar = "@#{name}"
24
+
25
+ define_singleton_method("#{name}=") do |val|
26
+ singleton_class.class_eval do
27
+ remove_possible_method(name)
28
+ define_method(name) do |value="UNDEFINED"|
29
+ if value == "UNDEFINED"
30
+ val
31
+ else
32
+ public_send("#{name}=", value)
33
+ end
34
+ end
35
+ end
36
+
37
+ class_eval do
38
+ remove_possible_method(name)
39
+ define_method(name) do
40
+ if instance_variable_defined? ivar
41
+ instance_variable_get ivar
42
+ else
43
+ singleton_class.send name
44
+ end
45
+ end
46
+ end
47
+ val
48
+ end
49
+
50
+ if instance_reader
51
+ remove_possible_method name
52
+ define_method(name) do
53
+ if instance_variable_defined?(ivar)
54
+ instance_variable_get ivar
55
+ else
56
+ self.class.public_send name
57
+ end
58
+ end
59
+ define_method("#{name}?") { !!public_send(name) } if instance_predicate
60
+ end
61
+
62
+ attr_writer name if instance_writer
63
+ end
64
+ end
65
+
66
+ def configure
67
+ yield self
68
+ end
69
+ end
70
+ end
71
+ end