compony 0.2.3 → 0.3.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +80 -13
  4. data/VERSION +1 -1
  5. data/compony.gemspec +4 -4
  6. data/doc/ComponentGenerator.html +1 -1
  7. data/doc/Components.html +1 -1
  8. data/doc/ComponentsGenerator.html +1 -1
  9. data/doc/Compony/Component.html +308 -341
  10. data/doc/Compony/ComponentMixins/Default/Labelling.html +1 -1
  11. data/doc/Compony/ComponentMixins/Default/Standalone/ResourcefulVerbDsl.html +1 -1
  12. data/doc/Compony/ComponentMixins/Default/Standalone/StandaloneDsl.html +1 -1
  13. data/doc/Compony/ComponentMixins/Default/Standalone/VerbDsl.html +1 -1
  14. data/doc/Compony/ComponentMixins/Default/Standalone.html +1 -1
  15. data/doc/Compony/ComponentMixins/Default.html +1 -1
  16. data/doc/Compony/ComponentMixins/Resourceful.html +1 -1
  17. data/doc/Compony/ComponentMixins.html +1 -1
  18. data/doc/Compony/Components/Button.html +3 -3
  19. data/doc/Compony/Components/Destroy.html +3 -3
  20. data/doc/Compony/Components/Edit.html +19 -19
  21. data/doc/Compony/Components/Form.html +3 -3
  22. data/doc/Compony/Components/New.html +19 -19
  23. data/doc/Compony/Components/WithForm.html +4 -4
  24. data/doc/Compony/Components.html +1 -1
  25. data/doc/Compony/ControllerMixin.html +1 -1
  26. data/doc/Compony/Engine.html +1 -1
  27. data/doc/Compony/MethodAccessibleHash.html +1 -1
  28. data/doc/Compony/ModelFields/Anchormodel.html +1 -1
  29. data/doc/Compony/ModelFields/Association.html +1 -1
  30. data/doc/Compony/ModelFields/Attachment.html +1 -1
  31. data/doc/Compony/ModelFields/Base.html +1 -1
  32. data/doc/Compony/ModelFields/Boolean.html +1 -1
  33. data/doc/Compony/ModelFields/Color.html +1 -1
  34. data/doc/Compony/ModelFields/Currency.html +1 -1
  35. data/doc/Compony/ModelFields/Date.html +1 -1
  36. data/doc/Compony/ModelFields/Datetime.html +1 -1
  37. data/doc/Compony/ModelFields/Decimal.html +1 -1
  38. data/doc/Compony/ModelFields/Email.html +1 -1
  39. data/doc/Compony/ModelFields/Float.html +1 -1
  40. data/doc/Compony/ModelFields/Integer.html +1 -1
  41. data/doc/Compony/ModelFields/Percentage.html +1 -1
  42. data/doc/Compony/ModelFields/Phone.html +1 -1
  43. data/doc/Compony/ModelFields/RichText.html +1 -1
  44. data/doc/Compony/ModelFields/String.html +1 -1
  45. data/doc/Compony/ModelFields/Text.html +1 -1
  46. data/doc/Compony/ModelFields/Time.html +1 -1
  47. data/doc/Compony/ModelFields/Url.html +1 -1
  48. data/doc/Compony/ModelFields.html +1 -1
  49. data/doc/Compony/ModelMixin.html +1 -1
  50. data/doc/Compony/NaturalOrdering.html +292 -0
  51. data/doc/Compony/RequestContext.html +72 -1
  52. data/doc/Compony/Version.html +1 -1
  53. data/doc/Compony/ViewHelpers.html +1 -1
  54. data/doc/Compony.html +3 -3
  55. data/doc/ComponyController.html +1 -1
  56. data/doc/_index.html +8 -1
  57. data/doc/class_list.html +1 -1
  58. data/doc/file.README.html +73 -16
  59. data/doc/index.html +73 -16
  60. data/doc/method_list.html +100 -100
  61. data/doc/top-level-namespace.html +1 -1
  62. data/lib/compony/component.rb +30 -54
  63. data/lib/compony/components/edit.rb +2 -4
  64. data/lib/compony/components/new.rb +4 -6
  65. data/lib/compony/components/with_form.rb +1 -1
  66. data/lib/compony/natural_ordering.rb +56 -0
  67. data/lib/compony/request_context.rb +8 -0
  68. data/lib/compony.rb +1 -0
  69. metadata +4 -2
@@ -102,7 +102,7 @@
102
102
  </div>
103
103
 
104
104
  <div id="footer">
105
- Generated on Sat Apr 27 10:22:11 2024 by
105
+ Generated on Wed May 29 15:53:00 2024 by
106
106
  <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
107
107
  0.9.34 (ruby-3.2.2).
108
108
  </div>
@@ -7,6 +7,7 @@ module Compony
7
7
 
8
8
  attr_reader :parent_comp
9
9
  attr_reader :comp_opts
10
+ attr_reader :content_blocks # needed in RequestContext for nesting
10
11
 
11
12
  # root comp: component that is registered to be root of the application.
12
13
  # parent comp: component that is registered to be the parent of this comp. If there is none, this is the root comp.
@@ -24,9 +25,9 @@ module Compony
24
25
  @sub_comps = []
25
26
  @index = index
26
27
  @comp_opts = comp_opts
27
- @before_render_block = nil
28
- @content_blocks = []
29
- @actions = []
28
+ @before_render_blocks = NaturalOrdering.new
29
+ @content_blocks = NaturalOrdering.new
30
+ @actions = NaturalOrdering.new
30
31
  @skipped_actions = Set.new
31
32
 
32
33
  init_standalone
@@ -111,46 +112,39 @@ module Compony
111
112
  comp_cst.to_s.underscore
112
113
  end
113
114
 
114
- # @todo deprecate (check for usages beforehand)
115
- def comp_class_for(...)
116
- Compony.comp_class_for(...)
117
- end
118
-
119
- # @todo deprecate (check for usages beforehand)
120
- def comp_class_for!(...)
121
- Compony.comp_class_for!(...)
122
- end
123
-
124
115
  # DSL method
125
- def before_render(&block)
126
- @before_render_block = block
127
- end
128
-
129
- # DSL method
130
- # Overrides previous content (also from superclasses). Will be the first content block to run.
131
- # You can use dyny here.
132
- def content(&block)
133
- fail("`content` expects a block in #{inspect}.") unless block_given?
134
- @content_blocks = [block]
116
+ # Adds or overrides a before_render block.
117
+ # You can use controller.redirect_to to redirect away and halt the before_render/content chain
118
+ # @param [Symbol,String] name The name of the before_render block, defaults to `:main`
119
+ # @param [nil,Symbol,String] before If nil, the block will be added to the bottom of the before_render chain. Otherwise, pass the name of another block.
120
+ # @param [Proc] block The block that should be run as part of the before_render pipeline. Will run in the component's context.
121
+ def before_render(name = :main, before: nil, **kwargs, &block)
122
+ fail("`before_render` expects a block in #{inspect}.") unless block_given?
123
+ @before_render_blocks.natural_push(name, block, before:, **kwargs)
135
124
  end
136
125
 
137
126
  # DSL method
138
- # Adds a content block that will be executed after all previous ones.
139
- # It is safe to use this method even if `content` has never been called
140
- # You can use dyny here.
141
- def add_content(index = -1, &block)
127
+ # Adds or overrides a content block.
128
+ # @param [Symbol,String] name The name of the content block, defaults to `:main`
129
+ # @param [nil,Symbol,String] before If nil, the block will be added to the bottom of the content chain. Otherwise, pass the name of another block.
130
+ # @param [Hash] kwargs If hidden is true, the content will not be rendered by default, allowing you to nest it in another content block.
131
+ # @param [Proc] block The block that should be run as part of the content pipeline. Will run in the component's context. You can use Dyny here.
132
+ def content(name = :main, before: nil, **kwargs, &block)
142
133
  fail("`content` expects a block in #{inspect}.") unless block_given?
143
- @content_blocks ||= []
144
- @content_blocks.insert(index, block)
134
+ @content_blocks.natural_push(name, block, before:, **kwargs)
145
135
  end
146
136
 
147
137
  # Renders the component using the controller passsed to it and returns it as a string.
148
138
  # @param [Boolean] standalone pass true iff `render` is called from `render_standalone`
149
139
  # Do not overwrite.
150
140
  def render(controller, standalone: false, **locals)
151
- # Call before_render hook if any and backfire instance variables back to the component
152
- # TODO: Can .request_context be removed from the next line? Test well!
153
- RequestContext.new(self, controller, locals:).request_context.evaluate_with_backfire(&@before_render_block) if @before_render_block
141
+ # Call before_render hooks (if any) and backfire instance variables back to the component
142
+ @before_render_blocks.each do |element|
143
+ RequestContext.new(self, controller, locals:).evaluate_with_backfire(&element.payload)
144
+ # Stop if a `before_render` block issued a body (e.g. through redirecting)
145
+ break unless controller.response_body.nil?
146
+ end
147
+
154
148
  # Render, unless before_render has already issued a body (e.g. through redirecting).
155
149
  if controller.response_body.nil?
156
150
  fail "#{self.class.inspect} must define `content` or set a response body in `before_render`" if @content_blocks.none?
@@ -161,9 +155,9 @@ module Compony
161
155
  if Compony.content_before_root_comp_block && standalone
162
156
  Compony::RequestContext.new(component, controller, helpers: self, locals: render_locals).evaluate(&Compony.content_before_root_comp_block)
163
157
  end
164
- content_blocks.each do |block|
158
+ content_blocks.reject{ |el| el.hidden }.each do |element|
165
159
  # Instanciate and evaluate a fresh RequestContext in order to use the buffer allocated by the ActionView (needed for `concat` calls)
166
- Compony::RequestContext.new(component, controller, helpers: self, locals: render_locals).evaluate(&block)
160
+ Compony::RequestContext.new(component, controller, helpers: self, locals: render_locals).evaluate(&element.payload)
167
161
  end
168
162
  if Compony.content_after_root_comp_block && standalone
169
163
  Compony::RequestContext.new(component, controller, helpers: self, locals: render_locals).evaluate(&Compony.content_after_root_comp_block)
@@ -179,25 +173,7 @@ module Compony
179
173
  # Adds or replaces an action (for action buttons)
180
174
  # If before: is specified, will insert the action before the named action. When replacing, an element keeps its position unless before: is specified.
181
175
  def action(action_name, before: nil, &block)
182
- action_name = action_name.to_sym
183
- before_name = before&.to_sym
184
- action = MethodAccessibleHash.new(name: action_name, block:)
185
-
186
- existing_index = @actions.find_index { |el| el.name == action_name }
187
- if existing_index.present? && before_name.present?
188
- @actions.delete_at(existing_index) # Replacing an existing element with a before: directive - must delete before calculating indices
189
- end
190
- if before_name.present?
191
- before_index = @actions.find_index { |el| el.name == before_name } || fail("Action #{before_name} for :before not found in #{inspect}.")
192
- end
193
-
194
- if before_index.present?
195
- @actions.insert(before_index, action)
196
- elsif existing_index.present?
197
- @actions[existing_index] = action
198
- else
199
- @actions << action
200
- end
176
+ @actions.natural_push(action_name, block, before:)
201
177
  end
202
178
 
203
179
  # DSL method
@@ -213,7 +189,7 @@ module Compony
213
189
  button_htmls = @actions.map do |action|
214
190
  next if @skipped_actions.include?(action.name)
215
191
  Compony.with_button_defaults(feasibility_action: action.name.to_sym) do
216
- action_button = action.block.call(controller)
192
+ action_button = action.payload.call(controller)
217
193
  next unless action_button
218
194
  button_html = action_button.render(controller)
219
195
  next if button_html.blank?
@@ -50,10 +50,8 @@ module Compony
50
50
  end
51
51
  hsh? local_form_comp.schema_wrapper_key_for(local_data), &local_form_comp.schema_block_for(local_data)
52
52
  end
53
- schema.validate!(controller.request.params)
54
-
55
- # TODO: Why are we not saving the validated params?
56
- attrs_to_assign = controller.request.params[form_comp.schema_wrapper_key_for(@data)]
53
+ validated_params = schema.validate!(controller.request.params)
54
+ attrs_to_assign = validated_params[form_comp.schema_wrapper_key_for(@data)]
57
55
  @data.assign_attributes(attrs_to_assign) if attrs_to_assign
58
56
  end
59
57
 
@@ -32,10 +32,10 @@ module Compony
32
32
  label(:short) { I18n.t('compony.components.new.label.short') }
33
33
  icon { :plus }
34
34
 
35
- add_content do
35
+ content :label do
36
36
  h2 component.label
37
37
  end
38
- add_content do
38
+ content do
39
39
  concat form_comp.render(controller, data: @data)
40
40
  end
41
41
 
@@ -45,10 +45,8 @@ module Compony
45
45
  schema = Schemacop::Schema3.new :hash, additional_properties: true do
46
46
  hsh? local_form_comp.schema_wrapper_key_for(local_data), &local_form_comp.schema_block_for(local_data)
47
47
  end
48
- schema.validate!(controller.request.params)
49
-
50
- # TODO: Why are we not saving the validated params?
51
- attrs_to_assign = controller.request.params[form_comp.schema_wrapper_key_for(@data)]
48
+ validated_params = schema.validate!(controller.request.params)
49
+ attrs_to_assign = validated_params[form_comp.schema_wrapper_key_for(@data)]
52
50
  @data.assign_attributes(attrs_to_assign) if attrs_to_assign
53
51
  end
54
52
 
@@ -13,7 +13,7 @@ module Compony
13
13
  # Returns an instance of the form component responsible for rendering the form.
14
14
  # Feel free to override this in subclasses.
15
15
  def form_comp
16
- @form_comp ||= (form_comp_class || comp_class_for!(:form, family_cst)).new(
16
+ @form_comp ||= (form_comp_class || Compony.comp_class_for!(:form, family_cst)).new(
17
17
  self,
18
18
  submit_verb:,
19
19
  # If applicable, Rails adds the route keys automatically, thus, e.g. :id does not need to be passed here, as it comes from the request.
@@ -0,0 +1,56 @@
1
+ module Compony
2
+ # @api description
3
+ # This class provides an array-based data structure where elements have symbol names. New elements can be appended or placed at a location using `before:`.
4
+ # Important: do not mutate this class with any other method call than the natural_-prefixed methods defined below.
5
+ # Example:<br>
6
+ # ```ruby
7
+ # collection = Compony::NaturalOrdering.new
8
+ # collection.natural_push(:a, a_payload)
9
+ # collection.natural_push(:c, c_payload)
10
+ # collection.natural_push(:b, b_payload, before: :c)
11
+ # collection.natural_push(:d, d_payload, hidden: true)
12
+ # collection.natural_push(:a, a_new_payload) # overwrites :a
13
+ #
14
+ # collection.reject{|el| el.hidden}.map(&:name) # --> :a, :b, :c
15
+ # collection.map(&:payload) # --> a_new_payload, b_payload, c_payload, d_payload
16
+ # ```
17
+ class NaturalOrdering < Array
18
+ def natural_push(name, payload, before: nil, **kwargs)
19
+ name = name.to_sym
20
+ before_name = before&.to_sym
21
+ old_kwargs = {}
22
+
23
+ # Fetch existing element if any
24
+ existing_index = find_index { |el| el.name == name }
25
+ if existing_index.present?
26
+ # Copy all non-mentionned kwargs from the element we are about to overwrite
27
+ old_kwargs = self[existing_index].except(:name, :payload)
28
+
29
+ # Replacing an existing element with a before: directive - must delete before calculating indices
30
+ if before_name.present?
31
+ delete_at(existing_index)
32
+ end
33
+ end
34
+
35
+ # Fetch before element
36
+ if before_name.present?
37
+ before_index = find_index { |el| el.name == before_name } || fail("Element #{before_name.inspect} for :before not found in #{inspect}.")
38
+ end
39
+
40
+ # Create the element to insert
41
+ element = MethodAccessibleHash.new(name:, payload:, **old_kwargs.merge(kwargs))
42
+
43
+ # Insert new element
44
+ if before_index.present?
45
+ # Insert before another element
46
+ insert(before_index, element)
47
+ elsif existing_index.present?
48
+ # Override another element
49
+ self[existing_index] = element
50
+ else
51
+ # Append at the end
52
+ self << element
53
+ end
54
+ end
55
+ end
56
+ end
@@ -41,5 +41,13 @@ module Compony
41
41
  return true if @local_assigns.key?(method)
42
42
  return super
43
43
  end
44
+
45
+ # Renders a content block from the current component.
46
+ def content(name)
47
+ name = name.to_sym
48
+ content_block = component.content_blocks.find { |el| el.name == name } || fail("Content block #{name.inspect} not found in #{component.inspect}.")
49
+ # A fresh RequestContext is needed due to Rails' buffer
50
+ concat Compony::RequestContext.new(component, controller, helpers:, locals: local_assigns).evaluate(&content_block.payload)
51
+ end
44
52
  end
45
53
  end
data/lib/compony.rb CHANGED
@@ -300,6 +300,7 @@ require 'compony/components/new'
300
300
  require 'compony/components/edit'
301
301
  require 'compony/components/destroy'
302
302
  require 'compony/method_accessible_hash'
303
+ require 'compony/natural_ordering'
303
304
  require 'compony/model_mixin'
304
305
  require 'compony/request_context'
305
306
  require 'compony/version'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: compony
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.3
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sandro Kalbermatter
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2024-04-27 00:00:00.000000000 Z
12
+ date: 2024-05-29 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: yard
@@ -233,6 +233,7 @@ files:
233
233
  - doc/Compony/ModelFields/Time.html
234
234
  - doc/Compony/ModelFields/Url.html
235
235
  - doc/Compony/ModelMixin.html
236
+ - doc/Compony/NaturalOrdering.html
236
237
  - doc/Compony/RequestContext.html
237
238
  - doc/Compony/Version.html
238
239
  - doc/Compony/ViewHelpers.html
@@ -297,6 +298,7 @@ files:
297
298
  - lib/compony/model_fields/time.rb
298
299
  - lib/compony/model_fields/url.rb
299
300
  - lib/compony/model_mixin.rb
301
+ - lib/compony/natural_ordering.rb
300
302
  - lib/compony/request_context.rb
301
303
  - lib/compony/version.rb
302
304
  - lib/compony/view_helpers.rb