compony 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/CHANGELOG.md +23 -0
  4. data/Gemfile.lock +3 -3
  5. data/README.md +80 -13
  6. data/Rakefile +1 -1
  7. data/VERSION +1 -1
  8. data/compony.gemspec +5 -5
  9. data/doc/ComponentGenerator.html +1 -1
  10. data/doc/Components.html +1 -1
  11. data/doc/ComponentsGenerator.html +1 -1
  12. data/doc/Compony/Component.html +308 -341
  13. data/doc/Compony/ComponentMixins/Default/Labelling.html +1 -1
  14. data/doc/Compony/ComponentMixins/Default/Standalone/ResourcefulVerbDsl.html +1 -1
  15. data/doc/Compony/ComponentMixins/Default/Standalone/StandaloneDsl.html +1 -1
  16. data/doc/Compony/ComponentMixins/Default/Standalone/VerbDsl.html +1 -1
  17. data/doc/Compony/ComponentMixins/Default/Standalone.html +1 -1
  18. data/doc/Compony/ComponentMixins/Default.html +1 -1
  19. data/doc/Compony/ComponentMixins/Resourceful.html +1 -1
  20. data/doc/Compony/ComponentMixins.html +1 -1
  21. data/doc/Compony/Components/Button.html +3 -3
  22. data/doc/Compony/Components/Destroy.html +3 -3
  23. data/doc/Compony/Components/Edit.html +19 -19
  24. data/doc/Compony/Components/Form.html +3 -3
  25. data/doc/Compony/Components/New.html +19 -19
  26. data/doc/Compony/Components/WithForm.html +4 -4
  27. data/doc/Compony/Components.html +1 -1
  28. data/doc/Compony/ControllerMixin.html +1 -1
  29. data/doc/Compony/Engine.html +1 -1
  30. data/doc/Compony/MethodAccessibleHash.html +1 -1
  31. data/doc/Compony/ModelFields/Anchormodel.html +7 -3
  32. data/doc/Compony/ModelFields/Association.html +1 -1
  33. data/doc/Compony/ModelFields/Attachment.html +1 -1
  34. data/doc/Compony/ModelFields/Base.html +1 -1
  35. data/doc/Compony/ModelFields/Boolean.html +1 -1
  36. data/doc/Compony/ModelFields/Color.html +1 -1
  37. data/doc/Compony/ModelFields/Currency.html +1 -1
  38. data/doc/Compony/ModelFields/Date.html +1 -1
  39. data/doc/Compony/ModelFields/Datetime.html +1 -1
  40. data/doc/Compony/ModelFields/Decimal.html +1 -1
  41. data/doc/Compony/ModelFields/Email.html +1 -1
  42. data/doc/Compony/ModelFields/Float.html +1 -1
  43. data/doc/Compony/ModelFields/Integer.html +1 -1
  44. data/doc/Compony/ModelFields/Percentage.html +1 -1
  45. data/doc/Compony/ModelFields/Phone.html +1 -1
  46. data/doc/Compony/ModelFields/RichText.html +1 -1
  47. data/doc/Compony/ModelFields/String.html +1 -1
  48. data/doc/Compony/ModelFields/Text.html +1 -1
  49. data/doc/Compony/ModelFields/Time.html +1 -1
  50. data/doc/Compony/ModelFields/Url.html +1 -1
  51. data/doc/Compony/ModelFields.html +1 -1
  52. data/doc/Compony/ModelMixin.html +1 -1
  53. data/doc/Compony/NaturalOrdering.html +292 -0
  54. data/doc/Compony/RequestContext.html +72 -1
  55. data/doc/Compony/Version.html +1 -1
  56. data/doc/Compony/ViewHelpers.html +1 -1
  57. data/doc/Compony.html +3 -3
  58. data/doc/ComponyController.html +1 -1
  59. data/doc/_index.html +8 -1
  60. data/doc/class_list.html +1 -1
  61. data/doc/file.README.html +73 -16
  62. data/doc/index.html +73 -16
  63. data/doc/method_list.html +100 -100
  64. data/doc/top-level-namespace.html +1 -1
  65. data/lib/compony/component.rb +30 -54
  66. data/lib/compony/components/edit.rb +2 -4
  67. data/lib/compony/components/new.rb +4 -6
  68. data/lib/compony/components/with_form.rb +1 -1
  69. data/lib/compony/model_fields/anchormodel.rb +3 -1
  70. data/lib/compony/natural_ordering.rb +56 -0
  71. data/lib/compony/request_context.rb +8 -0
  72. data/lib/compony.rb +1 -0
  73. metadata +6 -4
@@ -102,7 +102,7 @@
102
102
  </div>
103
103
 
104
104
  <div id="footer">
105
- Generated on Wed Apr 24 17:14:05 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.
@@ -13,8 +13,10 @@ module Compony
13
13
  def simpleform_input_hidden(form, _component, name: nil, **input_opts)
14
14
  if form.object
15
15
  selected_cst = form.object.send(@name)
16
+ am_attr = form.object.class.anchormodel_attributes[@name]
17
+ am_serializer = (am_attr.multiple? ? ::Anchormodel::ActiveModelTypeValueMulti : ::Anchormodel::ActiveModelTypeValueSingle).new(am_attr)
16
18
  input_opts[:input_html] ||= {}
17
- input_opts[:input_html][:value] = selected_cst.is_a?(::Anchormodel) ? selected_cst.key : selected_cst
19
+ input_opts[:input_html][:value] = am_serializer.serialize(selected_cst)
18
20
  end
19
21
  return form.input name || @name, as: :hidden, **input_opts
20
22
  end
@@ -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.2
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-24 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
@@ -143,14 +143,14 @@ dependencies:
143
143
  requirements:
144
144
  - - "~>"
145
145
  - !ruby/object:Gem::Version
146
- version: 0.1.5
146
+ version: 0.2.0
147
147
  type: :runtime
148
148
  prerelease: false
149
149
  version_requirements: !ruby/object:Gem::Requirement
150
150
  requirements:
151
151
  - - "~>"
152
152
  - !ruby/object:Gem::Version
153
- version: 0.1.5
153
+ version: 0.2.0
154
154
  - !ruby/object:Gem::Dependency
155
155
  name: cancancan
156
156
  requirement: !ruby/object:Gem::Requirement
@@ -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