compony 0.7.0 → 0.8.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 (117) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +49 -1591
  5. data/VERSION +1 -1
  6. data/compony.gemspec +4 -4
  7. data/doc/ComponentGenerator.html +1 -1
  8. data/doc/Components.html +1 -1
  9. data/doc/ComponentsGenerator.html +1 -1
  10. data/doc/Compony/Component.html +193 -457
  11. data/doc/Compony/ComponentMixins/Default/Labelling.html +1 -1
  12. data/doc/Compony/ComponentMixins/Default/Standalone/ResourcefulVerbDsl.html +1 -1
  13. data/doc/Compony/ComponentMixins/Default/Standalone/StandaloneDsl.html +3 -3
  14. data/doc/Compony/ComponentMixins/Default/Standalone/VerbDsl.html +1 -1
  15. data/doc/Compony/ComponentMixins/Default/Standalone.html +187 -1
  16. data/doc/Compony/ComponentMixins/Default.html +1 -1
  17. data/doc/Compony/ComponentMixins/Resourceful.html +2 -2
  18. data/doc/Compony/ComponentMixins.html +1 -1
  19. data/doc/Compony/Components/Button.html +2 -2
  20. data/doc/Compony/Components/Buttons/CssButton.html +282 -0
  21. data/doc/Compony/Components/Buttons/Link.html +252 -0
  22. data/doc/Compony/Components/Buttons.html +126 -0
  23. data/doc/Compony/Components/Destroy.html +11 -11
  24. data/doc/Compony/Components/Edit.html +14 -14
  25. data/doc/Compony/Components/Form.html +100 -100
  26. data/doc/Compony/Components/Index.html +2 -2
  27. data/doc/Compony/Components/List.html +3 -3
  28. data/doc/Compony/Components/New.html +2 -2
  29. data/doc/Compony/Components/Show.html +24 -24
  30. data/doc/Compony/Components/WithForm.html +3 -3
  31. data/doc/Compony/Components.html +5 -3
  32. data/doc/Compony/ControllerMixin.html +2 -2
  33. data/doc/Compony/Engine.html +1 -1
  34. data/doc/Compony/ExposedIntentsDsl.html +403 -0
  35. data/doc/Compony/Intent.html +1503 -0
  36. data/doc/Compony/MethodAccessibleHash.html +1 -1
  37. data/doc/Compony/ModelFields/Anchormodel.html +1 -1
  38. data/doc/Compony/ModelFields/Association.html +2 -2
  39. data/doc/Compony/ModelFields/Attachment.html +1 -1
  40. data/doc/Compony/ModelFields/Base.html +1 -1
  41. data/doc/Compony/ModelFields/Boolean.html +1 -1
  42. data/doc/Compony/ModelFields/Color.html +1 -1
  43. data/doc/Compony/ModelFields/Currency.html +1 -1
  44. data/doc/Compony/ModelFields/Date.html +1 -1
  45. data/doc/Compony/ModelFields/Datetime.html +1 -1
  46. data/doc/Compony/ModelFields/Decimal.html +1 -1
  47. data/doc/Compony/ModelFields/Email.html +1 -1
  48. data/doc/Compony/ModelFields/Float.html +1 -1
  49. data/doc/Compony/ModelFields/Integer.html +1 -1
  50. data/doc/Compony/ModelFields/Percentage.html +1 -1
  51. data/doc/Compony/ModelFields/Phone.html +1 -1
  52. data/doc/Compony/ModelFields/RichText.html +1 -1
  53. data/doc/Compony/ModelFields/String.html +1 -1
  54. data/doc/Compony/ModelFields/Text.html +1 -1
  55. data/doc/Compony/ModelFields/Time.html +1 -1
  56. data/doc/Compony/ModelFields/Url.html +1 -1
  57. data/doc/Compony/ModelFields.html +1 -1
  58. data/doc/Compony/ModelMixin.html +6 -1
  59. data/doc/Compony/NaturalOrdering.html +1 -1
  60. data/doc/Compony/RequestContext.html +177 -14
  61. data/doc/Compony/Version.html +1 -1
  62. data/doc/Compony/ViewHelpers.html +15 -272
  63. data/doc/Compony/VirtualModel.html +152 -0
  64. data/doc/Compony.html +303 -837
  65. data/doc/ComponyController.html +1 -1
  66. data/doc/_index.html +37 -2
  67. data/doc/class_list.html +1 -1
  68. data/doc/file.README.html +49 -1635
  69. data/doc/guide/basic_component.md +263 -0
  70. data/doc/guide/example.md +228 -0
  71. data/doc/guide/feasibility.md +40 -0
  72. data/doc/guide/generators.md +15 -0
  73. data/doc/guide/inheritance.md +64 -0
  74. data/doc/guide/installation.md +47 -0
  75. data/doc/guide/intents.md +167 -0
  76. data/doc/guide/internal_datastructures.md +47 -0
  77. data/doc/guide/model_fields.md +64 -0
  78. data/doc/guide/nesting.md +134 -0
  79. data/doc/guide/ownership.md +23 -0
  80. data/doc/guide/pre_built_components/destroy.md +25 -0
  81. data/doc/guide/pre_built_components/edit.md +27 -0
  82. data/doc/guide/pre_built_components/form.md +117 -0
  83. data/doc/guide/pre_built_components/index.md +6 -0
  84. data/doc/guide/pre_built_components/list.md +14 -0
  85. data/doc/guide/pre_built_components/new.md +27 -0
  86. data/doc/guide/pre_built_components/show.md +8 -0
  87. data/doc/guide/pre_built_components/with_form.md +18 -0
  88. data/doc/guide/pre_built_components.md +20 -0
  89. data/doc/guide/resourceful.md +103 -0
  90. data/doc/guide/standalone.md +144 -0
  91. data/doc/guide/virtual_models.md +31 -0
  92. data/doc/index.html +49 -1635
  93. data/doc/method_list.html +273 -161
  94. data/doc/top-level-namespace.html +1 -1
  95. data/lib/compony/component.rb +19 -48
  96. data/lib/compony/component_mixins/default/standalone/standalone_dsl.rb +2 -2
  97. data/lib/compony/component_mixins/default/standalone.rb +16 -0
  98. data/lib/compony/component_mixins/resourceful.rb +1 -1
  99. data/lib/compony/components/buttons/css_button.rb +32 -0
  100. data/lib/compony/components/buttons/link.rb +31 -0
  101. data/lib/compony/components/destroy.rb +9 -8
  102. data/lib/compony/components/edit.rb +5 -4
  103. data/lib/compony/components/form.rb +7 -1
  104. data/lib/compony/components/index.rb +2 -2
  105. data/lib/compony/components/list.rb +4 -4
  106. data/lib/compony/components/new.rb +1 -1
  107. data/lib/compony/components/show.rb +8 -11
  108. data/lib/compony/components/with_form.rb +1 -1
  109. data/lib/compony/exposed_intents_dsl.rb +29 -0
  110. data/lib/compony/intent.rb +145 -0
  111. data/lib/compony/model_fields/association.rb +1 -1
  112. data/lib/compony/request_context.rb +21 -0
  113. data/lib/compony/view_helpers.rb +5 -48
  114. data/lib/compony/virtual_model.rb +10 -0
  115. data/lib/compony.rb +67 -149
  116. metadata +36 -3
  117. data/lib/compony/components/button.rb +0 -61
@@ -4,55 +4,12 @@ module Compony
4
4
  # Rule of thumb: this holds methods that require a view context and results are rendered immediately.
5
5
  # @see Compony Compony for standalone/pure helpers
6
6
  module ViewHelpers
7
- # Use this in your application layout to render all actions of the current root component.
8
- def compony_actions
9
- return nil unless Compony.root_comp
10
- Compony.root_comp.render_actions(self, wrapper_class: 'root-actions', action_class: 'root-action')
11
- end
12
-
13
- # Renders a link to a component given a comp and model or family. If authentication is configured
7
+ # Renders a button/link to a component given a comp and model or family. If authentication is configured
14
8
  # and the current user has insufficient permissions to access the target object, the link is not displayed.
15
- # @param comp_name_or_cst [String,Symbol] The component that should be loaded, for instance `ShowForAll`, `'ShowForAll'` or `:show_for_all`
16
- # @param model_or_family_name_or_cst [String,Symbol,ApplicationRecord] Either the family that contains the requested component,
17
- # or an instance implementing `model_name` from which the family name is auto-generated. Examples:
18
- # `Users`, `'Users'`, `:users`, `User.first`
19
- # @param link_args [Array] Positional arguments that will be passed to the Rails `link_to` helper
20
- # @param label_opts [Hash] Options hash that will be passed to the label method (see {Compony::ComponentMixins::Default::Labelling#label})
21
- # @param link_kwargs [Hash] Named arguments that will be passed to the Rails `link_to` helper
22
- def compony_link(comp_name_or_cst_or_class,
23
- model_or_family_name_or_cst = nil,
24
- *link_args,
25
- label: nil,
26
- label_opts: {},
27
- params: {},
28
- feasibility_action: nil,
29
- feasibility_target: nil,
30
- standalone_name: nil,
31
- **link_kwargs)
32
- model = model_or_family_name_or_cst.respond_to?(:model_name) ? model_or_family_name_or_cst : nil
33
- if comp_name_or_cst_or_class.is_a?(Class) && (comp_name_or_cst_or_class <= Compony::Component)
34
- target_comp_instance = comp_name_or_cst_or_class.new(data: model)
35
- else
36
- target_comp_instance = Compony.comp_class_for!(comp_name_or_cst_or_class, model_or_family_name_or_cst).new(data: model)
37
- end
38
- return unless target_comp_instance.standalone_access_permitted_for?(self, standalone_name:)
39
- feasibility_action ||= comp_name_or_cst_or_class.to_s.underscore.to_sym
40
- feasibility_target ||= model
41
- label ||= target_comp_instance.label(model, **label_opts)
42
- path ||= Compony.path(target_comp_instance.comp_name, target_comp_instance.family_name, model, standalone_name:, **params)
43
- if feasibility_target && !feasibility_target.feasible?(feasibility_action)
44
- path = '#'
45
- link_kwargs[:class] = link_kwargs[:class].is_a?(String) ? "#{link_kwargs[:class]} disabled" : 'disabled'
46
- link_kwargs[:title] = feasibility_target.full_feasibility_messages(feasibility_action).presence
47
- end
48
- return helpers.link_to(label, path, *link_args, **link_kwargs)
49
- end
50
-
51
- # Given a component and a family/model, this instanciates and renders a button component.
52
- # @see Compony#button Check Compony.button for accepted params
53
- # @see Compony::Components::Button Compony::Components::Button: the default underlying implementation
54
- def compony_button(...)
55
- Compony.button(...).render(helpers.controller)
9
+ # When inside a request context (`content do...`), this is preceded by {RequestContext#render_intent}.
10
+ # @param button [Hash] Parameters that will be given to the button component initializer.
11
+ def render_intent(*, button: {}, **)
12
+ Compony.intent(*, **).render(self, **button)
56
13
  end
57
14
  end
58
15
  end
@@ -0,0 +1,10 @@
1
+ module Compony
2
+ class VirtualModel < ActiveType::Object
3
+ # Use this as a base class whenever you would be inheriting from ActiveType::Object
4
+ # Note: this class is only available in applications that have `active_type` in their Gemfile
5
+
6
+ include Compony::ModelMixin
7
+ include Anchormodel::ModelMixin
8
+ include ActiveModel::Attributes
9
+ end
10
+ end
data/lib/compony.rb CHANGED
@@ -3,17 +3,25 @@
3
3
  # the setters, create an initializer `config/initializers/compony.rb` and call
4
4
  # them from there.
5
5
  # @see Compony::ViewHelpers Compony::ViewHelpers for helpers that require a view context and render results immediately
6
+ # @see Compony::RequestContext Compony::RequestContext for helpers that require a view context and render results immediately
6
7
  module Compony
7
8
  ##########=====-------
8
9
  # Configuration writers
9
10
  ##########=====-------
10
11
 
11
- # Setter for the global button component class. This allows you to implement a
12
- # custom button component and have all Compony button helpers use your custom
13
- # button component instead of {Compony::Components::Button}.
14
- # @param button_component_class [String] Name of your custom button component class (inherit from {Compony::Components::Button} or {Compony::Component})
15
- def self.button_component_class=(button_component_class)
16
- @button_component_class = button_component_class
12
+ # Adds a button style that can be referred to when rendering an intent.
13
+ # @param name [Symbol] Name of the style. If it exists already, will override the style.
14
+ # @param button_component_class [String] String with the class name of the button component that will be instanciated to render the intent.
15
+ def self.register_button_style(name, button_component_class_name)
16
+ @button_component_class_names ||= {}
17
+ @button_component_class_names[name.to_sym] = button_component_class_name
18
+ end
19
+
20
+ # Setter for the default button style. Defaults to :css_button.
21
+ # @param default_button_style [Symbol] Name of the style that should be used as default.
22
+ # @see {Compony#default_button_style}
23
+ def self.default_button_style=(default_button_style)
24
+ @default_button_style = default_button_style
17
25
  end
18
26
 
19
27
  # Setter for the global field namespaces. This allows you to implement custom
@@ -58,12 +66,31 @@ module Compony
58
66
  # Configuration readers
59
67
  ##########=====-------
60
68
 
61
- # Getter for the global button component class.
62
- # @see Compony#button_component_class= Explanation of button_component_class (documented in the corresponding setter)
63
- def self.button_component_class
64
- @button_component_class ||= Components::Button
65
- @button_component_class = const_get(@button_component_class) if @button_component_class.is_a?(String)
66
- return @button_component_class
69
+ # Getter for the button component class for a given style.
70
+ # @param style [Symbol] Style for which the matching button component class should be returned. Defaults to {Compony.default_button_style}.
71
+ # @see {Compony#register_button_style}
72
+ # @see {Compony#default_button_style}
73
+ def self.button_component_class(style = default_button_style)
74
+ # Lazy initialize
75
+ if @button_component_classes.nil?
76
+ @button_component_classes = {
77
+ css_button: Compony::Components::Buttons::CssButton,
78
+ link: Compony::Components::Buttons::Link
79
+ }.merge(@button_component_class_names&.transform_values(&:constantize) || {})
80
+ @button_component_classes.each_value do |button_component_class|
81
+ unless button_component_class.is_a?(Class) && button_component_class < Compony::Component
82
+ fail("Expected a button component class, got #{button_component_class.inspect}")
83
+ end
84
+ end
85
+ end
86
+ # Retrieval
87
+ return @button_component_classes[style&.to_sym] || fail("Unknown button style #{style.inspect}. Use one of: #{@button_component_classes.keys.inspect}")
88
+ end
89
+
90
+ # Getter for the default button style, defaults to `:css_button`.
91
+ # @see {Compony#default_button_style=}
92
+ def self.default_button_style
93
+ return @default_button_style || :css_button
67
94
  end
68
95
 
69
96
  # Getter for the global field namespaces.
@@ -94,123 +121,38 @@ module Compony
94
121
  # Application-wide available pure helpers
95
122
  ##########=====-------
96
123
 
124
+ # Pure helper to create a Compony Intent. If given an intent, will return it unchanged. Otherwise, will give all params to the intent initializer.
125
+ def self.intent(intent_or_comp_args, ...)
126
+ if intent_or_comp_args.is_a?(Intent)
127
+ return intent_or_comp_args
128
+ else
129
+ return Intent.new(intent_or_comp_args, ...)
130
+ end
131
+ end
132
+
97
133
  # Generates a Rails path to a component. Examples: `Compony.path(:index, :users)`, `Compony.path(:show, User.first)`
98
- # @param comp_name_or_cst_or_class [String,Symbol] The component that should be loaded, for instance `ShowForAll`, `'ShowForAll'` or `:show_for_all`
99
- # or can also pass a component class (such as Components::Users::Show)
100
- # @param model_or_family_name_or_cst [String,Symbol,ApplicationRecord] Either the family that contains the requested component,
101
- # or an instance implementing `model_name` from which the family name is auto-generated. Examples:
102
- # `Users`, `'Users'`, `:users`, `User.first`
103
- # @param standalone_name [Symbol,nil] Name of the standalone config to point to (defaults to nil the default standalone config).
104
- # @param args_for_path_helper [Array] Positional arguments passed to the Rails helper
105
- # @param kwargs_for_path_helper [Hash] Named arguments passed to the Rails helper. If a model is given to `model_or_family_name_or_cst`,
106
- # the param `id` defaults to the passed model's ID.
107
- def self.path(comp_name_or_cst_or_class, model_or_family_name_or_cst = nil, *args_for_path_helper, standalone_name: nil, **kwargs_for_path_helper)
108
- # Extract model if any, to get the ID
109
- model = model_or_family_name_or_cst.respond_to?(:model_name) ? model_or_family_name_or_cst : nil
110
- comp_class = if comp_name_or_cst_or_class.is_a?(Class) && (comp_name_or_cst_or_class <= Compony::Component)
111
- comp_name_or_cst_or_class
112
- else
113
- comp_class_for!(comp_name_or_cst_or_class, model_or_family_name_or_cst)
114
- end
115
- return comp_class.new.path(model, *args_for_path_helper, standalone_name:, **kwargs_for_path_helper)
134
+ # The first two arguments are given to create an {Intent} and all subsequend args and all kwargs are given to {Intent#path}
135
+ def self.path(comp_name_or_cst_or_class, model_or_family_name_or_cst = nil, ...)
136
+ intent(comp_name_or_cst_or_class, model_or_family_name_or_cst).path(...)
116
137
  end
117
138
 
118
139
  # Given a component and a family/model, this returns the matching component class if any, or nil if the component does not exist.
119
- # @param comp_name_or_cst [String,Symbol] The component that should be loaded, for instance `ShowForAll`, `'ShowForAll'` or `:show_for_all`
120
- # @param model_or_family_name_or_cst [String,Symbol,ApplicationRecord] Either the family that contains the requested component,
121
- # or an instance implementing `model_name` from which the family name is auto-generated. Examples:
122
- # `Users`, `'Users'`, `:users`, `User.first`
123
- def self.comp_class_for(comp_name_or_cst, model_or_family_name_or_cst)
124
- family_cst_str = family_name_for(model_or_family_name_or_cst).camelize
125
- comp_cst_str = comp_name_or_cst.to_s.camelize
126
- return nil unless ::Components.const_defined?(family_cst_str)
127
- family_constant = ::Components.const_get(family_cst_str)
128
- return nil unless family_constant.const_defined?(comp_cst_str)
129
- return family_constant.const_get(comp_cst_str)
140
+ # @see Intent for allowed parameters.
141
+ def self.comp_class_for(...)
142
+ return intent(...).comp_class
143
+ rescue NameError
144
+ return nil
130
145
  end
131
146
 
132
147
  # Same as Compony#comp_class_for but fails if none found
148
+ # @see Intent for allowed parameters.
133
149
  # @see Compony#comp_class_for
134
- def self.comp_class_for!(comp_name_or_cst, model_or_family_name_or_cst)
135
- comp_class_for(comp_name_or_cst, model_or_family_name_or_cst) || fail(
150
+ def self.comp_class_for!(...)
151
+ comp_class_for(...) || fail(
136
152
  "No component found for [#{comp_name_or_cst.inspect}, #{model_or_family_name_or_cst.inspect}]"
137
153
  )
138
154
  end
139
155
 
140
- # Given a component and a family, this returns the name of the Rails URL helper returning the path to this component.<br>
141
- # The parameters are the same as for {Compony#rails_action_name}.<br>
142
- # Example usage: `send("#{path_helper_name(:index, :users)}_url)`
143
- # @see Compony#path
144
- # @see Compony#rails_action_name rails_action_name for the accepted params
145
- def self.path_helper_name(...)
146
- "#{rails_action_name(...)}_comp"
147
- end
148
-
149
- # Given a component and a family or a component class, this returns the name of the ComponyController action for this component.<br>
150
- # Optionally can pass a name for extra standalone configs.
151
- # @param comp_name_or_cst [String,Symbol] Name of the component the action points to.
152
- # @param model_or_family_name_or_cst [String,Symbol] Name of the family the action points to.
153
- # @param name [String,Symbol] If referring to an extra standalone entrypoint, specify its name using this param.
154
- # @see Compony#path
155
- def self.rails_action_name(comp_name_or_cst_or_class, model_or_family_name_or_cst, name = nil)
156
- if comp_name_or_cst_or_class.is_a?(Class) && (comp_name_or_cst_or_class <= Compony::Component)
157
- comp_class = comp_name_or_cst_or_class
158
- comp_name_or_cst_or_class = comp_class.comp_name
159
- model_or_family_name_or_cst = comp_class.family_name
160
- end
161
- [name.presence, comp_name_or_cst_or_class.to_s.underscore, family_name_for(model_or_family_name_or_cst)].compact.join('_')
162
- end
163
-
164
- # Given a component and a family/model, this instanciates and returns a button component.
165
- # @param comp_name_or_cst_or_class [String,Symbol,Class] The component that should be loaded, for instance `ShowForAll`, `'ShowForAll'` or `:show_for_all`
166
- # @param model_or_family_name_or_cst [String,Symbol,ApplicationRecord] Either the family that contains the requested component,
167
- # or an instance implementing `model_name` from which the family name is auto-generated. Examples:
168
- # `Users`, `'Users'`, `:users`, `User.first`
169
- # @param label_opts [Hash] Options hash that will be passed to the label method (see {Compony::ComponentMixins::Default::Labelling#label})
170
- # @param params [Hash] GET parameters to be inclued into the path this button points to. Special case: e.g. format: :pdf -> some.url/foo/bar.pdf
171
- # @param feasibility_action [Symbol] Name of the feasibility action that should be checked for this button, defaults to the component name
172
- # @param feasibility_target [Symbol] Name of the feasibility target (subject) that the feasibility should be checked on, defaults to the model if given
173
- # @param standalone_name [Symbol,nil] Name of the standalone config to point to (defaults to nil the default standalone config).
174
- # @param override_kwargs [Hash] Override button options, see options for {Compony::Components::Button}
175
- # @see Compony::ViewHelpers#compony_button View helper providing a wrapper for this method that immediately renders a button.
176
- # @see Compony::Components::Button Compony::Components::Button: the default underlying implementation
177
- def self.button(comp_name_or_cst_or_class,
178
- model_or_family_name_or_cst = nil,
179
- label_opts: nil,
180
- params: nil,
181
- feasibility_action: nil,
182
- feasibility_target: nil,
183
- method: nil,
184
- standalone_name: nil,
185
- **override_kwargs)
186
- label_opts ||= button_defaults[:label_opts] || {}
187
- params ||= button_defaults[:params] || {}
188
- model = model_or_family_name_or_cst.respond_to?(:model_name) ? model_or_family_name_or_cst : nil
189
- if comp_name_or_cst_or_class.is_a?(Class) && (comp_name_or_cst_or_class <= Compony::Component)
190
- target_comp_instance = comp_name_or_cst_or_class.new(data: model)
191
- else
192
- target_comp_instance = Compony.comp_class_for!(comp_name_or_cst_or_class, model_or_family_name_or_cst).new(data: model)
193
- end
194
- feasibility_action ||= button_defaults[:feasibility_action] || comp_name_or_cst_or_class.to_s.underscore.to_sym
195
- feasibility_target ||= button_defaults[:feasibility_target] || model
196
- options = {
197
- label: target_comp_instance.label(model, **label_opts),
198
- icon: target_comp_instance.icon,
199
- color: target_comp_instance.color,
200
- path: Compony.path(target_comp_instance.comp_name, target_comp_instance.family_name, model, standalone_name:, **params),
201
- method:,
202
- visible: ->(controller) { target_comp_instance.standalone_access_permitted_for?(controller, standalone_name:, verb: method) }
203
- }
204
- if feasibility_target
205
- options.merge!({
206
- enabled: feasibility_target.feasible?(feasibility_action),
207
- title: feasibility_target.full_feasibility_messages(feasibility_action).presence
208
- })
209
- end
210
- options.merge!(override_kwargs.symbolize_keys)
211
- return Compony.button_component_class.new(**options.symbolize_keys)
212
- end
213
-
214
156
  # Returns the current root component, if any
215
157
  def self.root_comp
216
158
  RequestStore.store[:compony_root_comp]
@@ -228,37 +170,6 @@ module Compony
228
170
  end
229
171
  end
230
172
 
231
- # Getter for current button defaults
232
- # @todo document params
233
- def self.button_defaults
234
- RequestStore.store[:button_defaults] || {}
235
- end
236
-
237
- # Overwrites the keys of the current button defaults by the ones provided during the execution of a given block and restores them afterwords.
238
- # This method is useful when the same set of options is to be given to a multitude of buttons.
239
- # @param keys_to_overwrite [Hash] Options that should be given to the buttons within the block, with their values
240
- # @param block [Block] Within this block, all omitted button options point to `keys_to_overwrite`
241
- def self.with_button_defaults(**keys_to_overwrite, &block)
242
- # Lazy initialize butto_defaults store if it hasn't been yet
243
- RequestStore.store[:button_defaults] ||= {}
244
- keys_to_overwrite.transform_keys!(&:to_sym)
245
- old_values = {}
246
- newly_defined_keys = keys_to_overwrite.keys - RequestStore.store[:button_defaults].keys
247
- keys_to_overwrite.each do |key, new_value|
248
- # Assign new value
249
- old_values[key] = RequestStore.store[:button_defaults][key]
250
- RequestStore.store[:button_defaults][key] = new_value
251
- end
252
- return_value = block.call
253
- # Restore previous value
254
- keys_to_overwrite.each_key do |key|
255
- RequestStore.store[:button_defaults][key] = old_values[key]
256
- end
257
- # Undefine keys that were not there previously
258
- newly_defined_keys.each { |key| RequestStore.store[:button_defaults].delete(key) }
259
- return return_value
260
- end
261
-
262
173
  # Goes through model_field_namespaces and returns the first hit for the given constant
263
174
  # @param constant [Constant] The constant that is searched, e.g. RichText -> would return e.g. Compony::ModelFields::RichText
264
175
  def self.model_field_class_for(constant)
@@ -279,6 +190,7 @@ require 'request_store'
279
190
  require 'schemacop'
280
191
  require 'simple_form'
281
192
 
193
+ require 'compony/intent'
282
194
  require 'compony/engine'
283
195
  require 'compony/model_fields/base'
284
196
  require 'compony/model_fields/anchormodel'
@@ -306,8 +218,10 @@ require 'compony/component_mixins/default/standalone/verb_dsl'
306
218
  require 'compony/component_mixins/default/standalone/resourceful_verb_dsl'
307
219
  require 'compony/component_mixins/default/labelling'
308
220
  require 'compony/component_mixins/resourceful'
221
+ require 'compony/exposed_intents_dsl'
309
222
  require 'compony/component'
310
- require 'compony/components/button'
223
+ require 'compony/components/buttons/link'
224
+ require 'compony/components/buttons/css_button'
311
225
  require 'compony/components/index'
312
226
  require 'compony/components/list'
313
227
  require 'compony/components/show'
@@ -323,3 +237,7 @@ require 'compony/request_context'
323
237
  require 'compony/version'
324
238
  require 'compony/view_helpers'
325
239
  require 'compony/controller_mixin'
240
+
241
+ if defined?(ActiveType::Object)
242
+ require 'compony/virtual_model'
243
+ end
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.7.0
4
+ version: 0.8.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: 2025-11-14 00:00:00.000000000 Z
12
+ date: 2025-11-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: yard
@@ -203,6 +203,9 @@ files:
203
203
  - doc/Compony/ComponentMixins/Resourceful.html
204
204
  - doc/Compony/Components.html
205
205
  - doc/Compony/Components/Button.html
206
+ - doc/Compony/Components/Buttons.html
207
+ - doc/Compony/Components/Buttons/CssButton.html
208
+ - doc/Compony/Components/Buttons/Link.html
206
209
  - doc/Compony/Components/Destroy.html
207
210
  - doc/Compony/Components/Edit.html
208
211
  - doc/Compony/Components/Form.html
@@ -213,6 +216,8 @@ files:
213
216
  - doc/Compony/Components/WithForm.html
214
217
  - doc/Compony/ControllerMixin.html
215
218
  - doc/Compony/Engine.html
219
+ - doc/Compony/ExposedIntentsDsl.html
220
+ - doc/Compony/Intent.html
216
221
  - doc/Compony/MethodAccessibleHash.html
217
222
  - doc/Compony/ModelFields.html
218
223
  - doc/Compony/ModelFields/Anchormodel.html
@@ -240,6 +245,7 @@ files:
240
245
  - doc/Compony/RequestContext.html
241
246
  - doc/Compony/Version.html
242
247
  - doc/Compony/ViewHelpers.html
248
+ - doc/Compony/VirtualModel.html
243
249
  - doc/ComponyController.html
244
250
  - doc/_index.html
245
251
  - doc/class_list.html
@@ -249,6 +255,29 @@ files:
249
255
  - doc/file.README.html
250
256
  - doc/file_list.html
251
257
  - doc/frames.html
258
+ - doc/guide/basic_component.md
259
+ - doc/guide/example.md
260
+ - doc/guide/feasibility.md
261
+ - doc/guide/generators.md
262
+ - doc/guide/inheritance.md
263
+ - doc/guide/installation.md
264
+ - doc/guide/intents.md
265
+ - doc/guide/internal_datastructures.md
266
+ - doc/guide/model_fields.md
267
+ - doc/guide/nesting.md
268
+ - doc/guide/ownership.md
269
+ - doc/guide/pre_built_components.md
270
+ - doc/guide/pre_built_components/destroy.md
271
+ - doc/guide/pre_built_components/edit.md
272
+ - doc/guide/pre_built_components/form.md
273
+ - doc/guide/pre_built_components/index.md
274
+ - doc/guide/pre_built_components/list.md
275
+ - doc/guide/pre_built_components/new.md
276
+ - doc/guide/pre_built_components/show.md
277
+ - doc/guide/pre_built_components/with_form.md
278
+ - doc/guide/resourceful.md
279
+ - doc/guide/standalone.md
280
+ - doc/guide/virtual_models.md
252
281
  - doc/imgs/intro-example-destroy.png
253
282
  - doc/imgs/intro-example-edit.png
254
283
  - doc/imgs/intro-example-index.png
@@ -271,7 +300,8 @@ files:
271
300
  - lib/compony/component_mixins/default/standalone/standalone_dsl.rb
272
301
  - lib/compony/component_mixins/default/standalone/verb_dsl.rb
273
302
  - lib/compony/component_mixins/resourceful.rb
274
- - lib/compony/components/button.rb
303
+ - lib/compony/components/buttons/css_button.rb
304
+ - lib/compony/components/buttons/link.rb
275
305
  - lib/compony/components/destroy.rb
276
306
  - lib/compony/components/edit.rb
277
307
  - lib/compony/components/form.rb
@@ -282,6 +312,8 @@ files:
282
312
  - lib/compony/components/with_form.rb
283
313
  - lib/compony/controller_mixin.rb
284
314
  - lib/compony/engine.rb
315
+ - lib/compony/exposed_intents_dsl.rb
316
+ - lib/compony/intent.rb
285
317
  - lib/compony/method_accessible_hash.rb
286
318
  - lib/compony/model_fields/anchormodel.rb
287
319
  - lib/compony/model_fields/association.rb
@@ -308,6 +340,7 @@ files:
308
340
  - lib/compony/request_context.rb
309
341
  - lib/compony/version.rb
310
342
  - lib/compony/view_helpers.rb
343
+ - lib/compony/virtual_model.rb
311
344
  - lib/generators/component/USAGE
312
345
  - lib/generators/component/component_generator.rb
313
346
  - lib/generators/component/templates/component.rb.erb
@@ -1,61 +0,0 @@
1
- module Compony
2
- module Components
3
- # @api description
4
- # This is the default button implementation, providing a minimal button
5
- class Button < Compony::Component
6
- SUPPORTED_TYPES = %i[button submit].freeze
7
-
8
- # path: If given a block, it will be evaluated in the helpers context when rendering
9
- # enabled: If given a block, it will be evaluated in the helpers context when rendering
10
- def initialize(*, label: nil, path: nil, method: nil, type: nil, enabled: nil, visible: nil, title: nil, button_params: nil, value: nil, **, &)
11
- @label = label || Compony.button_defaults[:label]
12
- @type = type&.to_sym || Compony.button_defaults[:type] || :button
13
- @path = path || Compony.button_defaults[:path] || 'javascript:void(0)'
14
- @method = method || Compony.button_defaults[:method]
15
- if @type != :button && !@method.nil?
16
- fail("Param `method` is only allowed for :button type buttons, but got method #{@method.inspect} for type #{@type.inspect}")
17
- end
18
- @method ||= :get
19
- @enabled = enabled
20
- @enabled = Compony.button_defaults[:enabled] if @enabled.nil?
21
- @enabled = true if @enabled.nil?
22
- @visible = visible
23
- @visible = Compony.button_defaults[:visible] if @visible.nil?
24
- @visible = true if @visible.nil?
25
- @title = title || Compony.button_defaults[:title]
26
- @button_params = button_params
27
- @value = value
28
-
29
- fail "Unsupported button type #{@type}, use on of: #{SUPPORTED_TYPES.inspect}" unless SUPPORTED_TYPES.include?(@type)
30
-
31
- super(*, **, &)
32
- end
33
-
34
- setup do
35
- before_render do
36
- if @path.respond_to?(:call)
37
- @path = instance_exec(&@path)
38
- end
39
- if @enabled.respond_to?(:call)
40
- @enabled = @enabled.call(controller)
41
- end
42
- if @visible.respond_to?(:call)
43
- @visible = @visible.call(controller)
44
- end
45
- @path = 'javascript:void(0)' unless @enabled
46
- end
47
-
48
- content do
49
- if @visible
50
- case @type
51
- when :button
52
- concat button_to(@label, @path, method: @method, disabled: !@enabled, title: @title, params: @button_params)
53
- when :submit
54
- concat button_tag(@label, type: :submit, disabled: !@enabled, title: @title, value: @value)
55
- end
56
- end
57
- end
58
- end
59
- end
60
- end
61
- end